From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mykyta Komarn <nkomarn@hotmail.com>
Date: Sat, 3 Oct 2020 22:40:00 -0700
Subject: [PATCH] Async entity tracking

Entity tracking is done the same way, but asynchronously.
Lowers the MSPT and NSPT by a lot.

The changes made:
Synchronised the trackedEntities map in PlayerChunkMap, also added additional lock to it.
Created a tracker executor which is processing the track queue asynchronously.

A minor change was to replace 2 array lists with our GlueList implementation.

Certain stuff had to be ensured to be called on the main thread when the tracker entry was ticked due to how
the game's code works and due to how bukkit's event system works.

Entity tracking is some of the heavy stuff the server does. Making this async is trivial for making the performance
go up.

Co-authored-by: Ivan Pekov <ivan@mrivanplays.com>

diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index b43b02c0bdd5dbf0b7d30de90bdc2f74c015ecc8..d3d1120395ee0e5be781febefa502c40ad9dacdd 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -49,6 +49,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
     public final MinecraftServer server;
     public final PlayerInteractManager playerInteractManager;
     public final Deque<Integer> removeQueue = new ArrayDeque<>(); // Paper
+    public final java.util.concurrent.locks.Lock removeQueueLock = new java.util.concurrent.locks.ReentrantLock(); // Yatopia
     private final AdvancementDataPlayer advancementDataPlayer;
     private final ServerStatisticManager serverStatisticManager;
     private float lastHealthScored = Float.MIN_VALUE;
@@ -459,6 +460,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
             this.activeContainer = this.defaultContainer;
         }
 
+        // Yatopia start
+        removeQueueLock.lock();
+        try {
+            // Yatopia end
         while (!this.removeQueue.isEmpty()) {
             int i = Math.min(this.removeQueue.size(), Integer.MAX_VALUE);
             int[] aint = new int[i];
@@ -479,6 +484,9 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
 
             this.playerConnection.sendPacket(new PacketPlayOutEntityDestroy(aint));
         }
+        } finally { // Yatopia start
+            removeQueueLock.unlock();
+        } // Yatopia end
 
         Entity entity = this.getSpecatorTarget();
 
@@ -1545,9 +1553,16 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
         // Applies to the same player, so we need to not duplicate our removal queue. The rest of this method does "resetting"
         // type logic so it does need to be called, maybe? This is silly.
         // this.removeQueue.addAll(entityplayer.removeQueue);
+        // Yatopia start
+        removeQueueLock.lock();
+        try {
+            // Yatopia end
         if (this.removeQueue != entityplayer.removeQueue) {
             this.removeQueue.addAll(entityplayer.removeQueue);
         }
+        } finally { // Yatopia start
+            removeQueueLock.unlock();
+        } // Yatopia end
         // Paper end
         this.cd = entityplayer.cd;
         this.ci = entityplayer.ci;
@@ -1733,13 +1748,27 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
         if (entity instanceof EntityHuman) {
             this.playerConnection.sendPacket(new PacketPlayOutEntityDestroy(new int[]{entity.getId()}));
         } else {
+            // Yatopia start
+            removeQueueLock.lock();
+            try {
+                // Yatopia end
             this.removeQueue.add((Integer) entity.getId()); // CraftBukkit - decompile error
+            } finally { // Yatopia start
+                removeQueueLock.unlock();
+            } // Yatopia end
         }
 
     }
 
     public void d(Entity entity) {
+        // Yatopia start
+        removeQueueLock.lock();
+        try {
+            // Yatopia end
         this.removeQueue.remove((Integer) entity.getId()); // CraftBukkit - decompile error
+        } finally { // Yatopia start
+            removeQueueLock.unlock();
+        } // Yatopia end
     }
 
     @Override
@@ -1801,10 +1830,18 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
             this.getBukkitEntity().teleport(new Location(newSpectatorTarget.getWorld().getWorld(), newSpectatorTarget.locX(), newSpectatorTarget.locY(), newSpectatorTarget.locZ(), this.yaw, this.pitch), TeleportCause.SPECTATE); // Correctly handle cross-world entities from api calls by using CB teleport
 
             // Make sure we're tracking the entity before sending
