diff --git a/src/main/java/li/cil/oc2/common/ext/ChunkAccessExt.java b/src/main/java/li/cil/oc2/common/ext/ChunkAccessExt.java deleted file mode 100644 index 75e1e6db..00000000 --- a/src/main/java/li/cil/oc2/common/ext/ChunkAccessExt.java +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -package li.cil.oc2.common.ext; - -import li.cil.oc2.common.mixin.ChunkMapMixin; -import li.cil.oc2.common.util.ChunkUtils; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.world.level.chunk.ChunkAccess; - -/** - * Interface injected into the {@link ChunkAccess} class. - *

- * Tracks a "lazy unsaved" flag, which is converted into the regular unsaved flag - * before certain manual save operations. - * - * @see ChunkUtils - * @see ChunkMapMixin - */ -public interface ChunkAccessExt { - /** - * Set the lazy unsaved flag for this instance. - *

- * This method is used by the utility methods in {@link ChunkUtils}. - */ - void setLazyUnsaved(); - - /** - * Set the unsaved flag for this instance, if the lazy unsaved flag is set, - * then clears the lazy unsaved flag. - *

- * This method is invoked from mixins injected into the {@link ChunkMap} class. - */ - void applyAndClearLazyUnsaved(); -} diff --git a/src/main/java/li/cil/oc2/common/mixin/ChunkAccessMixin.java b/src/main/java/li/cil/oc2/common/mixin/ChunkAccessMixin.java deleted file mode 100644 index 40e5615b..00000000 --- a/src/main/java/li/cil/oc2/common/mixin/ChunkAccessMixin.java +++ /dev/null @@ -1,37 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -package li.cil.oc2.common.mixin; - -import li.cil.oc2.common.ext.ChunkAccessExt; -import li.cil.oc2.common.util.ChunkUtils; -import net.minecraft.world.level.chunk.ChunkAccess; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -/** - * Tracks a "lazy unsaved" flag per {@link ChunkAccess} instance, to allow - * marking chunks as needing saving just in time for "hard" saves. - * - * @see ChunkMapMixin ChunkMapMixin for more information - * @see ChunkUtils - */ -@Mixin(ChunkAccess.class) -public abstract class ChunkAccessMixin implements ChunkAccessExt { - @Shadow - protected volatile boolean unsaved; - - private volatile boolean lazyUnsaved; - - @Override - public void setLazyUnsaved() { - lazyUnsaved = true; - } - - @Override - public void applyAndClearLazyUnsaved() { - if (!unsaved && lazyUnsaved) { - unsaved = true; - } - lazyUnsaved = false; - } -} diff --git a/src/main/java/li/cil/oc2/common/mixin/ChunkMapMixin.java b/src/main/java/li/cil/oc2/common/mixin/ChunkMapMixin.java deleted file mode 100644 index 57fc74c6..00000000 --- a/src/main/java/li/cil/oc2/common/mixin/ChunkMapMixin.java +++ /dev/null @@ -1,65 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -package li.cil.oc2.common.mixin; - -import com.mojang.datafixers.DataFixer; -import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -import li.cil.oc2.common.ext.ChunkAccessExt; -import li.cil.oc2.common.util.ChunkUtils; -import net.minecraft.server.level.ChunkHolder; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.storage.ChunkStorage; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.nio.file.Path; - -/** - * Hooks into {@link ChunkMap} saving code-paths for "hard" save operations. - *

- * Minecraft immediately serializes all chunk data, including {@link BlockEntity} NBT. - * This is a massive performance issue for blocks with state that changes every tick, - * such as computers and things accepting energy. - *

- * To avoid this per-frame serialization operations, we track a "lazy unsaved" flag per - * {@link ChunkAccess} using the {@link ChunkAccessMixin}, and flush this flag into the - * real unsaved flag during "hard" save operations, just before the flag would be - * checked. These save operations include: - *

- *

- * The flag is set using the injected interface {@link ChunkAccessExt}, via the utility - * methods in {@link ChunkUtils}. - * - * @see ChunkAccessMixin - * @see ChunkUtils - */ -@Mixin(ChunkMap.class) -public abstract class ChunkMapMixin extends ChunkStorage { - @Shadow private volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; - - public ChunkMapMixin(final Path path, final DataFixer dataFixer, final boolean sync) { - super(path, dataFixer, sync); - } - - @Inject(method = "saveAllChunks", at = {@At(value = "HEAD")}) - private void beforeAsyncSave(final CallbackInfo ci) { - visibleChunkMap.values().forEach(holder -> { - if (holder.wasAccessibleSinceLastSave()) { - final ChunkAccess chunkToSave = holder.getChunkToSave().getNow(null); - if (chunkToSave instanceof ChunkAccessExt ext) { - ext.applyAndClearLazyUnsaved(); - } - } - }); - } -} diff --git a/src/main/java/li/cil/oc2/common/mixin/ServerChunkCacheMixin.java b/src/main/java/li/cil/oc2/common/mixin/ServerChunkCacheMixin.java new file mode 100644 index 00000000..a2aec04f --- /dev/null +++ b/src/main/java/li/cil/oc2/common/mixin/ServerChunkCacheMixin.java @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT */ + +package li.cil.oc2.common.mixin; + +import li.cil.oc2.common.util.ChunkUtils; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.chunk.ChunkSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerChunkCache.class) +public abstract class ServerChunkCacheMixin extends ChunkSource { + @Inject(method = "save", at = @At("HEAD")) + private void applyLazyUnsavedChunks(final CallbackInfo ci) { + ChunkUtils.applyChunkLazyUnsaved(); + } +} diff --git a/src/main/java/li/cil/oc2/common/util/ChunkUtils.java b/src/main/java/li/cil/oc2/common/util/ChunkUtils.java index 630b98c6..da0ee155 100644 --- a/src/main/java/li/cil/oc2/common/util/ChunkUtils.java +++ b/src/main/java/li/cil/oc2/common/util/ChunkUtils.java @@ -3,7 +3,7 @@ package li.cil.oc2.common.util; import li.cil.oc2.api.API; -import li.cil.oc2.common.ext.ChunkAccessExt; +import li.cil.oc2.common.mixin.ServerChunkCacheMixin; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.world.level.ChunkPos; @@ -13,8 +13,18 @@ import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + @Mod.EventBusSubscriber(modid = API.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) public final class ChunkUtils { + /** + * All chunks marked for lazy saving. The lazy unsaved state will be applied when + * chunks unload and when chunks get explicitly saved, via the {@link ServerChunkCacheMixin}. + */ + private static final Set UNSAVED_CHUNKS = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + /** * This will mark a chunk unsaved lazily, right before an attempt to save it would be made due * to of these events: @@ -36,9 +46,7 @@ public final class ChunkUtils { * @param chunkAccess the chunk to set the flag for. */ public static void setLazyUnsaved(final ChunkAccess chunkAccess) { - if (chunkAccess instanceof ChunkAccessExt ext) { - ext.setLazyUnsaved(); - } + UNSAVED_CHUNKS.add(chunkAccess); } /** @@ -99,10 +107,24 @@ public final class ChunkUtils { } } + /** + * Marks the specified as unsaved, if it was marked as lazy unsaved before + * and clears the lazy unsaved flags. + * + * @param chunk the chunk to apply the lazy unsaved state for. + */ + public static void applyChunkLazyUnsaved() { + for (final ChunkAccess chunk : UNSAVED_CHUNKS) { + chunk.setUnsaved(true); + } + UNSAVED_CHUNKS.clear(); + } + @SubscribeEvent public static void handleChunkUnload(final ChunkEvent.Unload event) { - if (event.getChunk() instanceof ChunkAccessExt ext) { - ext.applyAndClearLazyUnsaved(); + final ChunkAccess chunk = event.getChunk(); + if (UNSAVED_CHUNKS.remove(chunk)) { + chunk.setUnsaved(true); } } } diff --git a/src/main/resources/mixins.oc2.json b/src/main/resources/mixins.oc2.json index 79f30cc7..3e9d9541 100644 --- a/src/main/resources/mixins.oc2.json +++ b/src/main/resources/mixins.oc2.json @@ -1,19 +1,18 @@ { - "minVersion": "0.8", - "compatibilityLevel": "JAVA_17", - "required": true, - "package": "li.cil.oc2.common.mixin", - "refmap": "mixins.oc2.refmap.json", - "mixins": [ - "ChunkAccessMixin", - "ChunkMapMixin" - ], - "client": [ - "FrustumMixin", - "LevelRendererMixin", - "MinecraftMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "minVersion": "0.8.5", + "compatibilityLevel": "JAVA_17", + "required": true, + "package": "li.cil.oc2.common.mixin", + "refmap": "mixins.oc2.refmap.json", + "mixins": [ + "ServerChunkCacheMixin" + ], + "client": [ + "FrustumMixin", + "LevelRendererMixin", + "MinecraftMixin" + ], + "injectors": { + "defaultRequire": 1 + } }