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:
@@ -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) {
|
||||
|
||||
@@ -111,6 +111,6 @@ public abstract class EntityTickListMixin {
|
||||
}
|
||||
} finally {
|
||||
iterator.finishedIterating();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,6 @@ public interface ChunkSystemLevel {
|
||||
|
||||
public ChunkAccess moonrise$getSpecificChunkIfLoaded(final int chunkX, final int chunkZ, final ChunkStatus leastStatus);
|
||||
|
||||
public void moonrise$midTickTasks();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ public interface ChunkSystemMinecraftServer {
|
||||
|
||||
public void moonrise$setChunkSystemCrash(final Throwable throwable);
|
||||
|
||||
public void moonrise$executeMidTickTasks();
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user