From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Thu, 9 Jul 2020 13:34:59 -0700 Subject: [PATCH] Optimise WorldServer#notify Iterating over all of the navigators in the world is pretty expensive. Instead, only iterate over navigators in the current region that are eligible for repathing. diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index daa589cb9441d2255dbbd30583a8c003a325976f..4386402e5e55438475c48b023c61bfb2dbe71a8f 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -290,15 +290,81 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager; public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData { + // Paper start - optimise notify() + private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigators; + + public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { + return this.navigators; + } + + public boolean addToNavigators(final Mob navigator) { + if (this.navigators == null) { + this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(); + } + return this.navigators.add(navigator); + } + + public boolean removeFromNavigators(final Mob navigator) { + if (this.navigators == null) { + return false; + } + return this.navigators.remove(navigator); + } + // Paper end - optimise notify() } public static final class DataRegionSectionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSectionData { + // Paper start - optimise notify() + private io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigators; + + public io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { + return this.navigators; + } + + public boolean addToNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { + if (this.navigators == null) { + this.navigators = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(); + } + final boolean ret = this.navigators.add(navigator); + if (ret) { + final DataRegionData data = (DataRegionData)section.getRegion().regionData; + if (!data.addToNavigators(navigator)) { + throw new IllegalStateException(); + } + } + return ret; + } + + public boolean removeFromNavigators(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { + if (this.navigators == null) { + return false; + } + final boolean ret = this.navigators.remove(navigator); + if (ret) { + final DataRegionData data = (DataRegionData)section.getRegion().regionData; + if (!data.removeFromNavigators(navigator)) { + throw new IllegalStateException(); + } + } + return ret; + } + // Paper end - optimise notify() + @Override public void removeFromRegion(final io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section, final io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region from) { final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; final DataRegionData fromData = (DataRegionData)from.regionData; + // Paper start - optimise notify() + if (sectionData.navigators != null) { + for (final Iterator iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { + if (!fromData.removeFromNavigators(iterator.next())) { + throw new IllegalStateException(); + } + } + } + // Paper end - optimise notify() } @Override @@ -308,6 +374,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData; final DataRegionData newRegionData = (DataRegionData)newRegion.regionData; + // Paper start - optimise notify() + if (sectionData.navigators != null) { + for (final Iterator iterator = sectionData.navigators.unsafeIterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { + if (!newRegionData.addToNavigators(iterator.next())) { + throw new IllegalStateException(); + } + } + } + // Paper end - optimise notify() } } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 6e8115c3ab1f6986fdf3b3716cb09add91424306..cebaf0fb8187335ca303621a2cb412bb22584e23 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -1079,6 +1079,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public void tickNonPassenger(Entity entity) { // Paper start - log detailed entity tick information io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); + this.entityManager.updateNavigatorsInRegion(entity); // Paper - optimise notify try { if (currentlyTickingEntity.get() == null) { currentlyTickingEntity.lazySet(entity); @@ -1524,9 +1525,19 @@ public class ServerLevel extends Level implements WorldGenLevel { VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { - Iterator iterator = this.navigatingMobs.iterator(); + // Paper start - optimise notify() + io.papermc.paper.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4); + if (region == null) { + return; + } + io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators(); + if (navigatorsFromRegion == null) { + return; + } + io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigatorsFromRegion.iterator(); - while (iterator.hasNext()) { + + try { while (iterator.hasNext()) { // Paper end - optimise notify() // CraftBukkit start - fix SPIGOT-6362 Mob entityinsentient; try { @@ -1545,6 +1556,11 @@ public class ServerLevel extends Level implements WorldGenLevel { navigationabstract.recomputePath(pos); } } + // Paper start - optimise notify() + } finally { + iterator.finishedIterating(); + } + // Paper end - optimise notify() } } // Paper @@ -2326,10 +2342,12 @@ public class ServerLevel extends Level implements WorldGenLevel { public void onTickingStart(Entity entity) { ServerLevel.this.entityTickList.add(entity); + ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Paper - optimise notify } public void onTickingEnd(Entity entity) { ServerLevel.this.entityTickList.remove(entity); + ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Paper - optimise notify } public void onTrackingStart(Entity entity) { diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java index e605daac0c90f5d0b9315d1499938feb0e478d0e..b5c385aeb86ac267cae9726354c896ee38a50634 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java @@ -27,7 +27,7 @@ import net.minecraft.world.phys.Vec3; public abstract class PathNavigation { private static final int MAX_TIME_RECOMPUTE = 20; - protected final Mob mob; + protected final Mob mob; public final Mob getEntity() { return this.mob; } // Paper - public accessor protected final Level level; @Nullable protected Path path; @@ -40,7 +40,7 @@ public abstract class PathNavigation { protected long lastTimeoutCheck; protected double timeoutLimit; protected float maxDistanceToWaypoint = 0.5F; - protected boolean hasDelayedRecomputation; + protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Paper - public accessor protected long timeLastRecompute; protected NodeEvaluator nodeEvaluator; private BlockPos targetPos; @@ -49,6 +49,13 @@ public abstract class PathNavigation { public final PathFinder pathFinder; private boolean isStuck; + // Paper start + public boolean isViableForPathRecalculationChecking() { + return !this.needsPathRecalculation() && + (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0); + } + // Paper end + public PathNavigation(Mob mob, Level world) { this.mob = mob; this.level = world; @@ -404,7 +411,7 @@ public abstract class PathNavigation { } public void recomputePath(BlockPos pos) { - if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { + if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Paper - diff on change - needed for isViableForPathRecalculationChecking() Node node = this.path.getEndNode(); Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D); if (pos.closerThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()))) { diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java index a0dd49d17e1a83edbb0bf5cfbaba3f8fb665f75a..90427b7ee0ad69afa498ce6accd369d28a55427d 100644 --- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java @@ -57,6 +57,65 @@ public class PersistentEntitySectionManager implements A this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage); } + // Paper start - optimise notify() + public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) { + if (!(entity instanceof net.minecraft.world.entity.Mob)) { + return; + } + io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = + this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ); + if (section != null) { + net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; + sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); + } + } + + public final void removeNavigatorsFromData(Entity entity) { + if (!(entity instanceof net.minecraft.world.entity.Mob)) { + return; + } + BlockPos entityPos = entity.blockPosition(); + io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = + this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); + if (section != null) { + net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; + sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); + } + } + + public final void addNavigatorsIfPathingToRegion(Entity entity) { + if (!(entity instanceof net.minecraft.world.entity.Mob)) { + return; + } + BlockPos entityPos = entity.blockPosition(); + io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = + this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); + if (section != null) { + net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; + if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { + sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); + } + } + } + + public final void updateNavigatorsInRegion(Entity entity) { + if (!(entity instanceof net.minecraft.world.entity.Mob)) { + return; + } + BlockPos entityPos = entity.blockPosition(); + io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionSection section = + this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); + if (section != null) { + net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; + if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { + sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); + } else { + sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); + } + } + } + // Paper end - optimise notify() + void removeSectionIfEmpty(long sectionPos, EntitySection section) { if (section.isEmpty()) { this.sectionStorage.remove(sectionPos); @@ -435,11 +494,25 @@ public class PersistentEntitySectionManager implements A @Override public void onMove() { BlockPos blockposition = this.entity.blockPosition(); - long i = SectionPos.asLong(blockposition); + long i = SectionPos.asLong(blockposition); final long newSectionPos = i; // Paper - diff on change, new position section if (i != this.currentSectionKey) { PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper - Visibility visibility = this.currentSection.getStatus(); + Visibility visibility = this.currentSection.getStatus(); final Visibility oldVisibility = visibility; // Paper - diff on change - this should be OLD section visibility + // Paper start + int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift; + int oldChunkX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey); + int oldChunkZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey); + int oldRegionX = oldChunkX >> shift; + int oldRegionZ = oldChunkZ >> shift; + + int newRegionX = io.papermc.paper.util.CoordinateUtils.getChunkSectionX(newSectionPos) >> shift; + int newRegionZ = io.papermc.paper.util.CoordinateUtils.getChunkSectionZ(newSectionPos) >> shift; + + if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { + PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ); + } + // Paper end if (!this.currentSection.remove(this.entity)) { PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", this.entity, SectionPos.of(this.currentSectionKey), i); @@ -451,6 +524,11 @@ public class PersistentEntitySectionManager implements A entitysection.add(this.entity); // CraftBukkit - decompile error this.currentSection = entitysection; this.currentSectionKey = i; + // Paper start + if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && oldVisibility.isTicking() && entitysection.getStatus().isTicking()) { + PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity); + } + // Paper end this.updateStatus(visibility, entitysection.getStatus()); }