diff --git a/patches/api/Add-Git-information-to-version-command-on-startup.patch b/patches/api/Add-Git-information-to-version-command-on-startup.patch index 466e59152b..0cdfd79e66 100644 --- a/patches/api/Add-Git-information-to-version-command-on-startup.patch +++ b/patches/api/Add-Git-information-to-version-command-on-startup.patch @@ -18,8 +18,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.util.Map; +import java.util.WeakHashMap; +import java.util.jar.Manifest; -+import org.checkerframework.checker.nullness.qual.Nullable; ++ +import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public final class JarManifests { @@ -28,7 +30,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + private static final Map MANIFESTS = Collections.synchronizedMap(new WeakHashMap<>()); + -+ public static Manifest manifest(final ClassLoader loader) { ++ public static @NotNull Manifest manifest(final @NotNull ClassLoader loader) { + return MANIFESTS.computeIfAbsent(loader, classLoader -> { + final @Nullable InputStream stream = classLoader.getResourceAsStream("META-INF/MANIFEST.MF"); + if (stream == null) { diff --git a/patches/server/Add-API-for-quit-reason.patch b/patches/server/Add-API-for-quit-reason.patch index 4da82e338f..0909695520 100644 --- a/patches/server/Add-API-for-quit-reason.patch +++ b/patches/server/Add-API-for-quit-reason.patch @@ -29,8 +29,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event diff --git a/patches/server/Add-critical-damage-API.patch b/patches/server/Add-critical-damage-API.patch index 7d881ab273..12d22e27dc 100644 --- a/patches/server/Add-critical-damage-API.patch +++ b/patches/server/Add-critical-damage-API.patch @@ -28,39 +28,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public static DamageSource sting(LivingEntity attacker) { return new EntityDamageSource("sting", attacker); -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity { - return this.hasEffect(MobEffects.JUMP) ? (double) (0.1F * (float) (this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; - } - -+ protected long lastJumpTime = 0L; // Paper - add critical damage API - protected void jumpFromGround() { - double d0 = (double) this.getJumpPower() + this.getJumpBoostPower(); - Vec3 vec3d = this.getDeltaMovement(); -+ // Paper start - add critical damage API -+ long time = System.nanoTime(); -+ boolean canCrit = true; -+ if (this instanceof net.minecraft.world.entity.player.Player) { -+ canCrit = false; -+ if (time - this.lastJumpTime > (long)(0.250e9)) { -+ this.lastJumpTime = time; -+ canCrit = true; -+ } -+ } -+ // Paper end - add critical damage API - - this.setDeltaMovement(vec3d.x, d0, vec3d.z); - if (this.isSprinting()) { - float f = this.getYRot() * 0.017453292F; - -- this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); -+ if (canCrit) this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); // Paper - add critical damage API - } - - this.hasImpulse = true; diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java diff --git a/patches/server/Entity-isTicking.patch b/patches/server/Entity-isTicking.patch index 0ba82d101b..c53bdfc8f8 100644 --- a/patches/server/Entity-isTicking.patch +++ b/patches/server/Entity-isTicking.patch @@ -8,9 +8,9 @@ diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/jav index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -0,0 +0,0 @@ import net.minecraft.network.syncher.SynchedEntityData; - import net.minecraft.resources.ResourceKey; +@@ -0,0 +0,0 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; + import net.minecraft.server.MCUtil; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; diff --git a/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch index f054058e86..151bd60e5e 100644 --- a/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch +++ b/patches/server/Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch @@ -21,6 +21,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.lastGoodX = this.awaitingPositionFromClient.x; this.lastGoodY = this.awaitingPositionFromClient.y; this.lastGoodZ = this.awaitingPositionFromClient.z; +@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + // CraftBukkit end + + this.awaitingTeleportTime = this.tickCount; +- this.player.absMoveTo(d0, d1, d2, f, f1); ++ this.player.moveTo(d0, d1, d2, f, f1); // Paper - use proper setPositionRotation for teleportation + this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); + } + diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java diff --git a/patches/server/Fix-dangerous-end-portal-logic.patch b/patches/server/Fix-dangerous-end-portal-logic.patch index 9dc7912ca7..d97325299f 100644 --- a/patches/server/Fix-dangerous-end-portal-logic.patch +++ b/patches/server/Fix-dangerous-end-portal-logic.patch @@ -15,9 +15,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); } - // Paper end - + // Paper end - optimise entity tracking + // Paper start - make end portalling safe + public BlockPos portalBlock; + public ServerLevel portalWorld; @@ -48,10 +48,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.teleportTo(worldserver, null); + } + // Paper end - make end portalling safe -+ + public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); - this.passengers = ImmutableList.of(); @@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i } diff --git a/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch index ceba229bb7..220b687582 100644 --- a/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ b/patches/server/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -542,7 +542,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public abstract class DistanceManager { public boolean runAllUpdates(ChunkMap chunkStorage) { - this.naturalSpawnChunkCounter.runAllUpdates(); + //this.f.a(); // Paper - no longer used this.tickingTicketsTracker.runAllUpdates(); + org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper this.playerTicketManager.runAllUpdates(); @@ -1127,19 +1127,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private final String name; private final Comparator comparator; -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - // CraftBukkit end - - this.awaitingTeleportTime = this.tickCount; -- this.player.absMoveTo(d0, d1, d2, f, f1); -+ this.player.moveTo(d0, d1, d2, f, f1); // Paper - use proper setPositionRotation for teleportation - this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport, flag)); - } - diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java diff --git a/patches/server/Optimise-nearby-player-lookups.patch b/patches/server/Optimise-nearby-player-lookups.patch index 0d383b980f..bda32c7be8 100644 --- a/patches/server/Optimise-nearby-player-lookups.patch +++ b/patches/server/Optimise-nearby-player-lookups.patch @@ -13,9 +13,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/level/ChunkHolder.java +++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java @@ -0,0 +0,0 @@ public class ChunkHolder { - this.setTicketLevel(level); - this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; - this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper + long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); + this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); + this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); + // Paper start - optimise checkDespawn + LevelChunk chunk = this.getFullChunkUnchecked(); + if (chunk != null) { @@ -23,53 +23,57 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - optimise checkDespawn } + // Paper end - optimise isOutsideOfRange - // CraftBukkit start diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper - // Paper start - distance maps - private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + int viewDistance; + public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper + + // Paper start - optimise checkDespawn + public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40; + public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE = 16.0 * (GENERAL_AREA_MAP_SQUARE_RADIUS - 1); + public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE_SQUARED = GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE * GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE; + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap; + // Paper end - optimise checkDespawn - - void addPlayerToDistanceMaps(ServerPlayer player) { - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -+ // Paper start - optimise checkDespawn -+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); -+ // Paper end - optimise checkDespawn ++ + // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() + public final CallbackExecutor callbackExecutor = new CallbackExecutor(); + public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); + this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn } void removePlayerFromDistanceMaps(ServerPlayer player) { - -+ // Paper start - optimise checkDespawn -+ this.playerGeneralAreaMap.remove(player); -+ // Paper end - optimise checkDespawn +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ this.playerGeneralAreaMap.remove(player); // Paper - optimise checkDespawns } void updateMaps(ServerPlayer player) { - int chunkX = MCUtil.getChunkCoordinate(player.getX()); - int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); - // Note: players need to be explicitly added to distance maps before they can be updated -+ // Paper start - optimise checkDespawn -+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); -+ // Paper end - optimise checkDespawn +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); + } +- // Paper end - use distance map to optimise entity tracker ++ // Paper end - use distance map to optimise entity trackerD + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise PlayerChunkMap#isOutsideRange ++ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); // Paper - optimise checkDespawn } // Paper end // Paper start @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.regionManagers.add(this.dataRegionManager); - // Paper end - this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper + } + }); + // Paper end - optimise PlayerChunkMap#isOutsideRange + // Paper start - optimise checkDespawn + this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, diff --git a/patches/server/Optimize-ServerLevels-chunk-level-checking-methods.patch b/patches/server/Optimize-ServerLevels-chunk-level-checking-methods.patch new file mode 100644 index 0000000000..b1c953ff12 --- /dev/null +++ b/patches/server/Optimize-ServerLevels-chunk-level-checking-methods.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Thu, 16 Apr 2020 16:13:59 -0700 +Subject: [PATCH] Optimize ServerLevels chunk level checking methods + +These can be hot functions (i.e entity ticking and block ticking), +so inline where possible, and avoid the abstraction of the +Either class. + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel { + } + + private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) { +- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos); ++ // Paper start - optimize is ticking ready type functions ++ ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos); ++ return chunkHolder != null && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); ++ // Paper end + } + + public boolean isPositionEntityTicking(BlockPos pos) { +- return this.entityManager.isPositionTicking(pos); ++ return this.entityManager.isPositionTicking(ChunkPos.asLong(pos)); // Paper + } + + public boolean isPositionEntityTicking(ChunkPos pos) { +- return this.entityManager.isPositionTicking(pos); ++ return this.entityManager.isPositionTicking(pos.toLong()); // Paper + } + + private final class EntityCallbacks implements LevelCallback { +diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/ChunkPos.java ++++ b/src/main/java/net/minecraft/world/level/ChunkPos.java +@@ -0,0 +0,0 @@ public class ChunkPos { + } + + public static long asLong(BlockPos pos) { +- return asLong(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); ++ return (((long)pos.getX() >> 4) & 4294967295L) | ((((long)pos.getZ() >> 4) & 4294967295L) << 32); // Paper - inline + } + + public static int getX(long pos) { +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java ++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java +@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager implements A + public LevelEntityGetter getEntityGetter() { + return this.entityGetter; + } ++ // Paper start ++ public final boolean isPositionTicking(long position) { ++ return this.chunkVisibility.get(position).isTicking(); ++ } ++ // Paper end + + public boolean isPositionTicking(BlockPos pos) { + return ((Visibility) this.chunkVisibility.get(ChunkPos.asLong(pos))).isTicking(); diff --git a/patches/server/Optimize-isOutsideRange-to-use-distance-maps.patch b/patches/server/Optimize-isOutsideRange-to-use-distance-maps.patch new file mode 100644 index 0000000000..07e3c7e5b4 --- /dev/null +++ b/patches/server/Optimize-isOutsideRange-to-use-distance-maps.patch @@ -0,0 +1,318 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Tue, 5 May 2020 20:40:53 -0700 +Subject: [PATCH] Optimize isOutsideRange to use distance maps + +Use a distance map to find the players in range quickly + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -0,0 +0,0 @@ public class ChunkHolder { + boolean isUpdateQueued = false; // Paper + private final ChunkMap chunkMap; // Paper + ++ // Paper start - optimise isOutsideOfRange ++ // cached here to avoid a map lookup ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange; ++ ++ void updateRanges() { ++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); ++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); ++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ } ++ // Paper end - optimise isOutsideOfRange ++ + public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) { + this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size()); + this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; +@@ -0,0 +0,0 @@ public class ChunkHolder { + this.setTicketLevel(level); + this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; + this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper ++ this.updateRanges(); // Paper - optimise isOutsideOfRange + } + + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ // A note about the naming used here: ++ // Previously, mojang used a "spawn range" of 8 for controlling both ticking and ++ // mob spawn range. However, spigot makes the spawn range configurable by ++ // checking if the chunk is in the tick range (8) and the spawn range ++ // obviously this means a spawn range > 8 cannot be implemented ++ ++ // these maps are named after spigot's uses ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); ++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void removePlayerFromDistanceMaps(ServerPlayer player) { +- ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerMobSpawnMap.remove(player); ++ this.playerChunkTickRangeMap.remove(player); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + void updateMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise PlayerChunkMap#isOutsideRange + } + // Paper end + // Paper start +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.regionManagers.add(this.dataRegionManager); + // Paper end + this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper ++ // Paper start - optimise PlayerChunkMap#isOutsideRange ++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }); ++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }); ++ // Paper end - optimise PlayerChunkMap#isOutsideRange + } + + protected ChunkGenerator generator() { +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } else { + if (holder != null) { + holder.setTicketLevel(level); ++ holder.updateRanges(); // Paper - optimise isOutsideOfRange + } + + if (holder != null) { +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return this.anyPlayerCloseEnoughForSpawning(pos, false); + } + +- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = (chunkRange > 8) ? 8 : chunkRange; +- +- final int finalChunkRange = chunkRange; // Paper for lambda below +- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event +- double blockRange = 16384.0D; // Paper +- // Spigot end +- long i = chunkcoordintpair.toLong(); ++ // Paper start - optimise isOutsideOfRange ++ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { ++ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); ++ } + +- if (!this.distanceManager.hasPlayersNearby(i)) { ++ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { ++ // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance ++ // tested and confirmed via System.nanoTime() ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; ++ if (playersInRange == null) { + return false; +- } else { +- Iterator iterator = this.playerMap.getPlayers(i).iterator(); +- +- ServerPlayer entityplayer; ++ } ++ Object[] backingSet = playersInRange.getBackingSet(); + +- do { +- if (!iterator.hasNext()) { +- return false; ++ if (reducedRange) { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer player)) { ++ continue; + } +- +- entityplayer = (ServerPlayer) iterator.next(); +- // Paper start - add PlayerNaturallySpawnCreaturesEvent +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- blockRange = 16384.0D; +- if (reducedRange) { +- event = entityplayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) return false; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (this.playerIsCloseEnoughForSpawning(player, chunkcoordintpair, player.lastEntitySpawnRadiusSquared)) { ++ return true; // in range + } +- // Paper end +- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot +- +- return true; ++ } ++ } else { ++ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.MOB_SPAWN_RANGE * 16); ++ // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer player)) { ++ continue; ++ } ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (this.playerIsCloseEnoughForSpawning(player, chunkcoordintpair, range)) { ++ return true; // in range ++ } ++ } + } ++ // no players in range ++ return false; ++ // Paper end - optimise isOutsideOfRange + } + + public List getPlayersCloseForSpawning(ChunkPos pos) { +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); + public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); + private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); +- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); ++ public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used + private final TickingTracker tickingTicketsTracker = new TickingTracker(); + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); + // Paper start use a queue, but still keep unique requirement +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); + + public boolean runAllUpdates(ChunkMap chunkStorage) { +- this.naturalSpawnChunkCounter.runAllUpdates(); ++ //this.f.a(); // Paper - no longer used + this.tickingTicketsTracker.runAllUpdates(); + this.playerTicketManager.runAllUpdates(); + int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + ((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> { + return new ObjectOpenHashSet(); + })).add(player); +- this.naturalSpawnChunkCounter.update(i, 0, true); ++ //this.f.update(i, 0, true); // Paper - no longer used + this.playerTicketManager.update(i, 0, true); + this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); + } +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); +- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); ++ //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + this.playerTicketManager.update(i, Integer.MAX_VALUE, false); + this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); + } +@@ -0,0 +0,0 @@ public abstract class DistanceManager { + } + + public int getNaturalSpawnChunkCount() { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.size(); ++ // Paper start - use distance map to implement ++ // note: this is the spawn chunk count ++ return this.chunkMap.playerChunkTickRangeMap.size(); ++ // Paper end - use distance map to implement + } + + public boolean hasPlayersNearby(long chunkPos) { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); ++ // Paper start - use distance map to implement ++ // note: this is the is spawn chunk method ++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null; ++ // Paper end - use distance map to implement + } + + public String getDebugStatus() { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { + boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + + //Collections.shuffle(list); // Paper - no... just no... +- // Paper start - call player naturally spawn event +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (ServerPlayer entityPlayer : this.level.players()) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); +- }; +- // Paper end ++ // Paper - moved natural spawn event up + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { +@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { + LevelChunk chunk1 = chunkproviderserver_a.chunk; + ChunkPos chunkcoordintpair = chunk1.getPos(); + +- if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { ++ if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange + chunk1.incrementInhabitedTime(j); +- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && !this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot ++ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && !this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { + // CraftBukkit end + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + ++ public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) { diff --git a/patches/server/Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/Prevent-excessive-velocity-through-repeated-crits.patch new file mode 100644 index 0000000000..4cb38682d6 --- /dev/null +++ b/patches/server/Prevent-excessive-velocity-through-repeated-crits.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Thu, 25 Nov 2021 10:25:09 +0100 +Subject: [PATCH] Prevent excessive velocity through repeated crits + + +diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java ++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java +@@ -0,0 +0,0 @@ public abstract class LivingEntity extends Entity { + return this.hasEffect(MobEffects.JUMP) ? (double) (0.1F * (float) (this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; + } + ++ protected long lastJumpTime = 0L; // Paper + protected void jumpFromGround() { + double d0 = (double) this.getJumpPower() + this.getJumpBoostPower(); + Vec3 vec3d = this.getDeltaMovement(); ++ // Paper start ++ long time = System.nanoTime(); ++ boolean canCrit = true; ++ if (this instanceof net.minecraft.world.entity.player.Player) { ++ canCrit = false; ++ if (time - this.lastJumpTime > (long)(0.250e9)) { ++ this.lastJumpTime = time; ++ canCrit = true; ++ } ++ } ++ // Paper end + + this.setDeltaMovement(vec3d.x, d0, vec3d.z); + if (this.isSprinting()) { + float f = this.getYRot() * 0.017453292F; + ++ if (canCrit) // Paper + this.setDeltaMovement(this.getDeltaMovement().add((double) (-Mth.sin(f) * 0.2F), 0.0D, (double) (Mth.cos(f) * 0.2F))); + } + diff --git a/patches/server/Use-distance-map-to-optimise-entity-tracker.patch b/patches/server/Use-distance-map-to-optimise-entity-tracker.patch new file mode 100644 index 0000000000..1c236ce9dc --- /dev/null +++ b/patches/server/Use-distance-map-to-optimise-entity-tracker.patch @@ -0,0 +1,403 @@ +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/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -0,0 +0,0 @@ import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; + import net.minecraft.network.protocol.game.ClientboundSetPassengersPacket; + import net.minecraft.network.protocol.game.DebugPackets; + import net.minecraft.server.MCUtil; ++import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.progress.ChunkProgressListener; + import net.minecraft.server.network.ServerPlayerConnection; + import net.minecraft.util.CsvOutput; +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick + public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; + // Paper end - optimise PlayerChunkMap#isOutsideRange ++ // 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(); ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; ++ final int[] entityTrackerTrackRanges; ++ public final int getEntityTrackerRange(final int ordinal) { ++ return this.entityTrackerTrackRanges[ordinal]; ++ } ++ ++ private int convertSpigotRangeToVanilla(final int vanilla) { ++ return MinecraftServer.getServer().getScaledTrackingDistance(vanilla); ++ } ++ // Paper end - use distance map to optimise tracker + + void addPlayerToDistanceMaps(ServerPlayer player) { + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); ++ // 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 + // Note: players need to be explicitly added to distance maps before they can be updated + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + void removePlayerFromDistanceMaps(ServerPlayer 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 + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerMobSpawnMap.remove(player); + this.playerChunkTickRangeMap.remove(player); +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // 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 + this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise PlayerChunkMap#isOutsideRange + } + // Paper end +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.regionManagers.add(this.dataRegionManager); + // Paper end + this.playerMobDistanceMap = this.level.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.level.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 = EntityType.ENDER_DRAGON.clientTrackingRange() * 16; ++ 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 + // Paper start - optimise PlayerChunkMap#isOutsideRange + this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, + (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + public void move(ServerPlayer player) { +- ObjectIterator objectiterator = this.entityMap.values().iterator(); +- +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- +- if (playerchunkmap_entitytracker.entity == player) { +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); +- } else { +- playerchunkmap_entitytracker.updatePlayer(player); +- } +- } ++ // Paper - delay this logic for the entity tracker tick, no need to duplicate it + + int i = SectionPos.blockToSectionCoord(player.getBlockX()); + int j = SectionPos.blockToSectionCoord(player.getBlockZ()); +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker + this.entityMap.put(entity.getId(), playerchunkmap_entitytracker); +- playerchunkmap_entitytracker.updatePlayers(this.level.players()); ++ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer = (ServerPlayer) entity; + +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + entity.tracker = null; // Paper - We're no longer tracked + } + ++ // Paper start - optimised tracker ++ private final void processTrackQueue() { ++ this.level.timings.tracker1.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ // update tracker entry ++ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); ++ } ++ } finally { ++ this.level.timings.tracker1.stopTiming(); ++ } ++ ++ ++ this.level.timings.tracker2.startTiming(); ++ try { ++ for (TrackedEntity tracker : this.entityMap.values()) { ++ tracker.serverEntity.sendChanges(); ++ } ++ } finally { ++ this.level.timings.tracker2.stopTiming(); ++ } ++ } ++ // Paper end - optimised tracker ++ + protected void tick() { ++ // Paper start - optimized tracker ++ if (true) { ++ this.processTrackQueue(); ++ return; ++ } ++ // Paper end - optimized tracker + List list = Lists.newArrayList(); + List list1 = this.level.players(); + ObjectIterator objectiterator = this.entityMap.values().iterator(); +@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos()); + List list = Lists.newArrayList(); + List list1 = Lists.newArrayList(); +- ObjectIterator objectiterator = this.entityMap.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 == player) { ++ continue; ++ } ++ ChunkMap.TrackedEntity tracker = this.entityMap.get(entity.getId()); ++ if (tracker != null) { // dumb plugins... move on... ++ tracker.updatePlayer(player); ++ } + +- while (objectiterator.hasNext()) { +- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); +- Entity entity = playerchunkmap_entitytracker.entity; ++ // 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 != player && entity.chunkPosition().equals(chunk.getPos())) { +- playerchunkmap_entitytracker.updatePlayer(player); +- if (entity instanceof Mob && ((Mob) entity).getLeashHolder() != null) { +- list.add(entity); +- } ++ if (entity instanceof Mob && ((Mob)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 ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.lastSectionPos = SectionPos.of(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 ServerPlayer)) { ++ continue; ++ } ++ ServerPlayer player = (ServerPlayer)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 (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME ++ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) { ++ this.updatePlayer(conn.getPlayer()); ++ } ++ } ++ } ++ // Paper end - use distance map to optimise tracker ++ + public boolean equals(Object object) { + return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false; + } +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -0,0 +0,0 @@ import net.minecraft.network.syncher.EntityDataSerializers; + import net.minecraft.network.syncher.SynchedEntityData; + import net.minecraft.resources.ResourceKey; + import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.MCUtil; + import net.minecraft.server.MinecraftServer; + import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; +@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + } + // Paper end + ++ // Paper start - optimise entity tracking ++ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this); ++ ++ public boolean isLegacyTrackingEntity = false; ++ ++ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) { ++ this.isLegacyTrackingEntity = isLegacyTrackingEntity; ++ } ++ ++ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { ++ // determine highest range of passengers ++ if (this.passengers.isEmpty()) { ++ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] ++ .getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ Iterable passengers = this.getIndirectPassengers(); ++ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; ++ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; ++ int range = chunkMap.getEntityTrackerRange(type.ordinal()); ++ ++ for (Entity passenger : passengers) { ++ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; ++ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); ++ if (passengerRange > range) { ++ type = passengerType; ++ range = passengerRange; ++ } ++ } ++ ++ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); ++ } ++ // Paper end - optimise entity tracking ++ + public Entity(EntityType type, Level world) { + this.id = Entity.ENTITY_COUNTER.incrementAndGet(); + this.passengers = ImmutableList.of(); +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 @@ import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.decoration.ItemFrame; + import net.minecraft.world.entity.decoration.Painting; + import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.entity.monster.Ghast; + + public class TrackingRange + { +@@ -0,0 +0,0 @@ public class TrackingRange + { + return defaultRange; + } ++ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return defaultRange; // Paper - enderdragon is exempt + SpigotWorldConfig config = entity.level.spigotConfig; + if ( entity instanceof ServerPlayer ) + { +@@ -0,0 +0,0 @@ public class TrackingRange + return config.miscTrackingRange; + } else + { +- if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return ((net.minecraft.server.level.ServerLevel)(entity.getCommandSenderWorld())).getChunkSource().chunkMap.getEffectiveViewDistance(); // 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 net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt ++ if ( entity instanceof ServerPlayer ) ++ { ++ 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 ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb ) ++ // Paper end ++ { ++ return TrackingRangeType.MISC; ++ } else ++ { ++ return TrackingRangeType.OTHER; ++ } ++ } ++ ++ public static enum TrackingRangeType { ++ PLAYER, ++ ANIMAL, ++ MONSTER, ++ MISC, ++ OTHER, ++ ENDERDRAGON; ++ } ++ // Paper end - optimise entity tracking + }