From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 6 Apr 2020 04:20:44 -0700
Subject: [PATCH] Execute chunk tasks mid-tick

This will help the server load chunks if tick times are high.

diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
index a263cd7a0680e0cc3517f84308118eb32c487732..77df6888803093ad9527d276033f2ed767b39764 100644
--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
@@ -310,6 +310,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
                     if (toTick.tickState == STATE_TICKING) {
                         toTick.tickState = STATE_TICKED;
                     } // else it's STATE_CANCELLED_TICK
+                    MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick
                 } else {
                     // re-schedule eventually
                     toTick.tickState = STATE_SCHEDULED;
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 4082c4f366bce1784759bda773d3d35472403ade..7bb23731b548d5344013429a7d7df13686cb2249 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -893,7 +893,7 @@ public class ChunkProviderServer extends IChunkProvider {
         this.world.getMethodProfiler().enter("purge");
         this.world.timings.doChunkMap.startTiming(); // Spigot
         this.chunkMapDistance.purgeTickets();
-        this.world.getMinecraftServer().midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         this.tickDistanceManager();
         this.world.timings.doChunkMap.stopTiming(); // Spigot
         this.world.getMethodProfiler().exitEnter("chunks");
@@ -903,7 +903,7 @@ public class ChunkProviderServer extends IChunkProvider {
         this.world.timings.doChunkUnload.startTiming(); // Spigot
         this.world.getMethodProfiler().exitEnter("unload");
         this.playerChunkMap.unloadChunks(booleansupplier);
-        this.world.getMinecraftServer().midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         this.world.timings.doChunkUnload.stopTiming(); // Spigot
         this.world.getMethodProfiler().exit();
         this.clearCache();
@@ -975,6 +975,8 @@ public class ChunkProviderServer extends IChunkProvider {
             // Paper end
             this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings
 
+            int ticked = 0; // Tuinity - exec chunk tasks during world tick
+
             this.p = spawnercreature_d;
             this.world.getMethodProfiler().exit();
             //List<PlayerChunk> list = Lists.newArrayList(this.playerChunkMap.f()); // Paper
@@ -1004,7 +1006,7 @@ public class ChunkProviderServer extends IChunkProvider {
                             this.world.timings.chunkTicks.startTiming(); // Spigot // Paper
                             this.world.a(chunk, k);
                             this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper
-                            if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper
+                            if ((++ticked & 1) == 0) MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick
                         }
                     }
                 }
@@ -1160,41 +1162,7 @@ public class ChunkProviderServer extends IChunkProvider {
             ChunkProviderServer.this.world.getMethodProfiler().c("runTask");
             super.executeTask(runnable);
         }
-
-        // Paper start
-        private long lastMidTickChunkTask = 0;
-        public boolean pollChunkLoadTasks() {
-            if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask()) {
-                try {
-                    ChunkProviderServer.this.tickDistanceManager();
-                } finally {
-                    // from below: process pending Chunk loadCallback() and unloadCallback() after each run task
-                    playerChunkMap.callbackExecutor.run();
-                }
-                return true;
-            }
-            return false;
-        }
-        public void midTickLoadChunks() {
-            MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer();
-            // always try to load chunks, restrain generation/other updates only. don't count these towards tick count
-            //noinspection StatementWithEmptyBody
-            while (pollChunkLoadTasks()) {}
-
-            if (System.nanoTime() - lastMidTickChunkTask < 200000) {
-                return;
-            }
-
-            for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.canSleepForTick();) {
-                if (this.executeNext()) {
-                    server.midTickChunksTasksRan++;
-                    lastMidTickChunkTask = System.nanoTime();
-                } else {
-                    break;
-                }
-            }
-        }
-        // Paper end
+        // Tuinity - replace logic
 
         @Override
         protected boolean executeNext() {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index dc8e4a86ddae9ea4dfd62e591523fdc8a6599151..b96ffaab1253a8f1bd36f542865feeaebeadbff5 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -993,7 +993,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
                         // Paper end
                         tickSection = curTime;
                     }
-                    midTickChunksTasksRan = 0; // Paper
+                    // Tuinity - replace logic
                     // Spigot end
 
                     //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
@@ -1086,6 +1086,76 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
     }
     // Paper end
 
+    // Tuinity start - execute chunk tasks mid tick
+    static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
+    static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
+
+    static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
+
+    private static long lastMidTickExecute;
+    private static long lastMidTickExecuteFailure;
+
+    private boolean tickMidTickTasks() {
+        // give all worlds a fair chance at by targetting 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 (WorldServer world : this.getWorlds()) {
+            long currTime = System.nanoTime();
+            if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
+                continue;
+            }
+            if (!world.getChunkProvider().runTasks()) {
+                // we need to back off if this fails
+                world.lastMidTickExecuteFailure = currTime;
+            } else {
+                executed = true;
+            }
+        }
+
+        return executed;
+    }
+
+    public final void executeMidTickTasks() {
+        org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
+        long startTime = System.nanoTime();
+        if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - 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;
+        }
+
+        co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
+        try {
+            for (;;) {
+                boolean moreTasks = this.tickMidTickTasks();
+                long currTime = System.nanoTime();
+                long diff = currTime - startTime;
+
+                if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
+                    if (!moreTasks) {
+                        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
+                    }
+
+                    double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
+                    long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
+
+                    lastMidTickExecute = currTime + extraSleep;
+                    return;
+                }
+            }
+        } finally {
+            co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
+        }
+    }
+    // Tuinity end - execute chunk tasks mid tick
+
     private void executeModerately() {
         this.executeAll();
         java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
@@ -1099,22 +1169,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
         });
     }
 
