Files
paper-mc/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch
Spottedleaf 24cd24c8cc Optimise CraftWorld#getLoadedChunks
We can use the existing full chunk map so that we do not need
to iterate over all ChunkHolders. Additionally, do not use streams
to do the iteration either.
2025-06-05 08:16:25 -07:00

225 lines
10 KiB
Diff

--- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/net/minecraft/server/level/ServerChunkCache.java
@@ -77,6 +_,13 @@
@Nullable
@VisibleForDebug
private NaturalSpawner.SpawnState lastSpawnState;
+ // Paper start
+ public final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<net.minecraft.world.level.chunk.LevelChunk> fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>();
+ public int getFullChunksCount() {
+ return this.fullChunks.size();
+ }
+ long chunkFutureAwaitCounter;
+ // Paper end
public ServerChunkCache(
ServerLevel level,
@@ -127,6 +_,64 @@
this.clearCache();
}
+ // CraftBukkit start - properly implement isChunkLoaded
+ public boolean isChunkLoaded(int chunkX, int chunkZ) {
+ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(ChunkPos.asLong(chunkX, chunkZ));
+ if (chunk == null) {
+ return false;
+ }
+ return chunk.getFullChunkNow() != null;
+ }
+ // CraftBukkit end
+ // Paper start
+ public void addLoadedChunk(LevelChunk chunk) {
+ this.fullChunks.put(chunk.coordinateKey, chunk);
+ }
+
+ public void removeLoadedChunk(LevelChunk chunk) {
+ this.fullChunks.remove(chunk.coordinateKey);
+ }
+
+ @Nullable
+ public ChunkAccess getChunkAtImmediately(int x, int z) {
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
+ if (holder == null) {
+ return null;
+ }
+
+ return holder.getLatestChunk();
+ }
+
+ public void addTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel) {
+ this.ticketStorage.addTicket(new Ticket(ticketType, ticketLevel), chunkPos);
+ }
+
+ public void removeTicketAtLevel(TicketType ticketType, ChunkPos chunkPos, int ticketLevel) {
+ this.ticketStorage.removeTicket(new Ticket(ticketType, ticketLevel), chunkPos);
+ }
+
+ // "real" get chunk if loaded
+ // Note: Partially copied from the getChunkAt method below
+ @Nullable
+ public LevelChunk getChunkAtIfCachedImmediately(int x, int z) {
+ long k = ChunkPos.asLong(x, z);
+
+ // Note: Bypass cache since we need to check ticket level, and to make this MT-Safe
+
+ ChunkHolder playerChunk = this.getVisibleChunkIfPresent(k);
+ if (playerChunk == null) {
+ return null;
+ }
+
+ return playerChunk.getFullChunkNowUnchecked();
+ }
+
+ @Nullable
+ public LevelChunk getChunkAtIfLoadedImmediately(int x, int z) {
+ return this.fullChunks.get(ChunkPos.asLong(x, z));
+ }
+ // Paper end
+
@Override
public ThreadedLevelLightEngine getLightEngine() {
return this.lightEngine;
@@ -166,7 +_,7 @@
for (int i = 0; i < 4; i++) {
if (packedChunkPos == this.lastChunkPos[i] && chunkStatus == this.lastChunkStatus[i]) {
ChunkAccess chunkAccess = this.lastChunk[i];
- if (chunkAccess != null || !requireChunk) {
+ if (chunkAccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
return chunkAccess;
}
}
@@ -175,6 +_,7 @@
profilerFiller.incrementCounter("getChunkCacheMiss");
CompletableFuture<ChunkResult<ChunkAccess>> chunkFutureMainThread = this.getChunkFutureMainThread(x, z, chunkStatus, requireChunk);
this.mainThreadProcessor.managedBlock(chunkFutureMainThread::isDone);
+ // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads
ChunkResult<ChunkAccess> chunkResult = chunkFutureMainThread.join();
ChunkAccess chunkAccess1 = chunkResult.orElse(null);
if (chunkAccess1 == null && requireChunk) {
@@ -246,7 +_,15 @@
long packedChunkPos = chunkPos.toLong();
int i = ChunkLevel.byStatus(chunkStatus);
ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos);
- if (requireChunk) {
+ // CraftBukkit start - don't add new ticket for currently unloading chunk
+ boolean currentlyUnloading = false;
+ if (visibleChunkIfPresent != null) {
+ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.oldTicketLevel);
+ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.getTicketLevel());
+ currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
+ }
+ if (requireChunk && !currentlyUnloading) {
+ // CraftBukkit end
this.addTicket(new Ticket(TicketType.UNKNOWN, i), chunkPos);
if (this.chunkAbsent(visibleChunkIfPresent, i)) {
ProfilerFiller profilerFiller = Profiler.get();
@@ -266,7 +_,7 @@
}
private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int status) {
- return chunkHolder == null || chunkHolder.getTicketLevel() > status;
+ return chunkHolder == null || chunkHolder.oldTicketLevel > status; // CraftBukkit using oldTicketLevel for isLoaded checks
}
@Override
@@ -321,17 +_,39 @@
@Override
public void close() throws IOException {
- this.save(true);
+ // CraftBukkit start
+ this.close(true);
+ }
+
+ public void close(boolean save) throws IOException {
+ if (save) {
+ this.save(true);
+ }
+ // CraftBukkit end
this.dataStorage.close();
this.lightEngine.close();
this.chunkMap.close();
}
+ // CraftBukkit start - modelled on below
+ public void purgeUnload() {
+ ProfilerFiller gameprofilerfiller = Profiler.get();
+
+ gameprofilerfiller.push("purge");
+ this.ticketStorage.purgeStaleTickets(this.chunkMap);
+ this.runDistanceManagerUpdates();
+ gameprofilerfiller.popPush("unload");
+ this.chunkMap.tick(() -> true);
+ gameprofilerfiller.pop();
+ this.clearCache();
+ }
+ // CraftBukkit end
+
@Override
public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("purge");
- if (this.level.tickRateManager().runsNormally() || !tickChunks) {
+ if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot
this.ticketStorage.purgeStaleTickets(this.chunkMap);
}
@@ -388,12 +_,20 @@
);
this.lastSpawnState = spawnState;
profiler.popPush("spawnAndTick");
- boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
+ boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
List<MobCategory> filteredSpawningCategories;
if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) {
- boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L;
- filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag);
+ // Paper start - PlayerNaturallySpawnCreaturesEvent
+ for (ServerPlayer entityPlayer : this.level.players()) {
+ int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance());
+ chunkRange = Math.min(chunkRange, 8);
+ entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
+ entityPlayer.playerNaturallySpawnedEvent.callEvent();
+ }
+ // Paper end - PlayerNaturallySpawnCreaturesEvent
+ boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
+ filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit
} else {
filteredSpawningCategories = List.of();
}
@@ -547,8 +_,13 @@
@Override
public void setSpawnSettings(boolean spawnSettings) {
+ // CraftBukkit start
+ this.setSpawnSettings(spawnSettings, this.spawnFriendlies);
+ }
+ public void setSpawnSettings(boolean spawnSettings, boolean spawnFriendlies) {
this.spawnEnemies = spawnSettings;
- this.spawnFriendlies = this.spawnFriendlies;
+ this.spawnFriendlies = spawnFriendlies;
+ // CraftBukkit end
}
public String getChunkDebugData(ChunkPos chunkPos) {
@@ -621,12 +_,18 @@
@Override
public boolean pollTask() {
+ try { // CraftBukkit - process pending Chunk loadCallback() and unloadCallback() after each run task
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
return true;
} else {
ServerChunkCache.this.lightEngine.tryScheduleUpdate();
return super.pollTask();
}
+ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
+ } finally {
+ ServerChunkCache.this.chunkMap.callbackExecutor.run();
+ }
+ // CraftBukkit end - process pending Chunk loadCallback() and unloadCallback() after each run task
}
}
}