Skip to content

Commit

Permalink
Execute chunk tasks mid-tick.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Spottedleaf committed Jun 29, 2024
1 parent 2c97e8d commit bd3f32c
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -73,6 +74,8 @@ private boolean newBlockEntityTick(final Iterator<TickingBlockEntity> 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) {
Expand All @@ -84,6 +87,10 @@ private boolean newBlockEntityTick(final Iterator<TickingBlockEntity> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,6 @@ public void forEach(final Consumer<Entity> action) {
}
} finally {
iterator.finishedIterating();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> 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
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +49,9 @@ public abstract class ServerChunkCacheMixin extends ChunkSource implements Chunk
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<LevelChunk> 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);
Expand Down Expand Up @@ -274,4 +278,24 @@ public void getFullChunk(final long pos, final Consumer<LevelChunk> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -67,6 +69,10 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe
@Shadow
private PersistentEntitySectionManager<Entity> entityManager;

@Shadow
@Final
private MinecraftServer server;

protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
}
Expand All @@ -92,6 +98,12 @@ protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Leve
@Unique
private ChunkTaskScheduler chunkTaskScheduler;

@Unique
private long lastMidTickFailure;

@Unique
private long tickedBlocksOrFluids;

/**
* @reason Initialise fields / destroy entity manager state
* @author Spottedleaf
Expand Down Expand Up @@ -153,6 +165,11 @@ private void init(MinecraftServer minecraftServer, Executor executor,
return newChunkHolder.getChunkIfPresentUnchecked(leastStatus);
}

@Override
public final void moonrise$midTickTasks() {
((ChunkSystemMinecraftServer)this.server).moonrise$executeMidTickTasks();
}

@Override
public final ChunkAccess moonrise$syncLoadNonFull(final int chunkX, final int chunkZ, final ChunkStatus status) {
return this.moonrise$getChunkTaskScheduler().syncLoadNonFull(chunkX, chunkZ, status);
Expand Down Expand Up @@ -279,6 +296,16 @@ private void init(MinecraftServer minecraftServer, Executor executor,
return this.viewDistanceHolder;
}

@Override
public final long moonrise$getLastMidTickFailure() {
return this.lastMidTickFailure;
}

@Override
public final void moonrise$setLastMidTickFailure(final long time) {
this.lastMidTickFailure = time;
}

/**
* @reason Entities are guaranteed to be ticking in the new chunk system
* @author Spottedleaf
Expand Down Expand Up @@ -600,4 +627,38 @@ public boolean isNaturalSpawningAllowed(final ChunkPos pos) {
private int redirectCrashCount(final PersistentEntitySectionManager<Entity> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public interface ChunkSystemLevel {

public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus);

public void moonrise$midTickTasks();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ public interface ChunkSystemMinecraftServer {

public void moonrise$setChunkSystemCrash(final Throwable throwable);

public void moonrise$executeMidTickTasks();

}

0 comments on commit bd3f32c

Please sign in to comment.