From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 17 Aug 2019 18:06:04 -0700
Subject: [PATCH] Add soft async catcher

Must be enabled via -Dtuinity.strict-thread-checks=true

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 8918bad880d6eeed30db39b6326b2f65e24edf45..4666d6582535d6e49c5bd40d4fcdcdfe07590aa9 100644
--- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
@@ -186,6 +186,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
     }
 
     public void onChunkSetTicking(final int chunkX, final int chunkZ) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list chunk ticking update"); // Tuinity - soft async catcher
         final ArrayList<NextTickListEntry<T>> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ));
         if (pending == null) {
             return;
@@ -268,6 +269,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public void nextTick() {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher
         ++this.currentTick;
         if (this.currentTick != this.world.getTime()) {
             if (!this.warnedAboutDesync) {
@@ -280,6 +282,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public void tick() {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list tick"); // Tuinity - soft async catcher
         final ChunkProviderServer chunkProvider = this.world.getChunkProvider();
 
         this.world.getMethodProfiler().enter("cleaning");
@@ -424,6 +427,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
     }
 
     public void schedule(final BlockPosition pos, final T data, final long targetTick, final TickListPriority priority) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list schedule"); // Tuinity - soft async catcher
         final NextTickListEntry<T> entry = new NextTickListEntry<>(pos, data, targetTick, priority);
         if (this.excludeFromScheduling.test(entry.getData())) {
             return;
@@ -479,6 +483,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public List<NextTickListEntry<T>> getEntriesInBoundingBox(final StructureBoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get in bounding box"); // Tuinity - soft async catcher
         if (structureboundingbox.getMinX() == structureboundingbox.getMaxX() || structureboundingbox.getMinZ() == structureboundingbox.getMaxZ()) {
             return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above
         }
@@ -535,6 +540,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public void copy(StructureBoundingBox structureboundingbox, BlockPosition blockposition) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list copy"); // Tuinity - soft async catcher
         // start copy from TickListServer // TODO check on update
         List<NextTickListEntry<T>> list = this.getEntriesInBoundingBox(structureboundingbox, false, false);
         Iterator<NextTickListEntry<T>> iterator = list.iterator();
@@ -554,6 +560,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public List<NextTickListEntry<T>> getEntriesInChunk(ChunkCoordIntPair chunkPos, boolean removeReturned, boolean excludeTicked) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get"); // Tuinity - soft async catcher
         // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks
         // not at ticking status, and ticking status requires neighbours loaded
         // so with this method we will reduce scheduler churning
@@ -585,6 +592,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public NBTTagList serialize(ChunkCoordIntPair chunkcoordintpair) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list serialize"); // Tuinity - soft async catcher
         // start copy from TickListServer  // TODO check on update
         List<NextTickListEntry<T>> list = this.getEntriesInChunk(chunkcoordintpair, false, true);
 
@@ -594,6 +602,7 @@ public final class PaperTickList<T> extends TickListServer<T> { // extend to avo
 
     @Override
     public int getTotalScheduledEntries() {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("async tick list get size"); // Tuinity - soft async catcher
         // good thing this is only used in debug reports // TODO check on update
         int ret = 0;
 
diff --git a/src/main/java/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java
index 033548a58d27f64d3954206d267783c0437d4019..08ed243259f052165c6f75aed1d1d65a14219715 100644
--- a/src/main/java/com/tuinity/tuinity/util/TickThread.java
+++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java
@@ -1,7 +1,33 @@
 package com.tuinity.tuinity.util;
 
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
+
 public final class TickThread extends Thread {
 
+    public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("tuinity.strict-thread-checks");
+
+    static {
+        if (STRICT_THREAD_CHECKS) {
+            MinecraftServer.LOGGER.warn("Strict thread checks enabled - performance may suffer");
+        }
+    }
+
+    public static void softEnsureTickThread(final String reason) {
+        if (!STRICT_THREAD_CHECKS) {
+            return;
+        }
+        ensureTickThread(reason);
+    }
+
+
+    public static void ensureTickThread(final String reason) {
+        if (!Bukkit.isPrimaryThread()) {
+            MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
+            throw new IllegalStateException(reason);
+        }
+    }
+
     public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */
 
     public TickThread(final Runnable run, final String name, final int id) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
index 3644e8b24b082e17752ef52934625416130aaa08..4dfc1101d5fc78630ef8b816e8f84f5541683f0b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
@@ -74,6 +74,7 @@ public abstract class ChunkMapDistance {
     }
 
     protected void purgeTickets() {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async purge tickets"); // Tuinity
         ++this.currentTick;
         ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
 
@@ -107,6 +108,7 @@ public abstract class ChunkMapDistance {
     protected abstract PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k);
 
     public boolean a(PlayerChunkMap playerchunkmap) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot tick ChunkMapDistance off of the main-thread");// Tuinity
         //this.f.a(); // Paper - no longer used
         AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
         this.g.a();
@@ -379,6 +381,7 @@ public abstract class ChunkMapDistance {
     }
 
     private ArraySetSorted<Ticket<?>> e(long i) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async tickets compute"); // Tuinity
         return (ArraySetSorted) this.tickets.computeIfAbsent(i, (j) -> {
             return ArraySetSorted.a(4);
         });
@@ -396,6 +399,7 @@ public abstract class ChunkMapDistance {
     }
 
     public void a(SectionPosition sectionposition, EntityPlayer entityplayer) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player add"); // Tuinity
         long i = sectionposition.r().pair();
 
         ((ObjectSet) this.c.computeIfAbsent(i, (j) -> {
@@ -406,6 +410,7 @@ public abstract class ChunkMapDistance {
     }
 
     public void b(SectionPosition sectionposition, EntityPlayer entityplayer) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async player remove"); // Tuinity
         long i = sectionposition.r().pair();
         ObjectSet<EntityPlayer> objectset = (ObjectSet) this.c.get(i);
         if (objectset == null) return; // CraftBukkit - SPIGOT-6208
@@ -456,6 +461,7 @@ public abstract class ChunkMapDistance {
 
     // CraftBukkit start
     public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async ticket remove"); // Tuinity
         Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
 
         for (java.util.Iterator<Entry<ArraySetSorted<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
index e9b236bb95985ba0806a3d27d705ac61bce55ea5..c5038aa66703484a9243579ba4a6c92d138fc388 100644
--- a/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/level/ChunkProviderServer.java
@@ -1223,6 +1223,7 @@ public class ChunkProviderServer extends IChunkProvider {
 
         @Override
         protected boolean executeNext() {
+            com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot execute chunk tasks off-main thread");// Tuinity
         // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
         try {
             boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper
diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
index 1df8fb8cb3fcf8201e1c5fa8ca13f7a9c632c379..55637a74c6badf8b06512c36ae273404cc05d1be 100644
--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
+++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
@@ -103,6 +103,7 @@ public class EntityTrackerEntry {
 
     public final void tick() { this.a(); } // Paper - OBFHELPER
     public void a() {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity
         List<Entity> list = this.tracker.passengers; // Paper - do not copy list
 
         if (!list.equals(this.p)) {
diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
index f3d6811156e68040106f1d027a10ea33b5646b05..e7cd6392d722d0e13b86b57d70946b18688f6621 100644
--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java
@@ -255,6 +255,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     // Paper end - no-tick view distance
 
     void addPlayerToDistanceMaps(EntityPlayer player) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity
         int chunkX = MCUtil.getChunkCoordinate(player.locX());
         int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
         // Note: players need to be explicitly added to distance maps before they can be updated
@@ -285,6 +286,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
 
     void removePlayerFromDistanceMaps(EntityPlayer player) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity
         // Paper start - use distance map to optimise tracker
         for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
             this.playerEntityTrackerTrackMaps[i].remove(player);
@@ -302,6 +304,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
 
     void updateMaps(EntityPlayer player) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update distance maps off of the main thread"); // Tuinity
         int chunkX = MCUtil.getChunkCoordinate(player.locX());
         int chunkZ = MCUtil.getChunkCoordinate(player.locZ());
         // Note: players need to be explicitly added to distance maps before they can be updated
@@ -845,6 +848,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
     @Nullable
     private PlayerChunk a(long i, int j, @Nullable PlayerChunk playerchunk, int k) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Chunk holder update"); // Tuinity
         if (k > PlayerChunkMap.GOLDEN_TICKET && j > PlayerChunkMap.GOLDEN_TICKET) {
             return playerchunk;
         } else {
@@ -1166,6 +1170,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
 
     protected boolean b() {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update visibleChunks off of the main thread"); // Tuinity
         if (!this.updatingChunksModified) {
             return false;
         } else {
@@ -1605,6 +1610,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
 
     public void setViewDistance(int i) { // Paper - public
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity
         int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32
 
         if (j != this.viewDistance) {
@@ -1618,6 +1624,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
     // Paper start - no-tick view distance
     public final void setNoTickViewDistance(int viewDistance) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Cannot update view distance off of the main thread"); // Tuinity
         viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32);
 
         this.noTickViewDistance = viewDistance;
diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java
index f88b80e4772ade4199564cf96ef94ce45e493311..c485837c751fb8bf7c30dbca955321f586940a8b 100644
--- a/src/main/java/net/minecraft/server/level/WorldServer.java
+++ b/src/main/java/net/minecraft/server/level/WorldServer.java
@@ -1775,6 +1775,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
 
     @Override
     public void notify(BlockPosition blockposition, IBlockData iblockdata, IBlockData iblockdata1, int i) {
+        org.spigotmc.AsyncCatcher.catchOp("notify call"); // Tuinity
         this.getChunkProvider().flagDirty(blockposition);
         if(this.paperConfig.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates
         VoxelShape voxelshape = iblockdata.getCollisionShape(this, blockposition);
diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java
index 32b6de119c1ee27be0be42c0a0cdb4dd741a4c36..74894282575d12f25ba4968abf873d3a346529b7 100644
--- a/src/main/java/net/minecraft/world/level/World.java
+++ b/src/main/java/net/minecraft/world/level/World.java
@@ -423,6 +423,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
 
     @Override
     public boolean a(BlockPosition blockposition, IBlockData iblockdata, int i, int j) {
+        org.spigotmc.AsyncCatcher.catchOp("set type call"); // Tuinity
         // CraftBukkit start - tree generation
         if (this.captureTreeGeneration) {
             // Paper start
@@ -524,6 +525,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
 
     // CraftBukkit start - Split off from above in order to directly send client and physic updates
     public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i, int j) {
+        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Async notify and update"); // Tuinity
         IBlockData iblockdata = newBlock;
         IBlockData iblockdata1 = oldBlock;
         IBlockData iblockdata2 = actualBlock;
diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java
index 10606ed03e88e4e467de2d5424df12466c4e7873..51e9c54cddf4b28ba3d3d892322c487774bdab70 100644
--- a/src/main/java/org/spigotmc/AsyncCatcher.java
+++ b/src/main/java/org/spigotmc/AsyncCatcher.java
@@ -10,8 +10,9 @@ public class AsyncCatcher
 
     public static void catchOp(String reason)
     {
-        if ( enabled && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity
+        if ( ( enabled || com.tuinity.tuinity.util.TickThread.STRICT_THREAD_CHECKS ) && !org.bukkit.Bukkit.isPrimaryThread() ) // Tuinity
         {
+            MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed thread check for reason: Asynchronous " + reason, new Throwable()); // Tuinity - not all exceptions are printed
             throw new IllegalStateException( "Asynchronous " + reason + "!" );
         }
     }
