mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-27 02:49:15 +00:00
重构区块缓存
This commit is contained in:
@@ -42,7 +42,7 @@ public class LegacySlimeWorldDataStorage implements WorldDataStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk, boolean immediately) {
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk) {
|
||||
SlimeChunk slimeChunk = getWorld().getChunk(pos.x, pos.z);
|
||||
if (slimeChunk == null) return;
|
||||
CompoundTag nbt = DefaultChunkSerializer.serialize(chunk);
|
||||
|
||||
@@ -44,7 +44,7 @@ public class SlimeWorldDataStorage implements WorldDataStorage {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk, boolean immediately) {
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk) {
|
||||
SlimeChunk slimeChunk = getWorld().getChunk(pos.x, pos.z);
|
||||
if (slimeChunk == null) return;
|
||||
CompoundTag nbt = DefaultChunkSerializer.serialize(chunk);
|
||||
|
||||
@@ -196,7 +196,7 @@ public class FastAsyncWorldEditDelegate extends AbstractDelegateExtent {
|
||||
try {
|
||||
for (CEChunk ceChunk : this.chunksToSave) {
|
||||
CraftEngine.instance().debug(() -> "Saving chunk " + ceChunk.chunkPos());
|
||||
this.ceWorld.worldDataStorage().writeChunkAt(ceChunk.chunkPos(), ceChunk, true);
|
||||
this.ceWorld.worldDataStorage().writeChunkAt(ceChunk.chunkPos(), ceChunk);
|
||||
}
|
||||
this.chunksToSave.clear();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -345,8 +345,9 @@ light-system:
|
||||
force-update-light: false
|
||||
|
||||
chunk-system:
|
||||
# Unloaded chunks may be loaded soon. Delaying serialization can improve performance, especially for those double-dimension mob farms.
|
||||
delay-serialization: 20 # seconds -1 = disable
|
||||
# With cache system, those frequently load/unload chunks would consume fewer resources on serialization
|
||||
# Enabling this option will increase memory consumption to a certain extent
|
||||
cache-system: true
|
||||
# 1 = NONE | Compression Speed | Decompress Speed | Compression Ratio | Memory Usage |
|
||||
# 2 = DEFLATE | Medium-Slow Medium Moderate Low |
|
||||
# 3 = GZIP | Medium-Slow Medium Moderate Low |
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.implementation.bind.annotation.SuperCall;
|
||||
import net.bytebuddy.implementation.bind.annotation.This;
|
||||
import net.bytebuddy.implementation.bytecode.assign.Assigner;
|
||||
import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
|
||||
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
|
||||
import net.bytebuddy.implementation.bytecode.member.MethodReturn;
|
||||
@@ -77,6 +78,7 @@ public class BukkitInjector {
|
||||
|
||||
private static Class<?> clazz$InjectedPalettedContainer;
|
||||
private static Class<?> clazz$InjectedLevelChunkSection;
|
||||
private static MethodHandle constructor$InjectedLevelChunkSection;
|
||||
|
||||
private static VarHandle varHandle$InjectedPalettedContainer$target;
|
||||
|
||||
@@ -104,6 +106,7 @@ public class BukkitInjector {
|
||||
.name("net.minecraft.world.level.chunk.InjectedPalettedContainer")
|
||||
.implement(InjectedHolder.Palette.class)
|
||||
.defineField("target", Reflections.clazz$PalettedContainer, Visibility.PUBLIC)
|
||||
.defineField("active", boolean.class, Visibility.PUBLIC)
|
||||
.defineField("cesection", CESection.class, Visibility.PRIVATE)
|
||||
.defineField("cechunk", CEChunk.class, Visibility.PRIVATE)
|
||||
.defineField("cepos", SectionPos.class, Visibility.PRIVATE)
|
||||
@@ -116,6 +119,10 @@ public class BukkitInjector {
|
||||
.intercept(MethodDelegation.to(GetAndSetInterceptor.INSTANCE))
|
||||
.method(ElementMatchers.named("target"))
|
||||
.intercept(FieldAccessor.ofField("target"))
|
||||
.method(ElementMatchers.named("setTarget"))
|
||||
.intercept(FieldAccessor.ofField("target").withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC))
|
||||
.method(ElementMatchers.named("isActive").or(ElementMatchers.named("setActive")))
|
||||
.intercept(FieldAccessor.ofField("active"))
|
||||
.method(ElementMatchers.named("ceSection"))
|
||||
.intercept(FieldAccessor.ofField("cesection"))
|
||||
.method(ElementMatchers.named("ceChunk"))
|
||||
@@ -125,13 +132,14 @@ public class BukkitInjector {
|
||||
.make()
|
||||
.load(BukkitInjector.class.getClassLoader())
|
||||
.getLoaded();
|
||||
varHandle$InjectedPalettedContainer$target = Objects.requireNonNull(ReflectionUtils.findVarHandle(clazz$InjectedPalettedContainer, "target", Reflections.clazz$PalettedContainer));
|
||||
//varHandle$InjectedPalettedContainer$target = Objects.requireNonNull(ReflectionUtils.findVarHandle(clazz$InjectedPalettedContainer, "target", Reflections.clazz$PalettedContainer));
|
||||
|
||||
// Level Chunk Section
|
||||
clazz$InjectedLevelChunkSection = byteBuddy
|
||||
.subclass(Reflections.clazz$LevelChunkSection)
|
||||
.subclass(Reflections.clazz$LevelChunkSection, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
|
||||
.name("net.minecraft.world.level.chunk.InjectedLevelChunkSection")
|
||||
.implement(InjectedHolder.Section.class)
|
||||
.defineField("active", boolean.class, Visibility.PUBLIC)
|
||||
.defineField("cesection", CESection.class, Visibility.PRIVATE)
|
||||
.defineField("cechunk", CEChunk.class, Visibility.PRIVATE)
|
||||
.defineField("cepos", SectionPos.class, Visibility.PRIVATE)
|
||||
@@ -143,10 +151,16 @@ public class BukkitInjector {
|
||||
.intercept(FieldAccessor.ofField("cechunk"))
|
||||
.method(ElementMatchers.named("cePos"))
|
||||
.intercept(FieldAccessor.ofField("cepos"))
|
||||
.method(ElementMatchers.named("isActive").or(ElementMatchers.named("setActive")))
|
||||
.intercept(FieldAccessor.ofField("active"))
|
||||
.make()
|
||||
.load(BukkitInjector.class.getClassLoader())
|
||||
.getLoaded();
|
||||
|
||||
constructor$InjectedLevelChunkSection = MethodHandles.publicLookup().in(clazz$InjectedLevelChunkSection)
|
||||
.findConstructor(clazz$InjectedLevelChunkSection, MethodType.methodType(void.class, Reflections.clazz$PalettedContainer, Reflections.clazz$PalettedContainer))
|
||||
.asType(MethodType.methodType(Reflections.clazz$LevelChunkSection, Reflections.clazz$PalettedContainer, Reflections.clazz$PalettedContainer));
|
||||
|
||||
// State Predicate
|
||||
DynamicType.Unloaded<?> alwaysTrue = byteBuddy
|
||||
.subclass(Reflections.clazz$StatePredicate)
|
||||
@@ -408,32 +422,50 @@ public class BukkitInjector {
|
||||
try {
|
||||
if (Config.injectionTarget()) {
|
||||
Object container = FastNMS.INSTANCE.field$LevelChunkSection$states(targetSection);
|
||||
if (!(container instanceof InjectedHolder.Palette)) {
|
||||
if (!(container instanceof InjectedHolder.Palette holder)) {
|
||||
InjectedHolder.Palette injectedObject;
|
||||
if (Config.fastInjection()) {
|
||||
injectedObject = FastNMS.INSTANCE.createInjectedPalettedContainerHolder(container);
|
||||
} else {
|
||||
injectedObject = (InjectedHolder.Palette) Reflections.UNSAFE.allocateInstance(clazz$InjectedPalettedContainer);
|
||||
varHandle$InjectedPalettedContainer$target.set(injectedObject, container);
|
||||
injectedObject.setTarget(container);
|
||||
//varHandle$InjectedPalettedContainer$target.set(injectedObject, container);
|
||||
}
|
||||
injectedObject.ceChunk(chunk);
|
||||
injectedObject.ceSection(ceSection);
|
||||
injectedObject.cePos(pos);
|
||||
injectedObject.setActive(true);
|
||||
Reflections.varHandle$PalettedContainer$data.setVolatile(injectedObject, Reflections.varHandle$PalettedContainer$data.get(container));
|
||||
Reflections.field$LevelChunkSection$states.set(targetSection, injectedObject);
|
||||
} else {
|
||||
holder.ceChunk(chunk);
|
||||
holder.ceSection(ceSection);
|
||||
holder.cePos(pos);
|
||||
holder.setActive(true);
|
||||
}
|
||||
} else {
|
||||
InjectedHolder.Section injectedObject;
|
||||
if (true) {
|
||||
injectedObject = FastNMS.INSTANCE.createInjectedLevelChunkSectionHolder(targetSection);
|
||||
if (!(targetSection instanceof InjectedHolder.Section holder)) {
|
||||
InjectedHolder.Section injectedObject;
|
||||
if (Config.fastInjection()) {
|
||||
injectedObject = FastNMS.INSTANCE.createInjectedLevelChunkSectionHolder(targetSection);
|
||||
} else {
|
||||
injectedObject = (InjectedHolder.Section) constructor$InjectedLevelChunkSection.invoke(
|
||||
FastNMS.INSTANCE.field$LevelChunkSection$states(targetSection), FastNMS.INSTANCE.field$LevelChunkSection$biomes(targetSection));
|
||||
}
|
||||
injectedObject.ceChunk(chunk);
|
||||
injectedObject.ceSection(ceSection);
|
||||
injectedObject.cePos(pos);
|
||||
injectedObject.setActive(true);
|
||||
callback.accept(injectedObject);
|
||||
} else {
|
||||
holder.ceChunk(chunk);
|
||||
holder.ceSection(ceSection);
|
||||
holder.cePos(pos);
|
||||
holder.setActive(true);
|
||||
}
|
||||
injectedObject.ceChunk(chunk);
|
||||
injectedObject.ceSection(ceSection);
|
||||
injectedObject.cePos(pos);
|
||||
callback.accept(injectedObject);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to inject chunk section", e);
|
||||
} catch (Throwable e) {
|
||||
CraftEngine.instance().logger().severe("Failed to inject chunk section " + pos, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,15 +482,17 @@ public class BukkitInjector {
|
||||
if (Config.injectionTarget()) {
|
||||
Object states = FastNMS.INSTANCE.field$LevelChunkSection$states(section);
|
||||
if (states instanceof InjectedHolder.Palette holder) {
|
||||
try {
|
||||
Reflections.field$LevelChunkSection$states.set(section, holder.target());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
CraftEngine.instance().logger().severe("Failed to uninject palette", e);
|
||||
}
|
||||
holder.setActive(false);
|
||||
// try {
|
||||
// Reflections.field$LevelChunkSection$states.set(section, holder.target());
|
||||
// } catch (ReflectiveOperationException e) {
|
||||
// CraftEngine.instance().logger().severe("Failed to uninject palette", e);
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
if (section instanceof InjectedHolder.Section holder) {
|
||||
return FastNMS.INSTANCE.constructor$LevelChunkSection(holder);
|
||||
holder.setActive(false);
|
||||
//return FastNMS.INSTANCE.constructor$LevelChunkSection(holder);
|
||||
}
|
||||
}
|
||||
return section;
|
||||
@@ -730,7 +764,9 @@ public class BukkitInjector {
|
||||
int z = (int) args[2];
|
||||
Object newState = args[3];
|
||||
Object previousState = superMethod.call();
|
||||
compareAndUpdateBlockState(x, y, z, newState, previousState, holder);
|
||||
if (holder.isActive()) {
|
||||
compareAndUpdateBlockState(x, y, z, newState, previousState, holder);
|
||||
}
|
||||
return previousState;
|
||||
}
|
||||
}
|
||||
@@ -747,7 +783,9 @@ public class BukkitInjector {
|
||||
int z = (int) args[2];
|
||||
Object newState = args[3];
|
||||
Object previousState = FastNMS.INSTANCE.method$PalettedContainer$getAndSet(targetStates, x, y, z, newState);
|
||||
compareAndUpdateBlockState(x, y, z, newState, previousState, holder);
|
||||
if (holder.isActive()) {
|
||||
compareAndUpdateBlockState(x, y, z, newState, previousState, holder);
|
||||
}
|
||||
return previousState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,27 +266,24 @@ public class BukkitWorldManager implements WorldManager, Listener {
|
||||
if (ceChunk != null) {
|
||||
if (ceChunk.dirty()) {
|
||||
try {
|
||||
world.worldDataStorage().writeChunkAt(pos, ceChunk, false);
|
||||
this.plugin.debug(() -> "[Dirty Chunk]" + pos + " unloaded");
|
||||
world.worldDataStorage().writeChunkAt(pos, ceChunk);
|
||||
ceChunk.setDirty(false);
|
||||
} catch (IOException e) {
|
||||
this.plugin.logger().warn("Failed to write chunk tag at " + chunk.getX() + " " + chunk.getZ(), e);
|
||||
}
|
||||
}
|
||||
if (Config.restoreVanillaBlocks()) {
|
||||
boolean unsaved = false;
|
||||
CESection[] ceSections = ceChunk.sections();
|
||||
Object worldServer = FastNMS.INSTANCE.field$CraftChunk$worldServer(chunk);
|
||||
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
|
||||
Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunkAtIfLoadedMainThread(chunkSource, chunk.getX(), chunk.getZ());
|
||||
Object[] sections = FastNMS.INSTANCE.method$ChunkAccess$getSections(levelChunk);
|
||||
for (int i = 0; i < ceSections.length; i++) {
|
||||
CESection ceSection = ceSections[i];
|
||||
Object section = sections[i];
|
||||
Object uninjectedSection = BukkitInjector.uninjectLevelChunkSection(section);
|
||||
if (uninjectedSection != section) {
|
||||
sections[i] = uninjectedSection;
|
||||
section = uninjectedSection;
|
||||
}
|
||||
boolean unsaved = false;
|
||||
CESection[] ceSections = ceChunk.sections();
|
||||
Object worldServer = FastNMS.INSTANCE.field$CraftChunk$worldServer(chunk);
|
||||
Object chunkSource = FastNMS.INSTANCE.method$ServerLevel$getChunkSource(worldServer);
|
||||
Object levelChunk = FastNMS.INSTANCE.method$ServerChunkCache$getChunkAtIfLoadedMainThread(chunkSource, chunk.getX(), chunk.getZ());
|
||||
Object[] sections = FastNMS.INSTANCE.method$ChunkAccess$getSections(levelChunk);
|
||||
for (int i = 0; i < ceSections.length; i++) {
|
||||
CESection ceSection = ceSections[i];
|
||||
Object section = sections[i];
|
||||
BukkitInjector.uninjectLevelChunkSection(section);
|
||||
if (Config.restoreVanillaBlocks()) {
|
||||
if (!ceSection.statesContainer().isEmpty()) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
@@ -301,9 +298,9 @@ public class BukkitWorldManager implements WorldManager, Listener {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (unsaved && !FastNMS.INSTANCE.method$LevelChunk$isUnsaved(levelChunk)) {
|
||||
FastNMS.INSTANCE.method$LevelChunk$markUnsaved(levelChunk);
|
||||
}
|
||||
}
|
||||
if (unsaved && !FastNMS.INSTANCE.method$LevelChunk$isUnsaved(levelChunk)) {
|
||||
FastNMS.INSTANCE.method$LevelChunk$markUnsaved(levelChunk);
|
||||
}
|
||||
ceChunk.unload();
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class Config {
|
||||
protected boolean chunk_system$restore_vanilla_blocks_on_chunk_unload;
|
||||
protected boolean chunk_system$restore_custom_blocks_on_chunk_load;
|
||||
protected boolean chunk_system$sync_custom_blocks_on_chunk_load;
|
||||
protected int chunk_system$delay_serialization;
|
||||
protected boolean chunk_system$cache_system;
|
||||
protected boolean chunk_system$injection$use_fast_method;
|
||||
protected boolean chunk_system$injection$target;
|
||||
|
||||
@@ -274,7 +274,7 @@ public class Config {
|
||||
chunk_system$restore_vanilla_blocks_on_chunk_unload = config.getBoolean("chunk-system.restore-vanilla-blocks-on-chunk-unload", true);
|
||||
chunk_system$restore_custom_blocks_on_chunk_load = config.getBoolean("chunk-system.restore-custom-blocks-on-chunk-load", true);
|
||||
chunk_system$sync_custom_blocks_on_chunk_load = config.getBoolean("chunk-system.sync-custom-blocks-on-chunk-load", false);
|
||||
chunk_system$delay_serialization = config.getInt("chunk-system.delay-serialization", 20);
|
||||
chunk_system$cache_system = config.getBoolean("chunk-system.cache-system", true);
|
||||
chunk_system$injection$use_fast_method = config.getBoolean("chunk-system.injection.use-fast-method", false);
|
||||
if (firstTime) {
|
||||
chunk_system$injection$target = config.getEnum("chunk-system.injection.target", InjectionTarget.class, InjectionTarget.PALETTE) == InjectionTarget.PALETTE;
|
||||
@@ -699,8 +699,8 @@ public class Config {
|
||||
return instance.furniture$collision_entity_type;
|
||||
}
|
||||
|
||||
public static int delaySerialization() {
|
||||
return instance.chunk_system$delay_serialization;
|
||||
public static boolean enableChunkCache() {
|
||||
return instance.chunk_system$cache_system;
|
||||
}
|
||||
|
||||
public static boolean fastInjection() {
|
||||
|
||||
@@ -49,7 +49,7 @@ public abstract class CEWorld {
|
||||
for (Map.Entry<Long, CEChunk> entry : this.loadedChunkMap.entrySet()) {
|
||||
CEChunk chunk = entry.getValue();
|
||||
if (chunk.dirty()) {
|
||||
worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), chunk, true);
|
||||
worldDataStorage.writeChunkAt(new ChunkPos(entry.getKey()), chunk);
|
||||
chunk.setDirty(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,4 +68,17 @@ public class ChunkPos {
|
||||
public static long asLong(int chunkX, int chunkZ) {
|
||||
return (long) chunkX & 4294967295L | ((long) chunkZ & 4294967295L) << 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (!(o instanceof ChunkPos chunkPos)) return false;
|
||||
return x == chunkPos.x && z == chunkPos.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = x;
|
||||
result = 31 * result + z;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import net.momirealms.craftengine.core.world.SectionPos;
|
||||
|
||||
public interface InjectedHolder {
|
||||
|
||||
boolean isActive();
|
||||
|
||||
void setActive(boolean active);
|
||||
|
||||
CESection ceSection();
|
||||
|
||||
void ceSection(CESection section);
|
||||
@@ -22,5 +26,7 @@ public interface InjectedHolder {
|
||||
interface Palette extends InjectedHolder {
|
||||
|
||||
Object target();
|
||||
|
||||
void setTarget(Object target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.momirealms.craftengine.core.world.chunk.storage;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.Scheduler;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.world.CEWorld;
|
||||
import net.momirealms.craftengine.core.world.ChunkPos;
|
||||
import net.momirealms.craftengine.core.world.chunk.CEChunk;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CachedDefaultRegionFileStorage extends DefaultRegionFileStorage {
|
||||
private final Cache<ChunkPos, CEChunk> chunkCache;
|
||||
|
||||
public CachedDefaultRegionFileStorage(Path directory) {
|
||||
super(directory);
|
||||
this.chunkCache = Caffeine.newBuilder()
|
||||
.executor(CraftEngine.instance().scheduler().async())
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CEChunk readChunkAt(@NotNull CEWorld world, @NotNull ChunkPos pos) throws IOException {
|
||||
CEChunk chunk = this.chunkCache.getIfPresent(pos);
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
chunk = super.readChunkAt(world, pos);
|
||||
this.chunkCache.put(pos, chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ public class DefaultRegionFileStorage implements WorldDataStorage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk, boolean immediately) throws IOException {
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk) throws IOException {
|
||||
CompoundTag nbt = DefaultChunkSerializer.serialize(chunk);
|
||||
writeChunkTagAt(pos, nbt);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ public class DefaultStorageAdaptor implements StorageAdaptor {
|
||||
|
||||
@Override
|
||||
public @NotNull WorldDataStorage adapt(@NotNull World world) {
|
||||
if (Config.delaySerialization() > 0) {
|
||||
return new DelayedDefaultRegionFileStorage(world.directory().resolve(CEWorld.REGION_DIRECTORY), Config.delaySerialization());
|
||||
if (Config.enableChunkCache()) {
|
||||
return new CachedDefaultRegionFileStorage(world.directory().resolve(CEWorld.REGION_DIRECTORY));
|
||||
} else {
|
||||
return new DefaultRegionFileStorage(world.directory().resolve(CEWorld.REGION_DIRECTORY));
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package net.momirealms.craftengine.core.world.chunk.storage;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.world.CEWorld;
|
||||
import net.momirealms.craftengine.core.world.ChunkPos;
|
||||
import net.momirealms.craftengine.core.world.chunk.CEChunk;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DelayedDefaultRegionFileStorage extends DefaultRegionFileStorage {
|
||||
private final Cache<ChunkPos, CEChunk> chunkCache;
|
||||
private boolean isClosed;
|
||||
|
||||
public DelayedDefaultRegionFileStorage(Path directory, int time) {
|
||||
super(directory);
|
||||
this.chunkCache = Caffeine.newBuilder()
|
||||
.expireAfterWrite(time, TimeUnit.SECONDS)
|
||||
.removalListener((ChunkPos key, CEChunk value, RemovalCause cause) -> {
|
||||
if (key == null || value == null || isClosed) {
|
||||
return;
|
||||
}
|
||||
if (cause == RemovalCause.EXPIRED || cause == RemovalCause.SIZE) {
|
||||
try {
|
||||
super.writeChunkAt(key, value, true);
|
||||
} catch (ClosedChannelException e) {
|
||||
if (this.isClosed) {
|
||||
return;
|
||||
}
|
||||
CraftEngine.instance().logger().warn("Failed to write chunk at " + key, e);
|
||||
} catch (IOException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to write chunk at " + key, e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CEChunk readChunkAt(@NotNull CEWorld world, @NotNull ChunkPos pos) throws IOException {
|
||||
CEChunk chunk = this.chunkCache.asMap().remove(pos);
|
||||
if (chunk != null) {
|
||||
return chunk;
|
||||
}
|
||||
return super.readChunkAt(world, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk, boolean immediately) throws IOException {
|
||||
if (immediately) {
|
||||
super.writeChunkAt(pos, chunk, true);
|
||||
return;
|
||||
}
|
||||
if (chunk.isEmpty()) {
|
||||
super.writeChunkTagAt(pos, null);
|
||||
return;
|
||||
}
|
||||
this.chunkCache.put(pos, chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
this.isClosed = true;
|
||||
this.saveCache();
|
||||
this.chunkCache.cleanUp();
|
||||
super.close();
|
||||
}
|
||||
|
||||
private void saveCache() {
|
||||
try {
|
||||
for (var chunk : this.chunkCache.asMap().entrySet()) {
|
||||
super.writeChunkAt(chunk.getKey(), chunk.getValue(), true);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to save chunks", e);
|
||||
}
|
||||
this.chunkCache.invalidateAll();
|
||||
}
|
||||
}
|
||||
@@ -309,9 +309,6 @@ public class RegionFile implements AutoCloseable {
|
||||
}
|
||||
|
||||
public void clear(ChunkPos pos) throws IOException {
|
||||
if (!this.fileChannel.isOpen()) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
int chunkLocation = RegionFile.getChunkLocation(pos);
|
||||
int sectorInfo = this.sectorInfo.get(chunkLocation);
|
||||
if (sectorInfo != INFO_NOT_PRESENT) {
|
||||
@@ -325,9 +322,6 @@ public class RegionFile implements AutoCloseable {
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
protected synchronized void write(ChunkPos pos, ByteBuffer buf) throws IOException {
|
||||
if (!this.fileChannel.isOpen()) {
|
||||
throw new ClosedChannelException();
|
||||
}
|
||||
// get old offset info
|
||||
int offsetIndex = RegionFile.getChunkLocation(pos);
|
||||
int previousSectorInfo = this.sectorInfo.get(offsetIndex);
|
||||
|
||||
@@ -12,7 +12,7 @@ public interface WorldDataStorage {
|
||||
@NotNull
|
||||
CEChunk readChunkAt(@NotNull CEWorld world, @NotNull ChunkPos pos) throws IOException;
|
||||
|
||||
void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk, boolean immediately) throws IOException;
|
||||
void writeChunkAt(@NotNull ChunkPos pos, @NotNull CEChunk chunk) throws IOException;
|
||||
|
||||
void flush() throws IOException;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G
|
||||
|
||||
# Project settings
|
||||
# Rule: [major update].[feature update].[bug fix]
|
||||
project_version=0.0.53-beta.6
|
||||
project_version=0.0.53-beta.7
|
||||
config_version=32
|
||||
lang_version=12
|
||||
project_group=net.momirealms
|
||||
@@ -50,7 +50,7 @@ byte_buddy_version=1.17.5
|
||||
ahocorasick_version=0.6.3
|
||||
snake_yaml_version=2.4
|
||||
anti_grief_version=0.15
|
||||
nms_helper_version=0.65.12
|
||||
nms_helper_version=0.65.15
|
||||
evalex_version=3.5.0
|
||||
reactive_streams_version=1.0.4
|
||||
amazon_awssdk_version=2.31.23
|
||||
|
||||
Reference in New Issue
Block a user