From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Tue, 5 May 2020 20:18:05 -0700 Subject: [PATCH] Use distance map to optimise entity tracker Use the distance map to find candidate players for tracking. diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/Entity.java +++ b/src/main/java/net/minecraft/server/Entity.java @@ -0,0 +0,0 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke } // Paper end + // Paper start - optimise entity tracking + final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); + + boolean isLegacyTrackingEntity = false; + + public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { + this.isLegacyTrackingEntity = isLegacyTrackingEntity; + } + + final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { + return ((WorldServer)this.world).getChunkProvider().playerChunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] + .getObjectsInRange(MCUtil.getCoordinateKey(this)); + } + // Paper end - optimise entity tracking + public Entity(EntityTypes entitytypes, World world) { this.id = Entity.entityCount.incrementAndGet(); this.passengers = Lists.newArrayList(); diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java +++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java @@ -0,0 +0,0 @@ public class EntityTrackerEntry { this.r = entity.isOnGround(); } + public final void tick() { this.a(); } // Paper - OBFHELPER public void a() { List list = this.tracker.getPassengers(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -0,0 +0,0 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + // Paper start - use distance map to optimise tracker + public static boolean isLegacyTrackingEntity(Entity entity) { + return entity.isLegacyTrackingEntity; + } + + // inlined EnumMap, TrackingRange.TrackingRangeType + static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); + final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; + final int[] entityTrackerTrackRanges; + + private int convertSpigotRangeToVanilla(final int vanilla) { + return MinecraftServer.getServer().applyTrackingRangeScale(vanilla); + } + // Paper end - use distance map to optimise tracker void addPlayerToDistanceMaps(EntityPlayer player) { 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 + // Paper start - use distance map to optimise entity tracker + for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { + com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; + int trackRange = this.entityTrackerTrackRanges[i]; + + trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker } void removePlayerFromDistanceMaps(EntityPlayer player) { - + // 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); + } + // Paper end - use distance map to optimise tracker } void updateMaps(EntityPlayer player) { 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 + // Paper start - use distance map to optimise entity tracker + for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) { + com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; + int trackRange = this.entityTrackerTrackRanges[i]; + + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } + // Paper end - use distance map to optimise entity tracker } // Paper end @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.m = new VillagePlace(new File(this.w, "poi"), datafixer, flag, this.world); // Paper this.setViewDistance(i); this.playerMobDistanceMap = this.world.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper + // Paper start - use distance map to optimise entity tracker + this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length]; + this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length]; + + org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.world.spigotConfig; + + for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) { + org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal]; + int configuredSpigotValue; + switch (trackingRangeType) { + case PLAYER: + configuredSpigotValue = spigotWorldConfig.playerTrackingRange; + break; + case ANIMAL: + configuredSpigotValue = spigotWorldConfig.animalTrackingRange; + break; + case MONSTER: + configuredSpigotValue = spigotWorldConfig.monsterTrackingRange; + break; + case MISC: + configuredSpigotValue = spigotWorldConfig.miscTrackingRange; + break; + case OTHER: + configuredSpigotValue = spigotWorldConfig.otherTrackingRange; + break; + case ENDERDRAGON: + configuredSpigotValue = 10 * 16; // default is 10 chunk range // TODO check on update + break; + default: + throw new IllegalStateException("Missing case for enum " + trackingRangeType); + } + configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue); + + int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0); + this.entityTrackerTrackRanges[ordinal] = trackRange; + + this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); + } + // Paper end - use distance map to optimise entity tracker } public void updatePlayerMobTypeMap(Entity entity) { @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } public void movePlayer(EntityPlayer entityplayer) { - ObjectIterator objectiterator = this.trackedEntities.values().iterator(); - - while (objectiterator.hasNext()) { - PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); - - if (playerchunkmap_entitytracker.tracker == entityplayer) { - playerchunkmap_entitytracker.track(this.world.getPlayers()); - } else { - playerchunkmap_entitytracker.updatePlayer(entityplayer); - } - } + // Paper - delay this logic for the entity tracker tick, no need to duplicate it int i = MathHelper.floor(entityplayer.locX()) >> 4; int j = MathHelper.floor(entityplayer.locZ()) >> 4; @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker this.trackedEntities.put(entity.getId(), playerchunkmap_entitytracker); - playerchunkmap_entitytracker.track(this.world.getPlayers()); + playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players if (entity instanceof EntityPlayer) { EntityPlayer entityplayer = (EntityPlayer) entity; @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { entity.tracker = null; // Paper - We're no longer tracked } + // Paper start - optimised tracker + private final void processTrackQueue() { + this.world.timings.tracker1.startTiming(); + try { + for (EntityTracker tracker : this.trackedEntities.values()) { + // update tracker entry + tracker.updatePlayers(tracker.tracker.getPlayersInTrackRange()); + } + } finally { + this.world.timings.tracker1.stopTiming(); + } + + + this.world.timings.tracker2.startTiming(); + try { + for (EntityTracker tracker : this.trackedEntities.values()) { + tracker.trackerEntry.tick(); + } + } finally { + this.world.timings.tracker2.stopTiming(); + } + } + // Paper end - optimised tracker + protected void g() { + // Paper start - optimized tracker + if (true) { + this.processTrackQueue(); + return; + } + // Paper end - optimized tracker List list = Lists.newArrayList(); List list1 = this.world.getPlayers(); @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { PacketDebug.a(this.world, chunk.getPos()); List list = Lists.newArrayList(); List list1 = Lists.newArrayList(); - ObjectIterator objectiterator = this.trackedEntities.values().iterator(); + // Paper start - optimise entity tracker + // use the chunk entity list, not the whole trackedEntities map... + Entity[] entities = chunk.entities.getRawData(); + for (int i = 0, size = chunk.entities.size(); i < size; ++i) { + Entity entity = entities[i]; + if (entity == entityplayer) { + continue; + } + PlayerChunkMap.EntityTracker tracker = this.trackedEntities.get(entity.getId()); + if (tracker != null) { // dumb plugins... move on... + tracker.updatePlayer(entityplayer); + } - while (objectiterator.hasNext()) { - PlayerChunkMap.EntityTracker playerchunkmap_entitytracker = (PlayerChunkMap.EntityTracker) objectiterator.next(); - Entity entity = playerchunkmap_entitytracker.tracker; + // keep the vanilla logic here - this is REQUIRED or else passengers and their vehicles disappear! + // (and god knows what the leash thing is) - if (entity != entityplayer && entity.chunkX == chunk.getPos().x && entity.chunkZ == chunk.getPos().z) { - playerchunkmap_entitytracker.updatePlayer(entityplayer); - if (entity instanceof EntityInsentient && ((EntityInsentient) entity).getLeashHolder() != null) { - list.add(entity); - } + if (entity instanceof EntityInsentient && ((EntityInsentient)entity).getLeashHolder() != null) { + list.add(entity); + } - if (!entity.getPassengers().isEmpty()) { - list1.add(entity); - } + if (!entity.getPassengers().isEmpty()) { + list1.add(entity); } } + // Paper end - optimise entity tracker Iterator iterator; Entity entity1; @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { public class EntityTracker { - private final EntityTrackerEntry trackerEntry; + final EntityTrackerEntry trackerEntry; // Paper - private -> package private private final Entity tracker; private final int trackingDistance; private SectionPosition e; @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { this.e = SectionPosition.a(entity); } + // Paper start - use distance map to optimise tracker + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet lastTrackerCandidates; + + final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newTrackerCandidates) { + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet oldTrackerCandidates = this.lastTrackerCandidates; + this.lastTrackerCandidates = newTrackerCandidates; + + if (newTrackerCandidates != null) { + Object[] rawData = newTrackerCandidates.getBackingSet(); + for (int i = 0, len = rawData.length; i < len; ++i) { + Object raw = rawData[i]; + if (!(raw instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer)raw; + this.updatePlayer(player); + } + } + + if (oldTrackerCandidates == newTrackerCandidates) { + // this is likely the case. + // means there has been no range changes, so we can just use the above for tracking. + return; + } + + // stuff could have been removed, so we need to check the trackedPlayers set + // for players that were removed + + for (EntityPlayer player : this.trackedPlayers.toArray(new EntityPlayer[0])) { // avoid CME + if (newTrackerCandidates == null || !newTrackerCandidates.contains(player)) { + this.updatePlayer(player); + } + } + } + // Paper end - use distance map to optimise tracker + public boolean equals(Object object) { return object instanceof PlayerChunkMap.EntityTracker ? ((PlayerChunkMap.EntityTracker) object).tracker.getId() == this.tracker.getId() : false; } @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { int j = entity.getEntityType().getChunkRange() * 16; j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper - if (j > i) { + if (j < i) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic i = j; } } diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/spigotmc/TrackingRange.java +++ b/src/main/java/org/spigotmc/TrackingRange.java @@ -0,0 +0,0 @@ public class TrackingRange return config.miscTrackingRange; } else { - if (entity instanceof EntityEnderDragon) return ((WorldServer)(entity.getWorld())).getChunkProvider().playerChunkMap.getLoadViewDistance(); // Paper - enderdragon is exempt + if (entity instanceof EntityEnderDragon) return defaultRange; // Paper - enderdragon is exempt return config.otherTrackingRange; } } + + // Paper start - optimise entity tracking + // copied from above, TODO check on update + public static TrackingRangeType getTrackingRangeType(Entity entity) + { + if ( entity instanceof EntityPlayer ) + { + return TrackingRangeType.PLAYER; + // Paper start - Simplify and set water mobs to animal tracking range + } + switch (entity.activationType) { + case RAIDER: + case MONSTER: + case FLYING_MONSTER: + return TrackingRangeType.MONSTER; + case WATER: + case VILLAGER: + case ANIMAL: + return TrackingRangeType.ANIMAL; + case MISC: + } + if ( entity instanceof EntityItemFrame || entity instanceof EntityPainting || entity instanceof EntityItem || entity instanceof EntityExperienceOrb ) + // Paper end + { + return TrackingRangeType.MISC; + } else + { + if (entity instanceof EntityEnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt + return TrackingRangeType.OTHER; + } + } + + public static enum TrackingRangeType { + PLAYER, + ANIMAL, + MONSTER, + MISC, + OTHER, + ENDERDRAGON; + } + // Paper end - optimise entity tracking }