From 5372ac364bf598a98dc866d750ddba5123f3033a Mon Sep 17 00:00:00 2001 From: froobynooby Date: Thu, 20 Feb 2020 15:50:49 +0930 Subject: [PATCH] Reduce entity tracker updates on move With this patch, for each player we keep track of a set of entities that the player is tracking. This is used to split the entity tracker update logic in the movePlayer method in PlayerChunkMap in to two parts: * Full update: Run through all entity trackers and update them * Partial update: Run through all entity trackers for entities the player is already tracking and update them Partial updates will always take less time than full updates, usually by a considerable amount if players and entities are spread out over the map. Assuming they are evenly spread, and given there are x many players, it would be expected to take 1/x the time of a full update. Full updates are only run if the following conditions are met: * It has been 20 ticks since the last full update * The player has moved over set distance since the last full update (distance is configurable) The motivation for the first condition is that the client sends the server its position once a second, which calls movePlayer, so at a minimum we want to be sending the player an updated set of entities it can see every second. The motivation for the second condition is that looping through every entity in world to check if it is now within the tracking range after the player has moved 0.1 blocks is largely unnecessary. Checking only after the player has moved 1 or 2 blocks is far better for performance, and very unlikely to give any noticeable side effects. In testing, this has reduced the time taken for movement packet processing by up to 4x. Packet processing for movement packets often show up as a major contributor to TPS loss in servers with large player counts diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java index 7ca67a4a..e93176d8 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -668,4 +668,9 @@ public class PaperWorldConfig { private void zombieVillagerInfectionChance() { zombieVillagerInfectionChance = getDouble("zombie-villager-infection-chance", zombieVillagerInfectionChance); } + + public double trackerUpdateDistance = 1; + private void trackerUpdateDistance() { + trackerUpdateDistance = getDouble("tracker-update-distance", trackerUpdateDistance); + } } diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java index 24545b33..828ba458 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -86,6 +86,10 @@ public class EntityPlayer extends EntityHuman implements ICrafting { public final int[] mobCounts = new int[ENUMCREATURETYPE_TOTAL_ENUMS]; // Paper public final com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet cachedSingleMobDistanceMap; // Paper end + // Paper start - Reduce entity tracker updates on move + public Vec3D lastTrackedPosition = new Vec3D(0, 0, 0); + public long lastTrackedTick; + // Paper end // CraftBukkit start public String displayName; diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java index f564502f..0186ab9e 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -133,6 +133,39 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } + // Paper end + + // Paper start - Reduce entity tracker updates on move + private double trackerUpdateDistanceSquared; + private final Int2ObjectMap> playerTrackedEntities = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap> playerTrackedEntitiesRemoveQueue = new Int2ObjectOpenHashMap<>(); + + void flushRemoveQueue(EntityPlayer entityplayer) { + Queue removeQueue = getPlayerTrackedEntityMapRemoveQueue(entityplayer.getId()); + Int2ObjectMap entityMap = getPlayerTrackedEntityMap(entityplayer.getId()); + for (Integer id = removeQueue.poll(); id != null; id = removeQueue.poll()) { + entityMap.remove(id); + } + } + + void flushRemoveQueues() { + for (Int2ObjectMap.Entry> entry : playerTrackedEntitiesRemoveQueue.int2ObjectEntrySet()) { + Int2ObjectMap entityMap = getPlayerTrackedEntityMap(entry.getKey()); + Queue removeQueue = entry.getValue(); + for (Integer id = removeQueue.poll(); id != null; id = removeQueue.poll()) { + entityMap.remove(id); + } + } + } + + Int2ObjectMap getPlayerTrackedEntityMap(int id) { + return playerTrackedEntities.computeIfAbsent(id, i -> new Int2ObjectOpenHashMap<>()); + } + + Queue getPlayerTrackedEntityMapRemoveQueue(int id) { + return playerTrackedEntitiesRemoveQueue.computeIfAbsent(id, i -> new java.util.ArrayDeque<>()); + } + // Paper end public PlayerChunkMap(WorldServer worldserver, File file, DataFixer datafixer, DefinedStructureManager definedstructuremanager, Executor executor, IAsyncTaskHandler iasynctaskhandler, ILightAccess ilightaccess, ChunkGenerator chunkgenerator, WorldLoadListener worldloadlistener, Supplier supplier, int i) { @@ -167,6 +200,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.m = new VillagePlace(new File(this.w, "poi"), datafixer, this.world); // Paper this.setViewDistance(i); this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper + this.trackerUpdateDistanceSquared = Math.pow(this.world.paperConfig.trackerUpdateDistance, 2); // Paper } public void updatePlayerMobTypeMap(Entity entity) { @@ -1334,8 +1368,19 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } public void movePlayer(EntityPlayer entityplayer) { - ObjectIterator objectiterator = this.trackedEntities.values().iterator(); + // Paper start + // ObjectIterator objectiterator = this.trackedEntities.values().iterator(); + ObjectIterator objectiterator; + if (MinecraftServer.currentTick - entityplayer.lastTrackedTick >= 20 + || entityplayer.lastTrackedPosition.distanceSquared(entityplayer.getPositionVector()) >= trackerUpdateDistanceSquared) { + entityplayer.lastTrackedPosition = entityplayer.getPositionVector(); + entityplayer.lastTrackedTick = MinecraftServer.currentTick; + objectiterator = this.trackedEntities.values().iterator(); // Update all entity trackers + } else { + objectiterator = getPlayerTrackedEntityMap(entityplayer.getId()).values().iterator(); // Only update entity trackers for already tracked entities + } + // Paper end while (objectiterator.hasNext()) { PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); @@ -1345,6 +1390,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunkmap_entitytracker.updatePlayer(entityplayer); } } + flushRemoveQueues(); // Paper int i = MathHelper.floor(entityplayer.locX()) >> 4; int j = MathHelper.floor(entityplayer.locZ()) >> 4; @@ -1486,12 +1532,21 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { playerchunkmap_entitytracker.clear(entityplayer); } + // Paper start + playerTrackedEntities.remove(entityplayer.getId()); + playerTrackedEntitiesRemoveQueue.remove(entityplayer.getId()); + // Paper end } PlayerChunkMap.EntityTracker playerchunkmap_entitytracker1 = (PlayerChunkMap.EntityTracker) this.trackedEntities.remove(entity.getId()); if (playerchunkmap_entitytracker1 != null) { playerchunkmap_entitytracker1.a(); + // Paper start + for (EntityPlayer player : playerchunkmap_entitytracker1.trackedPlayers) { + getPlayerTrackedEntityMap(player.getId()).remove(playerchunkmap_entitytracker1.tracker.getId()); + } + // Paper end } entity.tracker = null; // Paper - We're no longer tracked } @@ -1532,7 +1587,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } world.timings.tracker2.stopTiming(); // Paper } - + flushRemoveQueues(); // Paper } @@ -1581,6 +1636,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } } } + flushRemoveQueue(entityplayer); // Paper Iterator iterator; Entity entity1; @@ -1677,6 +1733,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot if (this.trackedPlayers.remove(entityplayer)) { this.trackerEntry.a(entityplayer); + getPlayerTrackedEntityMap(entityplayer.getId()).remove(this.tracker.getId()); // Paper } } @@ -1713,9 +1770,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { if (flag1 && this.trackedPlayerMap.putIfAbsent(entityplayer, true) == null) { // Paper this.trackerEntry.b(entityplayer); + getPlayerTrackedEntityMap(entityplayer.getId()).put(this.tracker.getId(), this); // Paper } } else if (this.trackedPlayers.remove(entityplayer)) { this.trackerEntry.a(entityplayer); + getPlayerTrackedEntityMapRemoveQueue(entityplayer.getId()).add(this.tracker.getId()); // Paper } } -- 2.25.1.windows.1