--- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -145,6 +_,33 @@ public int serverViewDistance; private final WorldGenContext worldGenContext; + // 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 { + + private final java.util.Queue queue = new java.util.ArrayDeque<>(); + + @Override + public void execute(Runnable runnable) { + this.queue.add(runnable); + } + + @Override + public void run() { + Runnable task; + while ((task = this.queue.poll()) != null) { + task.run(); + } + } + }; + // CraftBukkit end + + // Paper start + public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { + return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); + } + // Paper end + public ChunkMap( ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, @@ -171,13 +_,19 @@ this.level = level; RegistryAccess registryAccess = level.registryAccess(); long seed = level.getSeed(); - if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { + // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random. + ChunkGenerator randomGenerator = generator; + if (randomGenerator instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator customChunkGenerator) { + randomGenerator = customChunkGenerator.getDelegate(); + } + if (randomGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { + // CraftBukkit end this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), seed); } else { this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), seed); } - this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, seed); + this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, seed, level.spigotConfig); // Spigot this.mainThreadExecutor = mainThreadExecutor; ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen"); this.progressListener = progressListener; @@ -207,6 +_,12 @@ this.chunksToEagerlySave.add(chunkPos.toLong()); } + // Paper start + public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { + return -1; + } + // Paper end + protected ChunkGenerator generator() { return this.worldGenContext.generator(); } @@ -354,9 +_,9 @@ } ); stringBuilder.append("Updating:").append(System.lineSeparator()); - this.updatingChunkMap.values().forEach(consumer); + ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper stringBuilder.append("Visible:").append(System.lineSeparator()); - this.visibleChunkMap.values().forEach(consumer); + ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading"); CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading"); crashReportCategory.setDetail("Details", details); @@ -392,6 +_,9 @@ holder.setTicketLevel(newLevel); } else { holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this); + // Paper start + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder); + // Paper end } this.updatingChunkMap.put(chunkPos, holder); @@ -420,8 +_,8 @@ protected void saveAllChunks(boolean flush) { if (flush) { - List list = this.visibleChunkMap - .values() + List list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level) // Paper - moonrise + //.values() // Paper - moonrise .stream() .filter(ChunkHolder::wasAccessibleSinceLastSave) .peek(ChunkHolder::refreshAccessibility) @@ -447,7 +_,7 @@ this.nextChunkSaveTime.clear(); long millis = Util.getMillis(); - for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) { + for (ChunkHolder chunkHolder : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper this.saveChunkIfNeeded(chunkHolder, millis); } } @@ -468,6 +_,7 @@ public boolean hasWork() { return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() + || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) // Paper - moonrise || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() @@ -526,7 +_,11 @@ this.scheduleUnload(chunkPos, chunkHolder); } else { ChunkAccess latestChunk = chunkHolder.getLatestChunk(); - if (this.pendingUnloads.remove(chunkPos, chunkHolder) && latestChunk != null) { + // Paper start + boolean removed; + if ((removed = this.pendingUnloads.remove(chunkPos, chunkHolder)) && latestChunk != null) { + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder); + // Paper end if (latestChunk instanceof LevelChunk levelChunk) { levelChunk.setLoaded(false); } @@ -540,7 +_,9 @@ this.lightEngine.tryScheduleUpdate(); this.progressListener.onStatusChange(latestChunk.getPos(), null); this.nextChunkSaveTime.remove(latestChunk.getPos().toLong()); - } + } else if (removed) { // Paper start + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder); + } // Paper end } }, this.unloadQueue::add).whenComplete((_void, error) -> { if (error != null) { @@ -818,7 +_,7 @@ } } - protected void setServerViewDistance(int viewDistance) { + public void setServerViewDistance(int viewDistance) { // Paper - publi int i = Mth.clamp(viewDistance, 2, 32); if (i != this.serverViewDistance) { this.serverViewDistance = i; @@ -856,7 +_,7 @@ } public int size() { - return this.visibleChunkMap.size(); + return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper } public net.minecraft.server.level.DistanceManager getDistanceManager() { @@ -864,7 +_,7 @@ } protected Iterable getChunks() { - return Iterables.unmodifiableIterable(this.visibleChunkMap.values()); + return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper } void dumpChunks(Writer writer) throws IOException { @@ -888,10 +_,10 @@ .build(writer); TickingTracker tickingTracker = this.distanceManager.tickingTracker(); - for (Entry entry : this.visibleChunkMap.long2ObjectEntrySet()) { - long longKey = entry.getLongKey(); + for (ChunkHolder entry : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper - Moonrise + long longKey = entry.pos.toLong(); // Paper - Moonrise ChunkPos chunkPos = new ChunkPos(longKey); - ChunkHolder chunkHolder = entry.getValue(); + ChunkHolder chunkHolder = entry; // Paper - Moonrise Optional optional = Optional.ofNullable(chunkHolder.getLatestChunk()); Optional optional1 = optional.flatMap(chunk -> chunk instanceof LevelChunk ? Optional.of((LevelChunk)chunk) : Optional.empty()); csvOutput.writeRow( @@ -931,11 +_,13 @@ } private CompletableFuture> readChunk(ChunkPos pos) { - return this.read(pos).thenApplyAsync(optional -> optional.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk")); + return this.read(pos).thenApplyAsync(optional -> optional.map(tag -> upgradeChunkTag(tag, pos)), Util.backgroundExecutor().forName("upgradeChunk")); // CraftBukkit } - private CompoundTag upgradeChunkTag(CompoundTag tag) { - return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer()); + // CraftBukkit start + private CompoundTag upgradeChunkTag(CompoundTag tag, ChunkPos pos) { + return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer(), pos, this.level); + // CraftBukkit end } void forEachSpawnCandidateChunk(Consumer action) { @@ -951,12 +_,34 @@ } public boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos) { - return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos); + // Spigot start + return this.anyPlayerCloseEnoughForSpawning(chunkPos, false); + } + + boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos, boolean reducedRange) { + return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, reducedRange); + // Spigot end } private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos) { + // Spigot start + return this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, false); + } + + private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos, boolean reducedRange) { + double blockRange; // Paper - use from event + // Spigot end for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { - if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) { + // Paper start - PlayerNaturallySpawnCreaturesEvent + com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; + blockRange = 16384.0D; + if (reducedRange) { + event = serverPlayer.playerNaturallySpawnedEvent; + if (event == null || event.isCancelled()) continue; + blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); + } + // Paper end - PlayerNaturallySpawnCreaturesEvent + if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, blockRange)) { return true; } } @@ -972,7 +_,7 @@ Builder builder = ImmutableList.builder(); for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { - if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) { + if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, 16384.0D)) { // Spigot builder.add(serverPlayer); } } @@ -981,12 +_,12 @@ } } - private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos) { + private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos, double range) { // Spigot if (player.isSpectator()) { return false; } else { double d = euclideanDistanceSquared(chunkPos, player); - return d < 16384.0; + return d < range; // Spigot } } @@ -1100,9 +_,19 @@ } public void addEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot + // Paper start - ignore and warn about illegal addEntity calls instead of crashing server + if (!entity.valid || entity.level() != this.level || this.entityMap.containsKey(entity.getId())) { + LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() + + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); + return; + } + // Paper end - ignore and warn about illegal addEntity calls instead of crashing server + if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delayadding to tracker until after list packets if (!(entity instanceof EnderDragonPart)) { EntityType type = entity.getType(); int i = type.clientTrackingRange() * 16; + i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot if (i != 0) { int updateInterval = type.updateInterval(); if (this.entityMap.containsKey(entity.getId())) { @@ -1126,6 +_,7 @@ } protected void removeEntity(Entity entity) { + org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot if (entity instanceof ServerPlayer serverPlayer) { this.updatePlayerStatus(serverPlayer, false); @@ -1230,7 +_,7 @@ }); } - class DistanceManager extends net.minecraft.server.level.DistanceManager { + public class DistanceManager extends net.minecraft.server.level.DistanceManager { // Paper - public protected DistanceManager(final Executor dispatcher, final Executor mainThreadExecutor) { super(dispatcher, mainThreadExecutor); } @@ -1258,10 +_,10 @@ final Entity entity; private final int range; SectionPos lastSectionPos; - public final Set seenBy = Sets.newIdentityHashSet(); + public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) { - this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast); + this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this.seenBy); // CraftBukkit this.entity = entity; this.range = range; this.lastSectionPos = SectionPos.of(entity); @@ -1297,24 +_,47 @@ } public void removePlayer(ServerPlayer player) { + org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); } } public void updatePlayer(ServerPlayer player) { + org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot if (player != this.entity) { - Vec3 vec3 = player.position().subtract(this.entity.position()); + // Paper start - remove allocation of Vec3D here + // Vec3 vec3d = player.position().subtract(this.entity.position()); + double vec3d_dx = player.getX() - this.entity.getX(); + double vec3d_dz = player.getZ() - this.entity.getZ(); + // Paper end - remove allocation of Vec3D here int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player); double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16); - double d1 = vec3.x * vec3.x + vec3.z * vec3.z; + double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper double d2 = d * d; - boolean flag = d1 <= d2 - && this.entity.broadcastToPlayer(player) - && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); + // Paper start - Configurable entity tracking range by Y + boolean flag = d1 <= d2; + if (flag && level.paperConfig().entities.trackingRangeY.enabled) { + double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); + if (rangeY != -1) { + double vec3d_dy = player.getY() - this.entity.getY(); + flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; + } + } + flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); + // Paper end - Configurable entity tracking range by Y + // CraftBukkit start - respect vanish API + if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits + flag = false; + } + // CraftBukkit end if (flag) { if (this.seenBy.add(player.connection)) { + // Paper start - entity tracking events + if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { this.serverEntity.addPairing(player); + } + // Paper end - entity tracking events } } else if (this.seenBy.remove(player.connection)) { this.serverEntity.removePairing(player); @@ -1331,6 +_,7 @@ for (Entity entity : this.entity.getIndirectPassengers()) { int i1 = entity.getType().clientTrackingRange() * 16; + i1 = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i1); // Paper if (i1 > i) { i = i1; }