Skip to content

Commit 2bb1fdf

Browse files
committed
Ensure tick lists are serialized on main thread
Closes #10
1 parent 2dc53b1 commit 2bb1fdf

File tree

8 files changed

+225
-0
lines changed

8 files changed

+225
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.yatopiamc.c2me.common.threading.chunkio;
2+
3+
import net.minecraft.nbt.ListTag;
4+
5+
public interface ICachedChunkTickScheduler {
6+
7+
void prepareCachedNbt();
8+
9+
ListTag getCachedNbt();
10+
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.yatopiamc.c2me.common.threading.chunkio;
2+
3+
import net.minecraft.nbt.ListTag;
4+
import net.minecraft.nbt.Tag;
5+
import net.minecraft.util.math.ChunkPos;
6+
7+
public interface ICachedServerTickScheduler {
8+
9+
void prepareCachedNbt(ChunkPos pos);
10+
11+
ListTag getCachedNbt(ChunkPos pos);
12+
}

src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinChunkSerializer.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.yatopiamc.c2me.mixin.threading.chunkio;
22

3+
import net.minecraft.nbt.ListTag;
4+
import net.minecraft.server.world.ServerTickScheduler;
5+
import net.minecraft.server.world.SimpleTickScheduler;
36
import net.minecraft.util.math.ChunkPos;
47
import net.minecraft.util.math.ChunkSectionPos;
58
import net.minecraft.world.ChunkSerializer;
9+
import net.minecraft.world.ChunkTickScheduler;
610
import net.minecraft.world.LightType;
711
import net.minecraft.world.chunk.ChunkNibbleArray;
812
import net.minecraft.world.chunk.ChunkSection;
@@ -12,6 +16,8 @@
1216
import org.spongepowered.asm.mixin.injection.At;
1317
import org.spongepowered.asm.mixin.injection.Redirect;
1418
import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoMainThreadTaskUtils;
19+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedChunkTickScheduler;
20+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedServerTickScheduler;
1521

1622
@Mixin(ChunkSerializer.class)
1723
public class MixinChunkSerializer {
@@ -31,4 +37,34 @@ private static void onPoiStorageInitForPalette(PointOfInterestStorage pointOfInt
3137
ChunkIoMainThreadTaskUtils.executeMain(() -> pointOfInterestStorage.initForPalette(chunkPos, chunkSection));
3238
}
3339

40+
@Redirect(method = "serialize", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/ChunkTickScheduler;toNbt()Lnet/minecraft/nbt/ListTag;"))
41+
private static ListTag onChunkTickSchedulerToNbt(@SuppressWarnings("rawtypes") ChunkTickScheduler chunkTickScheduler) {
42+
if (chunkTickScheduler instanceof ICachedChunkTickScheduler) {
43+
return ((ICachedChunkTickScheduler) chunkTickScheduler).getCachedNbt();
44+
} else {
45+
new IllegalStateException("Unable to take cached ticklist. Falling back to uncached query. This will affect data integrity. Incompatible mods?").printStackTrace();
46+
return chunkTickScheduler.toNbt();
47+
}
48+
}
49+
50+
@Redirect(method = "serialize", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/SimpleTickScheduler;toNbt()Lnet/minecraft/nbt/ListTag;"))
51+
private static ListTag onSimpleTickSchedulerToNbt(@SuppressWarnings("rawtypes") SimpleTickScheduler simpleTickScheduler) {
52+
if (simpleTickScheduler instanceof ICachedChunkTickScheduler) {
53+
return ((ICachedChunkTickScheduler) simpleTickScheduler).getCachedNbt();
54+
} else {
55+
new IllegalStateException("Unable to take cached ticklist. Falling back to uncached query. This will affect data integrity. Incompatible mods?").printStackTrace();
56+
return simpleTickScheduler.toNbt();
57+
}
58+
}
59+
60+
@Redirect(method = "serialize", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerTickScheduler;toTag(Lnet/minecraft/util/math/ChunkPos;)Lnet/minecraft/nbt/ListTag;"))
61+
private static ListTag onServerTickSchedulerToTag(@SuppressWarnings("rawtypes") ServerTickScheduler serverTickScheduler, ChunkPos chunkPos) {
62+
if (serverTickScheduler instanceof ICachedServerTickScheduler) {
63+
return ((ICachedServerTickScheduler) serverTickScheduler).getCachedNbt(chunkPos);
64+
} else {
65+
new IllegalStateException("Unable to take cached ticklist. Falling back to uncached query. This will affect data integrity. Incompatible mods?").printStackTrace();
66+
return serverTickScheduler.toTag(chunkPos);
67+
}
68+
}
69+
3470
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.yatopiamc.c2me.mixin.threading.chunkio;
2+
3+
import net.minecraft.nbt.ListTag;
4+
import net.minecraft.nbt.Tag;
5+
import net.minecraft.util.math.ChunkPos;
6+
import net.minecraft.world.ChunkTickScheduler;
7+
import org.spongepowered.asm.mixin.Final;
8+
import org.spongepowered.asm.mixin.Mixin;
9+
import org.spongepowered.asm.mixin.Shadow;
10+
import org.spongepowered.asm.mixin.injection.At;
11+
import org.spongepowered.asm.mixin.injection.Inject;
12+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
13+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedChunkTickScheduler;
14+
15+
import java.util.concurrent.atomic.AtomicReference;
16+
17+
@Mixin(ChunkTickScheduler.class)
18+
public abstract class MixinChunkTickScheduler implements ICachedChunkTickScheduler {
19+
20+
@Shadow public abstract ListTag toNbt();
21+
22+
@Shadow @Final private ChunkPos pos;
23+
private AtomicReference<ListTag> preparedNbt = new AtomicReference<>();
24+
25+
@Inject(method = "<init>", at = @At("RETURN"))
26+
private void onInit(CallbackInfo info) {
27+
preparedNbt = new AtomicReference<>();
28+
}
29+
30+
@Override
31+
public void prepareCachedNbt() {
32+
if (preparedNbt == null) preparedNbt = new AtomicReference<>();
33+
preparedNbt.set(toNbt());
34+
}
35+
36+
@Override
37+
public ListTag getCachedNbt() {
38+
if (preparedNbt == null) preparedNbt = new AtomicReference<>();
39+
if (preparedNbt.get() == null) {
40+
new IllegalStateException("Tried to serialize ticklist with no cached nbt for chunk " + pos + "! This will affect data integrity. Incompatible mods?").printStackTrace();
41+
prepareCachedNbt();
42+
}
43+
final ListTag preparedNbt = this.preparedNbt.get();
44+
this.preparedNbt = null;
45+
return preparedNbt;
46+
}
47+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.yatopiamc.c2me.mixin.threading.chunkio;
2+
3+
import com.google.common.base.Preconditions;
4+
import net.minecraft.nbt.ListTag;
5+
import net.minecraft.server.world.ServerTickScheduler;
6+
import net.minecraft.util.math.ChunkPos;
7+
import org.spongepowered.asm.mixin.Mixin;
8+
import org.spongepowered.asm.mixin.Shadow;
9+
import org.spongepowered.asm.mixin.injection.At;
10+
import org.spongepowered.asm.mixin.injection.Inject;
11+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
12+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedServerTickScheduler;
13+
14+
import java.util.concurrent.ConcurrentHashMap;
15+
16+
@Mixin(ServerTickScheduler.class)
17+
public abstract class MixinServerTickScheduler implements ICachedServerTickScheduler {
18+
19+
@Shadow public abstract ListTag toTag(ChunkPos chunkPos);
20+
21+
private ConcurrentHashMap<ChunkPos, ListTag> cachedNbt = new ConcurrentHashMap<>();
22+
23+
@Inject(method = "<init>", at = @At("RETURN"))
24+
private void onInit(CallbackInfo info) {
25+
cachedNbt = new ConcurrentHashMap<>();
26+
}
27+
28+
@Override
29+
public void prepareCachedNbt(ChunkPos pos) {
30+
Preconditions.checkNotNull(pos);
31+
if (cachedNbt == null) cachedNbt = new ConcurrentHashMap<>();
32+
cachedNbt.put(pos, toTag(pos));
33+
}
34+
35+
@Override
36+
public ListTag getCachedNbt(ChunkPos pos) {
37+
Preconditions.checkNotNull(pos);
38+
if (cachedNbt == null) cachedNbt = new ConcurrentHashMap<>();
39+
if (!cachedNbt.containsKey(pos)) {
40+
new IllegalStateException("Tried to serialize ticklist with no cached nbt for chunk " + pos + "! This will affect data integrity. Incompatible mods?").printStackTrace();
41+
prepareCachedNbt(pos);
42+
}
43+
return this.cachedNbt.remove(pos);
44+
}
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.yatopiamc.c2me.mixin.threading.chunkio;
2+
3+
import net.minecraft.nbt.ListTag;
4+
import net.minecraft.nbt.Tag;
5+
import net.minecraft.server.world.SimpleTickScheduler;
6+
import org.spongepowered.asm.mixin.Mixin;
7+
import org.spongepowered.asm.mixin.Shadow;
8+
import org.spongepowered.asm.mixin.injection.At;
9+
import org.spongepowered.asm.mixin.injection.Inject;
10+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
11+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedChunkTickScheduler;
12+
13+
import java.util.concurrent.atomic.AtomicReference;
14+
15+
@Mixin(SimpleTickScheduler.class)
16+
public abstract class MixinSimpleTickScheduler implements ICachedChunkTickScheduler {
17+
18+
@Shadow
19+
public abstract ListTag toNbt();
20+
21+
private AtomicReference<ListTag> preparedNbt = new AtomicReference<>();
22+
23+
@Inject(method = "<init>", at = @At("RETURN"))
24+
private void onInit(CallbackInfo info) {
25+
preparedNbt = new AtomicReference<>();
26+
}
27+
28+
@Override
29+
public void prepareCachedNbt() {
30+
if (preparedNbt == null) preparedNbt = new AtomicReference<>();
31+
preparedNbt.set(toNbt());
32+
}
33+
34+
@Override
35+
public ListTag getCachedNbt() {
36+
if (preparedNbt == null) preparedNbt = new AtomicReference<>();
37+
if (preparedNbt.get() == null) {
38+
new IllegalStateException("Tried to serialize ticklist with no cached nbt! This will affect data integrity. Incompatible mods?").printStackTrace();
39+
prepareCachedNbt();
40+
}
41+
final ListTag preparedNbt = this.preparedNbt.get();
42+
this.preparedNbt = null;
43+
return preparedNbt;
44+
}
45+
}

src/main/java/org/yatopiamc/c2me/mixin/threading/chunkio/MixinThreadedAnvilChunkStorage.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import com.ibm.asyncutil.locks.AsyncNamedLock;
44
import com.mojang.datafixers.DataFixer;
55
import com.mojang.datafixers.util.Either;
6+
import net.minecraft.block.Block;
7+
import net.minecraft.fluid.Fluid;
68
import net.minecraft.nbt.CompoundTag;
79
import net.minecraft.server.world.ChunkHolder;
10+
import net.minecraft.server.world.ServerTickScheduler;
811
import net.minecraft.server.world.ServerWorld;
912
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
1013
import net.minecraft.structure.StructureManager;
@@ -13,6 +16,7 @@
1316
import net.minecraft.util.thread.ThreadExecutor;
1417
import net.minecraft.world.ChunkSerializer;
1518
import net.minecraft.world.PersistentStateManager;
19+
import net.minecraft.world.TickScheduler;
1620
import net.minecraft.world.chunk.Chunk;
1721
import net.minecraft.world.chunk.ChunkStatus;
1822
import net.minecraft.world.chunk.ProtoChunk;
@@ -31,6 +35,8 @@
3135
import org.yatopiamc.c2me.common.threading.chunkio.C2MECachedRegionStorage;
3236
import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoMainThreadTaskUtils;
3337
import org.yatopiamc.c2me.common.threading.chunkio.ChunkIoThreadingExecutorUtils;
38+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedChunkTickScheduler;
39+
import org.yatopiamc.c2me.common.threading.chunkio.ICachedServerTickScheduler;
3440
import org.yatopiamc.c2me.common.threading.chunkio.ISerializingRegionBasedStorage;
3541
import org.yatopiamc.c2me.common.util.SneakyThrow;
3642

@@ -206,6 +212,26 @@ private boolean save(Chunk chunk) {
206212
// C2ME start - async serialization
207213
if (saveFutures == null) saveFutures = new ConcurrentLinkedQueue<>();
208214

215+
final TickScheduler<Block> chunkBlockTickScheduler = chunk.getBlockTickScheduler();
216+
final ServerTickScheduler<Block> worldBlockTickScheduler = this.world.getBlockTickScheduler();
217+
if (chunkBlockTickScheduler instanceof ICachedChunkTickScheduler) {
218+
((ICachedChunkTickScheduler) chunkBlockTickScheduler).prepareCachedNbt();
219+
} else if (worldBlockTickScheduler instanceof ICachedServerTickScheduler) {
220+
((ICachedServerTickScheduler) worldBlockTickScheduler).prepareCachedNbt(chunkPos);
221+
} else {
222+
new IllegalStateException("Unable to cache block ticklist. Incompatible mods?").printStackTrace();
223+
}
224+
225+
final TickScheduler<Fluid> chunkFluidTickScheduler = chunk.getFluidTickScheduler();
226+
final ServerTickScheduler<Fluid> worldFluidTickScheduler = this.world.getFluidTickScheduler();
227+
if (chunkFluidTickScheduler instanceof ICachedChunkTickScheduler) {
228+
((ICachedChunkTickScheduler) chunkFluidTickScheduler).prepareCachedNbt();
229+
} else if (worldFluidTickScheduler instanceof ICachedServerTickScheduler) {
230+
((ICachedServerTickScheduler) worldFluidTickScheduler).prepareCachedNbt(chunkPos);
231+
} else {
232+
new IllegalStateException("Unable to cache fluid ticklist. Incompatible mods?").printStackTrace();
233+
}
234+
209235
saveFutures.add(chunkLock.acquireLock(chunk.getPos()).toCompletableFuture().thenCompose(lockToken ->
210236
CompletableFuture.supplyAsync(() -> ChunkSerializer.serialize(this.world, chunk), ChunkIoThreadingExecutorUtils.serializerExecutor).thenAcceptAsync(compoundTag -> {
211237
this.setTagAt(chunkPos, compoundTag);

src/main/resources/c2me.mixins.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
"compatibilityLevel": "JAVA_11",
66
"mixins": [
77
"threading.chunkio.MixinChunkSerializer",
8+
"threading.chunkio.MixinChunkTickScheduler",
89
"threading.chunkio.MixinSerializingRegionBasedStorage",
10+
"threading.chunkio.MixinServerTickScheduler",
11+
"threading.chunkio.MixinSimpleTickScheduler",
912
"threading.chunkio.MixinStorageIoWorker",
1013
"threading.chunkio.MixinThreadedAnvilChunkStorage",
1114
"threading.chunkio.MixinVersionedChunkStorage",

0 commit comments

Comments
 (0)