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 97ec72dc383a2637c60cfc988bca2a8a86954ffb..236ba4c1950a3cced590f520b5349eede75fd59b 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -302,15 +302,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 @@ -320,6 +386,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 091e678a736f291b546ce1e684d4e03055734e02..1d0273a9ec5afc9287b9153a3746299a2ec9adfa 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -1117,6 +1117,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"); + if (!entity.isRemoved()) this.entityManager.updateNavigatorsInRegion(entity); // Paper - optimise notify try { if (currentlyTickingEntity.get() == null) { currentlyTickingEntity.lazySet(entity); @@ -1634,9 +1635,18 @@ public class ServerLevel extends Level implements WorldGenLevel { if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { List list = new ObjectArrayList(); - 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 { @@ -1658,16 +1668,23 @@ public class ServerLevel extends Level implements WorldGenLevel { try { this.isUpdatingNavigations = true; - iterator = list.iterator(); + // Paper start - optimise notify() + Iterator navigationIterator = list.iterator(); - while (iterator.hasNext()) { - PathNavigation navigationabstract1 = (PathNavigation) iterator.next(); + while (navigationIterator.hasNext()) { + PathNavigation navigationabstract1 = navigationIterator.next(); + // Paper end - optimise notify() navigationabstract1.recomputePath(); } } finally { this.isUpdatingNavigations = false; } + // Paper start - optimise notify() + } finally { + iterator.finishedIterating(); + } + // Paper end - optimise notify() } } // Paper @@ -2465,10 +2482,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 // Paper start - Reset pearls when they stop being ticked if (paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { pearl.cachedOwner = null; 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 af53372391d05dd6aa3757556418e8723b8b6d80..3f672d7c2377fca16a6d8d31cf7aaae4f009fdce 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 @@ -29,7 +29,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; @@ -42,7 +42,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; @Nullable @@ -420,7 +420,7 @@ public abstract class PathNavigation { public boolean shouldRecomputePath(BlockPos pos) { if (this.hasDelayedRecomputation) { return false; - } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { + } else 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); return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex())); @@ -436,4 +436,11 @@ public abstract class PathNavigation { public boolean isStuck() { return this.isStuck; } + + // Paper start + public boolean isViableForPathRecalculationChecking() { + return !this.needsPathRecalculation() && + (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0); + } + // Paper end } 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 8ad1c6f8147cfbd4677252a0d76f147786babe59..af37b1fcf8459af41482713a9e977599ae6da556 100644 --- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java @@ -71,6 +71,65 @@ public class PersistentEntitySectionManager implements A } // CraftBukkit end + // 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); @@ -456,11 +515,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 {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), i}); @@ -472,6 +545,11 @@ public class PersistentEntitySectionManager implements A entitysection.add(this.entity); 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()); }