mirror of
https://github.com/YatopiaMC/Yatopia.git
synced 2024-11-14 14:45:48 +01:00
2e894832ed
- Removed async entity tracking, as this is not a good implementation and has caused issues numerous times - Removed "0037-Load-also-the-chunk-that-you-re-teleporting-to" as it does not fix the core problem - Removed "0048-Fix-villager-dupe" as it was deemed unnecessary
579 lines
28 KiB
Diff
579 lines
28 KiB
Diff
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) {
|