From c3bf97a3f52f24c8b2cde67aa0451005d838f451 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Wed, 7 May 2025 19:49:21 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=8C=BA=E5=9D=97=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LegacySlimeWorldDataStorage.java | 2 +- .../slimeworld/SlimeWorldDataStorage.java | 2 +- .../worldedit/FastAsyncWorldEditDelegate.java | 2 +- bukkit/loader/src/main/resources/config.yml | 5 +- .../plugin/injector/BukkitInjector.java | 80 ++++++++++++----- .../bukkit/world/BukkitWorldManager.java | 35 ++++---- .../core/plugin/config/Config.java | 8 +- .../craftengine/core/world/CEWorld.java | 2 +- .../craftengine/core/world/ChunkPos.java | 13 +++ .../core/world/chunk/InjectedHolder.java | 6 ++ .../CachedDefaultRegionFileStorage.java | 43 ++++++++++ .../storage/DefaultRegionFileStorage.java | 2 +- .../chunk/storage/DefaultStorageAdaptor.java | 4 +- .../DelayedDefaultRegionFileStorage.java | 85 ------------------- .../core/world/chunk/storage/RegionFile.java | 6 -- .../world/chunk/storage/WorldDataStorage.java | 2 +- gradle.properties | 4 +- 17 files changed, 154 insertions(+), 147 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CachedDefaultRegionFileStorage.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DelayedDefaultRegionFileStorage.java diff --git a/bukkit/compatibility/legacy/src/main/java/net/momirealms/craftengine/bukkit/compatibility/legacy/slimeworld/LegacySlimeWorldDataStorage.java b/bukkit/compatibility/legacy/src/main/java/net/momirealms/craftengine/bukkit/compatibility/legacy/slimeworld/LegacySlimeWorldDataStorage.java index 74acc1e05..4feff8a93 100644 --- a/bukkit/compatibility/legacy/src/main/java/net/momirealms/craftengine/bukkit/compatibility/legacy/slimeworld/LegacySlimeWorldDataStorage.java +++ b/bukkit/compatibility/legacy/src/main/java/net/momirealms/craftengine/bukkit/compatibility/legacy/slimeworld/LegacySlimeWorldDataStorage.java @@ -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); diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/slimeworld/SlimeWorldDataStorage.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/slimeworld/SlimeWorldDataStorage.java index ae6703087..663689520 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/slimeworld/SlimeWorldDataStorage.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/slimeworld/SlimeWorldDataStorage.java @@ -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); diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/FastAsyncWorldEditDelegate.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/FastAsyncWorldEditDelegate.java index 9127b6c7b..8d7ee7e7e 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/FastAsyncWorldEditDelegate.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/worldedit/FastAsyncWorldEditDelegate.java @@ -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) { diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index b25083cec..1c9c98fa2 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -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 | diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java index 0bb780dd4..9a54e77a1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java @@ -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; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java index ffc0b77bf..d527a0ab7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldManager.java @@ -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(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index e595457bf..fde4746d5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -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() { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java index 51bc86577..e36abc7ce 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/CEWorld.java @@ -49,7 +49,7 @@ public abstract class CEWorld { for (Map.Entry 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); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java index 6962e334b..115dd689a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/ChunkPos.java @@ -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; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedHolder.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedHolder.java index a625a0a47..4287019e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/InjectedHolder.java @@ -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); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CachedDefaultRegionFileStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CachedDefaultRegionFileStorage.java new file mode 100644 index 000000000..782bbd90b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CachedDefaultRegionFileStorage.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java index 132bc699f..bfeef7a58 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultRegionFileStorage.java @@ -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); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultStorageAdaptor.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultStorageAdaptor.java index 401ed6880..281add860 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultStorageAdaptor.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DefaultStorageAdaptor.java @@ -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)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DelayedDefaultRegionFileStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DelayedDefaultRegionFileStorage.java deleted file mode 100644 index f711fbbb3..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/DelayedDefaultRegionFileStorage.java +++ /dev/null @@ -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 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(); - } -} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java index b50452bae..0ee384791 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java @@ -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); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/WorldDataStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/WorldDataStorage.java index b3b4dd47d..bd44492a9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/WorldDataStorage.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/WorldDataStorage.java @@ -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; diff --git a/gradle.properties b/gradle.properties index 7d1d6d3f4..cb3c9ca19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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