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.
This commit is contained in:
Spottedleaf
2024-06-28 19:04:11 -07:00
parent 2c97e8dc6e
commit bd3f32c944
9 changed files with 194 additions and 2 deletions

View File

@@ -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 @@ public abstract class LevelMixin implements LevelAccessor, AutoCloseable {
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 @@ public abstract class LevelMixin implements LevelAccessor, AutoCloseable {
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) {

View File

@@ -111,6 +111,6 @@ public abstract class EntityTickListMixin {
}
} finally {
iterator.finishedIterating();
}
}
}
}

View File

@@ -189,6 +189,11 @@ public abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityG
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 @@ public abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityG
}
// 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();
}
}

View File

@@ -55,13 +55,84 @@ public abstract class MinecraftServerMixin extends ReentrantBlockableEventLoop<T
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;
}

View File

@@ -8,6 +8,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel
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<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);
@@ -274,4 +278,24 @@ public abstract class ServerChunkCacheMixin extends ChunkSource implements Chunk
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();
}
}

View File

@@ -15,6 +15,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManage
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.entity.PersistentEntitySectionManager;
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<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);
}
@@ -92,6 +98,12 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe
@Unique
private ChunkTaskScheduler chunkTaskScheduler;
@Unique
private long lastMidTickFailure;
@Unique
private long tickedBlocksOrFluids;
/**
* @reason Initialise fields / destroy entity manager state
* @author Spottedleaf
@@ -153,6 +165,11 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe
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);
@@ -279,6 +296,16 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe
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
@@ -600,4 +627,38 @@ public abstract class ServerLevelMixin extends Level implements ChunkSystemServe
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();
}
}

View File

@@ -17,4 +17,6 @@ public interface ChunkSystemLevel {
public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus);
public void moonrise$midTickTasks();
}

View File

@@ -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);
}

View File

@@ -4,4 +4,6 @@ public interface ChunkSystemMinecraftServer {
public void moonrise$setChunkSystemCrash(final Throwable throwable);
public void moonrise$executeMidTickTasks();
}