From bd3f32c944c6ebbcaf32244c872bd7ba5b43577a Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Fri, 28 Jun 2024 19:04:11 -0700 Subject: [PATCH] Execute chunk tasks mid-tick. If the server tick length is high, then the amount of time available to process chunk tasks inbetween ticks is low. As a result, chunk loading and generation may appear to slow down. To ensure that chunk tasks are always processed, we add logic to execute chunk tasks during tile entity tick, entity tick, chunk random ticking, and scheduled block/fluid ticking. The mid-tick task execution is timed so that it is not prioritised over the server tick. --- .../mixin/block_entity_remove/LevelMixin.java | 7 ++ .../chunk_system/EntityTickListMixin.java | 2 +- .../mixin/chunk_system/LevelMixin.java | 22 ++++++ .../chunk_system/MinecraftServerMixin.java | 73 ++++++++++++++++++- .../chunk_system/ServerChunkCacheMixin.java | 24 ++++++ .../mixin/chunk_system/ServerLevelMixin.java | 61 ++++++++++++++++ .../chunk_system/level/ChunkSystemLevel.java | 2 + .../level/ChunkSystemServerLevel.java | 3 + .../server/ChunkSystemMinecraftServer.java | 2 + 9 files changed, 194 insertions(+), 2 deletions(-) diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/block_entity_remove/LevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/block_entity_remove/LevelMixin.java index 40eb75b7..8f49669b 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/block_entity_remove/LevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/block_entity_remove/LevelMixin.java @@ -1,5 +1,6 @@ package ca.spottedleaf.moonrise.mixin.block_entity_remove; +import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.core.BlockPos; import net.minecraft.world.TickRateManager; @@ -73,6 +74,8 @@ private boolean newBlockEntityTick(final Iterator ignored) { int i = 0; // current ticking entity (exclusive) int len = tickList.size(); + int tickedEntities = 0; + Objects.checkFromToIndex(0, len, elements.length); try { for (; i < len; ++i) { @@ -84,6 +87,10 @@ private boolean newBlockEntityTick(final Iterator ignored) { if (doTick && this.shouldTickBlocksAt(tileEntity.getPos())) { tileEntity.tick(); + // call mid tick tasks for chunk system + if ((++tickedEntities & 7) == 0) { + ((ChunkSystemLevel)(Level)(Object)this).moonrise$midTickTasks(); + } } if (writeToBase) { diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/EntityTickListMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/EntityTickListMixin.java index 3094ae76..e2dcacf5 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/EntityTickListMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/EntityTickListMixin.java @@ -111,6 +111,6 @@ public void forEach(final Consumer action) { } } finally { iterator.finishedIterating(); - } + } } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java index b4a01679..64d2ca4d 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/LevelMixin.java @@ -189,6 +189,11 @@ public final List getEntitiesOfClass(final Class entity return this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, false); } + @Override + public void moonrise$midTickTasks() { + // no-op on ClientLevel + } + /** * @reason Allow block updates in non-ticking chunks, as new chunk system sends non-ticking chunks to clients * @author Spottedleaf @@ -207,4 +212,21 @@ private boolean sendUpdatesForFullChunks(final FullChunkStatus instance, } // TODO: Thread.currentThread() != this.thread to TickThread? + + + /** + * @reason Execute mid-tick chunk tasks during entity ticking + * @author Spottedleaf + */ + @Inject( + method = "guardEntityTick", + at = @At( + value = "INVOKE", + shift = At.Shift.AFTER, + target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V" + ) + ) + private void midTickEntity(final CallbackInfo ci) { + this.moonrise$midTickTasks(); + } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/MinecraftServerMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/MinecraftServerMixin.java index aae05374..cea99780 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/MinecraftServerMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/MinecraftServerMixin.java @@ -55,13 +55,84 @@ public MinecraftServerMixin(String string) { this.chunkSystemCrash = throwable; } + @Unique + private static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us + @Unique + private static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us + @Unique + private static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us + + @Unique + private long lastMidTickExecute; + @Unique + private long lastMidTickExecuteFailure; + + @Unique + private boolean tickMidTickTasks() { + // give all worlds a fair chance at by targeting them all. + // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time. + boolean executed = false; + for (final ServerLevel world : this.getAllLevels()) { + long currTime = System.nanoTime(); + if (currTime - ((ChunkSystemServerLevel)world).moonrise$getLastMidTickFailure() <= TASK_EXECUTION_FAILURE_BACKOFF) { + continue; + } + if (!world.getChunkSource().pollTask()) { + // we need to back off if this fails + ((ChunkSystemServerLevel)world).moonrise$setLastMidTickFailure(currTime); + } else { + executed = true; + } + } + + return executed; + } + + @Override + public final void moonrise$executeMidTickTasks() { + final long startTime = System.nanoTime(); + if ((startTime - this.lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - this.lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) { + // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed. + // so, backoff to prevent this + return; + } + + for (;;) { + final boolean moreTasks = this.tickMidTickTasks(); + final long currTime = System.nanoTime(); + final long diff = currTime - startTime; + + if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) { + if (!moreTasks) { + this.lastMidTickExecuteFailure = currTime; + } + + // note: negative values reduce the time + long overuse = diff - MAX_CHUNK_EXEC_TIME; + if (overuse >= (10L * 1000L * 1000L)) { // 10ms + // make sure something like a GC or dumb plugin doesn't screw us over... + overuse = 10L * 1000L * 1000L; // 10ms + } + + final double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; + final long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); + + this.lastMidTickExecute = currTime + extraSleep; + return; + } + } + } + /** - * @reason Force execution of tasks for all worlds, so that the first world does not hog all of the task processing + * @reason Force execution of tasks for all worlds, so that the first world does not hog the task processing time. + * Additionally, perform mid-tick task execution when handling the normal server queue so that chunk tasks + * are guaranteed to be processed during tick sleep. * @author Spottedleaf */ @Overwrite private boolean pollTaskInternal() { if (super.pollTask()) { + this.moonrise$executeMidTickTasks(); return true; } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java index f86c300c..c47e1b98 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerChunkCacheMixin.java @@ -8,6 +8,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; +import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer; import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; @@ -48,6 +49,9 @@ public abstract class ServerChunkCacheMixin extends ChunkSource implements Chunk @Unique private final ConcurrentLong2ReferenceChainedHashTable fullChunks = new ConcurrentLong2ReferenceChainedHashTable<>(); + @Unique + private long chunksTicked; + @Override public final void moonrise$setFullChunk(final int chunkX, final int chunkZ, final LevelChunk chunk) { final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); @@ -274,4 +278,24 @@ public void getFullChunk(final long pos, final Consumer consumer) { private boolean skipSaveTicketUpdates(final ServerChunkCache instance) { return false; } + + /** + * @reason Perform mid-tick chunk task processing during chunk tick + * @author Spottedleaf + */ + @Inject( + method = "tickChunks", + at = @At( + value = "INVOKE", + shift = At.Shift.AFTER, + target = "Lnet/minecraft/server/level/ServerLevel;tickChunk(Lnet/minecraft/world/level/chunk/LevelChunk;I)V" + ) + ) + private void midTickChunks(final CallbackInfo ci) { + if ((++this.chunksTicked & 7L) != 0L) { + return; + } + + ((ChunkSystemMinecraftServer)this.level.getServer()).moonrise$executeMidTickTasks(); + } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java index c84f0574..8f2a1f7a 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/chunk_system/ServerLevelMixin.java @@ -15,6 +15,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder; import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ThreadedTicketLevelPropagator; +import ca.spottedleaf.moonrise.patches.chunk_system.server.ChunkSystemMinecraftServer; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; @@ -43,6 +44,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.ServerLevelData; import net.minecraft.world.level.storage.WritableLevelData; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; @@ -67,6 +69,10 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe @Shadow private PersistentEntitySectionManager entityManager; + @Shadow + @Final + private MinecraftServer server; + protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, RegistryAccess registryAccess, Holder holder, Supplier supplier, boolean bl, boolean bl2, long l, int i) { super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i); } @@ -92,6 +98,12 @@ protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey instance) { return this.moonrise$getEntityLookup().getEntityCount(); } + + /** + * @reason Execute mid-tick chunk tasks during fluid ticking + * @author Spottedleaf + */ + @Inject( + method = "tickFluid", + at = @At( + value = "RETURN" + ) + ) + private void midTickFluids(final CallbackInfo ci) { + if ((++this.tickedBlocksOrFluids & 7L) != 0L) { + return; + } + ((ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } + + /** + * @reason Execute mid-tick chunk tasks during block ticking + * @author Spottedleaf + */ + @Inject( + method = "tickBlock", + at = @At( + value = "RETURN" + ) + ) + private void midTickBlock(final CallbackInfo ci) { + if ((++this.tickedBlocksOrFluids & 7L) != 0L) { + return; + } + ((ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks(); + } } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java index eab09949..efcd9057 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemLevel.java @@ -17,4 +17,6 @@ public interface ChunkSystemLevel { public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus); + public void moonrise$midTickTasks(); + } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java index a31c392e..becb0023 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/ChunkSystemServerLevel.java @@ -46,4 +46,7 @@ public interface ChunkSystemServerLevel extends ChunkSystemLevel { public RegionizedPlayerChunkLoader.ViewDistanceHolder moonrise$getViewDistanceHolder(); + public long moonrise$getLastMidTickFailure(); + + public void moonrise$setLastMidTickFailure(final long time); } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java index 21c95627..cb6af371 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/server/ChunkSystemMinecraftServer.java @@ -4,4 +4,6 @@ public interface ChunkSystemMinecraftServer { public void moonrise$setChunkSystemCrash(final Throwable throwable); + public void moonrise$executeMidTickTasks(); + }