-    // Paper start
-    public int midTickChunksTasksRan = 0;
-    private long midTickLastRan = 0;
-    public void midTickLoadChunks() {
-        if (!isMainThread() || System.nanoTime() - midTickLastRan < 1000000) {
-            // only check once per 0.25ms incase this code is called in a hot method
-            return;
-        }
-        try (co.aikar.timings.Timing ignored = co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming()) {
-            for (WorldServer value : this.getWorlds()) {
-                value.getChunkProvider().serverThreadQueue.midTickLoadChunks();
-            }
-            midTickLastRan = System.nanoTime();
-        }
-    }
-    // Paper end
+    // Tuinity - replace logic
 
     @Override
     protected TickTask postToMainThread(Runnable runnable) {
@@ -1141,6 +1196,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
 
     private boolean bb() {
         if (super.executeNext()) {
+            this.executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
             return true;
         } else {
             if (this.canSleepForTick()) {
@@ -1208,7 +1264,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
         // Paper start - move oversleep into full server tick
         isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
         this.awaitTasks(() -> {
-            midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick
+            // Tuinity - replace logic
             return !this.canOversleep();
         });
         isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
@@ -1299,16 +1355,16 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
     }
 
     protected void b(BooleanSupplier booleansupplier) {
-        midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper
         this.server.getScheduler().mainThreadHeartbeat(this.ticks); // CraftBukkit
         MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
-        midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         this.methodProfiler.enter("commandFunctions");
         MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper
         this.getFunctionData().tick();
         MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper
-        midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         this.methodProfiler.exitEnter("levels");
         Iterator iterator = this.getWorlds().iterator();
 
@@ -1319,7 +1375,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
             processQueue.remove().run();
         }
         MinecraftTimings.processQueueTimer.stopTiming(); // Spigot
-        midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
         // Send time updates to everyone, it will get the right time from the world the player is in.
         // Paper start - optimize time updates
@@ -1362,12 +1418,12 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
             this.methodProfiler.enter("tick");
 
             try {
-                midTickLoadChunks(); // Paper
+                // Tuinity - replace logic
                 worldserver.timings.doTick.startTiming(); // Spigot
                 worldserver.doTick(booleansupplier);
                 worldserver.getChunkProvider().playerChunkMap.dataRegionManager.recalculateRegions(); // Tuinity
                 worldserver.timings.doTick.stopTiming(); // Spigot
-                midTickLoadChunks(); // Paper
+                // Tuinity - replace logic
             } catch (Throwable throwable) {
                 // Spigot Start
                 CrashReport crashreport;
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 43b5fa67a6b6e778a059063390fb47d6a599a948..165b8e20ab090284b7c70b5cc9c0d58242400b54 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -900,6 +900,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
             return;
             // Paper end
         }
+        MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick
     }
     // Paper start - Prevent armor stands from doing entity lookups
     @Override
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 97a9a201a055520a7ab92b3ecc8987eb560bf263..f2e630598320f7f09ecdee5a9a548b20cc1b7877 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -297,6 +297,10 @@ public class WorldServer extends World implements GeneratorAccessSeed {
     }
     // Tuinity end
 
+    // Tuinity start - execute chunk tasks mid tick
+    long lastMidTickExecuteFailure;
+    // Tuinity end - execute chunk tasks mid tick
+
     // Add env and gen to constructor, WorldData -> WorldDataServer
     public WorldServer(MinecraftServer minecraftserver, Executor executor, Convertable.ConversionSession convertable_conversionsession, IWorldDataServer iworlddataserver, ResourceKey<World> resourcekey, DimensionManager dimensionmanager, WorldLoadListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<MobSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
         super(iworlddataserver, resourcekey, dimensionmanager, minecraftserver::getMethodProfiler, false, flag, i, gen, env, executor); // Paper pass executor
@@ -559,7 +563,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         }
         timings.scheduledBlocks.stopTiming(); // Paper
 
-        this.getMinecraftServer().midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         gameprofilerfiller.exitEnter("raid");
         this.timings.raids.startTiming(); // Paper - timings
         this.persistentRaid.a();
@@ -568,7 +572,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         timings.doSounds.startTiming(); // Spigot
         this.ak();
         timings.doSounds.stopTiming(); // Spigot
-        this.getMinecraftServer().midTickLoadChunks(); // Paper
+        // Tuinity - replace logic
         this.ticking = false;
         gameprofilerfiller.exitEnter("entities");
         boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
@@ -645,7 +649,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
             }
             this.afterEntityTickingTasks.clear();
             // Paper end
-            this.getMinecraftServer().midTickLoadChunks(); // Paper
+            // Tuinity - replace logic
 
             Entity entity2;
 
@@ -655,7 +659,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
             }
 
             timings.tickEntities.stopTiming(); // Spigot
-            this.getMinecraftServer().midTickLoadChunks(); // Paper
+            // Tuinity - replace logic
             this.tickBlockEntities();
         }
 