-            PlayerChunkMap.EntityTracker tracker = ((WorldServer)newSpectatorTarget.world).getChunkProvider().playerChunkMap.trackedEntities.get(newSpectatorTarget.getId());
+            // Yatopia start
+            PlayerChunkMap chunkMap = ((WorldServer)newSpectatorTarget.world).getChunkProvider().playerChunkMap;
+            chunkMap.trackedEntitiesLock.lock();
+            try {
+            PlayerChunkMap.EntityTracker tracker = chunkMap.trackedEntities.get(newSpectatorTarget.getId());
+            // Yatopia end
             if (tracker != null) { // dumb plugins...
                 tracker.updatePlayer(this);
             }
+            } finally { // Yatopia start
+                chunkMap.trackedEntitiesLock.unlock();
+            } // Yatopia end
         } else {
             this.playerConnection.teleport(this.spectatedEntity.locX(), this.spectatedEntity.locY(), this.spectatedEntity.locZ(), this.yaw, this.pitch, TeleportCause.SPECTATE); // CraftBukkit
         }
diff --git a/src/main/java/net/minecraft/server/EntityTNTPrimed.java b/src/main/java/net/minecraft/server/EntityTNTPrimed.java
index 3e71332c47000b21ff90aec6937f90dc639a41bd..b12aa1b3bf1397eea42cffb1c67c03448a56409b 100644
--- a/src/main/java/net/minecraft/server/EntityTNTPrimed.java
+++ b/src/main/java/net/minecraft/server/EntityTNTPrimed.java
@@ -87,18 +87,33 @@ public class EntityTNTPrimed extends Entity {
              */
             // Send position and velocity updates to nearby players on every tick while the TNT is in water.
             // This does pretty well at keeping their clients in sync with the server.
-            PlayerChunkMap.EntityTracker ete = ((WorldServer)this.world).getChunkProvider().playerChunkMap.trackedEntities.get(this.getId());
+            // Yatopia start
+            PlayerChunkMap chunkMap = ((WorldServer)this.world).getChunkProvider().playerChunkMap;
+            chunkMap.trackedEntitiesLock.lock();
+            try {
+            PlayerChunkMap.EntityTracker ete = chunkMap.trackedEntities.get(this.getId());
+                // Yatopia end
             if (ete != null) {
                 PacketPlayOutEntityVelocity velocityPacket = new PacketPlayOutEntityVelocity(this);
                 PacketPlayOutEntityTeleport positionPacket = new PacketPlayOutEntityTeleport(this);
 
+                // Yatopia start
+                ete.trackedPlayersLock.lock();
+                try {
+                    // Yatopia end
                 ete.trackedPlayers.stream()
                     .filter(viewer -> (viewer.locX() - this.locX()) * (viewer.locY() - this.locY()) * (viewer.locZ() - this.locZ()) < 16 * 16)
                     .forEach(viewer -> {
                         viewer.playerConnection.sendPacket(velocityPacket);
                         viewer.playerConnection.sendPacket(positionPacket);
                     });
+                } finally { // Yatopia start
+                    ete.trackedPlayersLock.unlock();
+                } // Yatopia end
             }
+            } finally { // Yatopia start
+                chunkMap.trackedEntitiesLock.unlock();
+            } // Yatopia end
         }
         // Paper end
     }
diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
index aea72b0db10eed151db18490c02f291c3cded92a..8e35ada472e9ba2e896c83cbbbf9a093063e6a65 100644
--- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java
+++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
@@ -39,6 +39,7 @@ public class EntityTrackerEntry {
     private boolean r;
     // CraftBukkit start
     final Set<EntityPlayer> trackedPlayers; // Paper - private -> package
+    final java.util.concurrent.locks.Lock trackedPlayersLock = new java.util.concurrent.locks.ReentrantLock(); // Yatopia
     // Paper start
     private java.util.Map<EntityPlayer, Boolean> trackedPlayerMap = null;
 
@@ -74,7 +75,7 @@ public class EntityTrackerEntry {
 
     public final void tick() { this.a(); } // Paper - OBFHELPER
     public void a() {
-        com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity
+        // com.tuinity.tuinity.util.TickThread.softEnsureTickThread("Tracker update"); // Tuinity // Yatopia
         List<Entity> list = this.tracker.getPassengers();
 
         if (!list.equals(this.p)) {
@@ -89,18 +90,25 @@ public class EntityTrackerEntry {
 
             if (this.tickCounter % 10 == 0 && itemstack.getItem() instanceof ItemWorldMap) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
                 WorldMap worldmap = ItemWorldMap.getSavedMap(itemstack, this.b);
+                trackedPlayersLock.lock(); // Yatopia
+                try { // Yatopia
                 Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit
 
                 while (iterator.hasNext()) {
                     EntityPlayer entityplayer = (EntityPlayer) iterator.next();
 
+                    MCUtil.ensureMain(() -> { // Yatopia - ensure worldmap updates are done on the main thread
                     worldmap.a((EntityHuman) entityplayer, itemstack);
+                    }); // Yatopia
                     Packet<?> packet = ((ItemWorldMap) itemstack.getItem()).a(itemstack, (World) this.b, (EntityHuman) entityplayer);
 
                     if (packet != null) {
                         entityplayer.playerConnection.sendPacket(packet);
                     }
                 }
+                } finally { // Yatopia start
+                    trackedPlayersLock.unlock();
+                } // Yatopia end
             }
 
             this.c();
@@ -240,6 +248,7 @@ public class EntityTrackerEntry {
         ++this.tickCounter;
         if (this.tracker.velocityChanged) {
             // CraftBukkit start - Create PlayerVelocity event
+            MCUtil.ensureMain(() -> { // Yatopia - ensure this is called on the main thread
             boolean cancelled = false;
 
             if (this.tracker instanceof EntityPlayer) {
@@ -259,6 +268,7 @@ public class EntityTrackerEntry {
             if (!cancelled) {
                 this.broadcastIncludingSelf(new PacketPlayOutEntityVelocity(this.tracker));
             }
+            }); // Yatopia
             // CraftBukkit end
             this.tracker.velocityChanged = false;
         }
@@ -282,7 +292,9 @@ public class EntityTrackerEntry {
     // Purpur end
 
     public void a(EntityPlayer entityplayer) {
+        MCUtil.ensureMain(() -> { // Yatopia - ensure main
         this.tracker.c(entityplayer);
+        }); // Yatopia
         entityplayer.c(this.tracker);
     }
 
@@ -291,7 +303,9 @@ public class EntityTrackerEntry {
 
         entityplayer.playerConnection.getClass();
         this.a(playerconnection::sendPacket, entityplayer); // CraftBukkit - add player
+        MCUtil.ensureMain(() -> { // Yatopia - ensure main
         this.tracker.b(entityplayer);
+        }); // Yatopia
         entityplayer.d(this.tracker);
     }
 
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 6c8cb39ac8786734cda994ef29ba74c685f3b9be..9cde23731ca8e22b5263784e65c7efb0ce5312f8 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -11,6 +11,7 @@ import com.google.common.collect.Sets;
 import com.mojang.datafixers.DataFixer;
 import com.mojang.datafixers.util.Either;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; // Yatopia
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 import it.unimi.dsi.fastutil.longs.Long2ByteMap;
 import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
@@ -113,10 +114,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     private final File w;
     private final PlayerMap playerMap;
     public final Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities;
+    public final java.util.concurrent.locks.Lock trackedEntitiesLock = new java.util.concurrent.locks.ReentrantLock(); // Yatopia
     private final Long2ByteMap z;
     private final Queue<Runnable> A; private final Queue<Runnable> getUnloadQueueTasks() { return this.A; } // Paper - OBFHELPER
     int viewDistance; // Paper - private -> package private
     public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper
+    private static final java.util.concurrent.ExecutorService trackerExecutor = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Entity Tracker - %d").build()); // Yatopia
 
     // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
     public final CallbackExecutor callbackExecutor = new CallbackExecutor();
@@ -301,7 +304,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         this.unloadQueue = new LongOpenHashSet();
         this.u = new AtomicInteger();
         this.playerMap = new PlayerMap();
-        this.trackedEntities = new Int2ObjectOpenHashMap();
+        this.trackedEntities = Int2ObjectMaps.synchronize(new Int2ObjectOpenHashMap<>()); // Yatopia
         this.z = new Long2ByteOpenHashMap();
         this.A = new com.destroystokyo.paper.utils.CachedSizeConcurrentLinkedQueue<>(); // Paper - need constant-time size()
         this.definedStructureManager = definedstructuremanager;
@@ -2008,6 +2011,8 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     protected void addEntity(Entity entity) {
         org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
         // Paper start - ignore and warn about illegal addEntity calls instead of crashing server
+        trackedEntitiesLock.lock(); // Yatopia
+        try { // Yatopia
         if (!entity.valid || entity.world != this.world || this.trackedEntities.containsKey(entity.getId())) {
             new Throwable("[ERROR] Illegal PlayerChunkMap::addEntity for world " + this.world.getWorld().getName()
                 + ": " + entity  + (this.trackedEntities.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""))
@@ -2047,10 +2052,15 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
 
             }
         }
+        } finally { // Yatopia start
+            trackedEntitiesLock.unlock();
+        } // Yatopia end
     }
 
     protected void removeEntity(Entity entity) {
         org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot
+        trackedEntitiesLock.lock(); // Yatopia
+        try { // Yatopia
         if (entity instanceof EntityPlayer) {
             EntityPlayer entityplayer = (EntityPlayer) entity;
 
@@ -2069,6 +2079,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         if (playerchunkmap_entitytracker1 != null) {
             playerchunkmap_entitytracker1.a();
         }
+        } finally { // Yatopia start
+            trackedEntitiesLock.unlock();
+        } // Yatopia end
         entity.tracker = null; // Paper - We're no longer tracked
     }
 
@@ -2077,12 +2090,14 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
         this.world.timings.tracker1.startTiming();
         try {
             com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Chunk> iterator = this.world.getChunkProvider().entityTickingChunks.iterator();
+            trackedEntitiesLock.lock(); // Yatopia
             try {
             while (iterator.hasNext()) {
                 Chunk chunk = iterator.next();
                 Entity[] entities = chunk.entities.getRawData();
                 for (int i = 0, len = chunk.entities.size(); i < len; ++i) {
                     Entity entity = entities[i];
+                    if (entity == null) continue; // Yatopia
                     EntityTracker tracker = this.trackedEntities.get(entity.getId());
                     if (tracker != null) {
                         tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange());
@@ -2092,6 +2107,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
             }
             } finally {
                 iterator.finishedIterating();
+                trackedEntitiesLock.unlock(); // Yatopia
             }
         } finally {
             this.world.timings.tracker1.stopTiming();
@@ -2099,10 +2115,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
     // Paper end - optimised tracker
 
-    protected void g() {
+    protected void g() { // Yatopia - diff on change - make sure paper didn't drop tracker optimizations, otherwise kaboom
         // Paper start - optimized tracker
         if (true) {
-            this.processTrackQueue();
+            trackerExecutor.execute(this::processTrackQueue); // Yatopia
             return;
         }
         // Paper end - optimized tracker
@@ -2146,20 +2162,30 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
     }
 
     protected void broadcast(Entity entity, Packet<?> packet) {
+        trackedEntitiesLock.lock(); // Yatopia
+        try { // Yatopia
         PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) this.trackedEntities.get(entity.getId());
 
         if (playerchunkmap_entitytracker != null) {
             playerchunkmap_entitytracker.broadcast(packet);
         }
+        } finally { // Yatopia start
+            trackedEntitiesLock.unlock();
+        } // Yatopia end
 
     }
 
     protected void broadcastIncludingSelf(Entity entity, Packet<?> packet) {
+        trackedEntitiesLock.lock(); // Yatopia
+        try { // Yatopia
         PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) this.trackedEntities.get(entity.getId());
 
         if (playerchunkmap_entitytracker != null) {
             playerchunkmap_entitytracker.broadcastIncludingSelf(packet);
         }
+        } finally { // Yatopia start
+            trackedEntitiesLock.unlock();
+        } // Yatopia end
 
     }
 
@@ -2272,11 +2298,13 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
 
         entityplayer.a(chunk.getPos(), apacket[0], apacket[1]);
         PacketDebug.a(this.world, chunk.getPos());
-        List<Entity> list = Lists.newArrayList();
-        List<Entity> list1 = Lists.newArrayList();
+        List<Entity> list = new net.yatopia.server.list.GlueList<>(); // Yatopia
+        List<Entity> list1 = new net.yatopia.server.list.GlueList<>(); // Yatopia
         // Paper start - optimise entity tracker
         // use the chunk entity list, not the whole trackedEntities map...
         Entity[] entities = chunk.entities.getRawData();
+        trackedEntitiesLock.lock(); // Yatopia
+        try { // Yatopia
         for (int i = 0, size = chunk.entities.size(); i < size; ++i) {
             Entity entity = entities[i];
             if (entity == entityplayer) {
@@ -2298,6 +2326,9 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
                 list1.add(entity);
             }
         }
+        } finally { // Yatopia start
+            trackedEntitiesLock.unlock();
+        } // Yatopia end
         // Paper end - optimise entity tracker
 
         Iterator iterator;
@@ -2345,6 +2376,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
         // their first update (which is forced to have absolute coordinates), false afterward.
         public java.util.Map<EntityPlayer, Boolean> trackedPlayerMap = new java.util.HashMap<>();
         public Set<EntityPlayer> trackedPlayers = trackedPlayerMap.keySet();
+        public java.util.concurrent.locks.Lock trackedPlayersLock = new java.util.concurrent.locks.ReentrantLock(); // Yatopia
 
         public EntityTracker(Entity entity, int i, int j, boolean flag) {
             this.trackerEntry = new EntityTrackerEntry(PlayerChunkMap.this.world, entity, j, flag, this::broadcast, trackedPlayerMap); // CraftBukkit // Paper
@@ -2381,11 +2413,18 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
             // stuff could have been removed, so we need to check the trackedPlayers set
             // for players that were removed
 
+            // Yatopia start
+            trackedPlayersLock.lock();
+            try {
+                // Yatopia end
             for (EntityPlayer player : this.trackedPlayers.toArray(new EntityPlayer[0])) { // avoid CME
                 if (newTrackerCandidates == null || !newTrackerCandidates.contains(player)) {
                     this.updatePlayer(player);
                 }
             }
+            } finally { // Yatopia start
+                trackedPlayersLock.unlock();
+            } // Yatopia end
         }
         // Paper end - use distance map to optimise tracker
 
@@ -2398,6 +2437,10 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
         }
 
         public void broadcast(Packet<?> packet) {
+            // Yatopia start
+            trackedPlayersLock.lock();
+            try {
+                // Yatopia end
             Iterator iterator = this.trackedPlayers.iterator();
 
             while (iterator.hasNext()) {
@@ -2405,6 +2448,9 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
 
                 entityplayer.playerConnection.sendPacket(packet);
             }
+            } finally { // Yatopia start
+                trackedPlayersLock.unlock();
+            } // Yatopia end
 
         }
 
@@ -2417,6 +2463,10 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
         }
 
         public void a() {
+            // Yatopia start
+            trackedPlayersLock.lock();
+            try {
+                // Yatopia end
             Iterator iterator = this.trackedPlayers.iterator();
 
             while (iterator.hasNext()) {
@@ -2424,19 +2474,29 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
 
                 this.trackerEntry.a(entityplayer);
             }
+            } finally { // Yatopia start
+                trackedPlayersLock.unlock();
+            } // Yatopia end
 
         }
 
         public void clear(EntityPlayer entityplayer) {
             org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot
+            // Yatopia start
+            trackedPlayersLock.lock();
+            try {
+                // Yatopia end
             if (this.trackedPlayers.remove(entityplayer)) {
                 this.trackerEntry.a(entityplayer);
             }
+            } finally { // Yatopia start
+                trackedPlayersLock.unlock();
+            } // Yatopia end
 
         }
 
         public void updatePlayer(EntityPlayer entityplayer) {
-            org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
+            // org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot // Yatopia
             if (entityplayer != this.tracker) {
                 // Paper start - remove allocation of Vec3D here
                 //Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113
@@ -2447,6 +2507,10 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
                 int i = Math.min(this.b(), (PlayerChunkMap.this.viewDistance - 1) * 16);
                 boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.tracker.a(entityplayer); // Paper - remove allocation of Vec3D here
 
+                // Yatopia start
+                trackedPlayersLock.lock();
+                try {
+                    // Yatopia end
                 if (flag) {
                     boolean flag1 = this.tracker.attachedToPlayer;
 
@@ -2467,7 +2531,14 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
                         }
                     }
 
+                    // Yatopia start
+                    entityplayer.removeQueueLock.lock();
+                    try {
+                        // Yatopia end
                     entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId()));
+                    } finally { // Yatopia start
+                        entityplayer.removeQueueLock.unlock();
+                    } // Yatopia end
                     // CraftBukkit end
 
                     if (flag1 && this.trackedPlayerMap.putIfAbsent(entityplayer, true) == null) { // Paper
@@ -2476,6 +2547,9 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
                 } else if (this.trackedPlayers.remove(entityplayer)) {
                     this.trackerEntry.a(entityplayer);
                 }
+                } finally { // Yatopia start
+                    trackedPlayersLock.unlock();
+                } // Yatopia end
 
             }
         }
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 7e91e95941039cce630ed3eb88e1919e1d08c091..e87c02eec6885805a86e67fada0cf033ced5f304 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -1253,10 +1253,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
     private void unregisterPlayer(EntityPlayer other) {
         PlayerChunkMap tracker = ((WorldServer) entity.world).getChunkProvider().playerChunkMap;
         // Paper end
+        // Yatopia start
+        tracker.trackedEntitiesLock.lock();
+        try { // Yatopia end
         PlayerChunkMap.EntityTracker entry = tracker.trackedEntities.get(other.getId());
         if (entry != null) {
             entry.clear(getHandle());
         }
+        } finally { // Yatopia start
+            tracker.trackedEntitiesLock.unlock();
+        } // Yatopia end
 
         // Remove the hidden player from this player user list, if they're on it
         if (other.sentListPacket) {
@@ -1303,10 +1309,25 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
 
         getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other));
 
+        // Yatopia start
+        tracker.trackedEntitiesLock.lock();
+        try { // Yatopia end
         PlayerChunkMap.EntityTracker entry = tracker.trackedEntities.get(other.getId());
-        if (entry != null && !entry.trackedPlayers.contains(getHandle())) {
+        if (entry != null) { // Yatopia - check moved down
+            // Yatopia start
+            entry.trackedPlayersLock.lock();
+            try {
+                if (!entry.trackedPlayers.contains(getHandle())) {
             entry.updatePlayer(getHandle());
+                }
+            } finally {
+                entry.trackedPlayersLock.unlock();
+            }
+            // Yatopia end
         }
+        } finally { // Yatopia start
+            tracker.trackedEntitiesLock.unlock();
+        } // Yatopia end
     }
     // Paper start
     private void reregisterPlayer(EntityPlayer player) {
