mirror of
https://github.com/PaperMC/Paper.git
synced 2025-08-15 20:23:53 -07:00
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.
225 lines
10 KiB
Diff
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
|
|
}
|
|
}
|
|
}
|