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:
- *
- * - Chunk unloaded.
- * - Game paused (singleplayer).
- * - Save command.
- * - Server stopped.
- *
- *
- * 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
+ }
}