aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch
diff options
context:
space:
mode:
authorMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
committerMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
commitbee74680e607c2e29b038329f62181238911cd83 (patch)
tree708fd1a4a0227d9071243adf2a42d5e9e96cde4a /patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch
parent0a44692ef6ff6e255d48eb3ba1bb114166eafda9 (diff)
downloadPaper-softspoon.tar.gz
Paper-softspoon.zip
add remapped patches as a testsoftspoon
Diffstat (limited to 'patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch')
-rw-r--r--patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch644
1 files changed, 644 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch
new file mode 100644
index 0000000000..1f70a0e9ed
--- /dev/null
+++ b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -0,0 +1,644 @@
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -7,10 +7,11 @@
+ import java.io.File;
+ import java.io.IOException;
+ import java.util.Arrays;
++import java.util.Iterator;
+ import java.util.List;
++import java.util.Objects;
+ import java.util.Optional;
+ import java.util.concurrent.CompletableFuture;
+-import java.util.concurrent.CompletionStage;
+ import java.util.concurrent.Executor;
+ import java.util.function.BooleanSupplier;
+ import java.util.function.Consumer;
+@@ -27,9 +28,9 @@
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.ai.village.poi.PoiManager;
+ import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.EnumSkyBlock;
+ import net.minecraft.world.level.GameRules;
+ import net.minecraft.world.level.Level;
+-import net.minecraft.world.level.LightLayer;
+ import net.minecraft.world.level.LocalMobCapCalculator;
+ import net.minecraft.world.level.NaturalSpawner;
+ import net.minecraft.world.level.chunk.ChunkAccess;
+@@ -47,6 +48,7 @@
+ import net.minecraft.world.level.storage.LevelStorageSource;
+
+ public class ServerChunkCache extends ChunkSource {
++
+ private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList();
+ private final DistanceManager distanceManager;
+ final ServerLevel level;
+@@ -56,8 +58,8 @@
+ public final ChunkMap chunkMap;
+ private final DimensionDataStorage dataStorage;
+ private long lastInhabitedUpdate;
+- private boolean spawnEnemies = true;
+- private boolean spawnFriendlies = true;
++ public boolean spawnEnemies = true;
++ public boolean spawnFriendlies = true;
+ private static final int CACHE_SIZE = 4;
+ private final long[] lastChunkPos = new long[4];
+ private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4];
+@@ -66,47 +68,31 @@
+ @VisibleForDebug
+ private NaturalSpawner.SpawnState lastSpawnState;
+
+- public ServerChunkCache(
+- ServerLevel level,
+- LevelStorageSource.LevelStorageAccess levelStorageAccess,
+- DataFixer fixerUpper,
+- StructureTemplateManager structureManager,
+- Executor dispatcher,
+- ChunkGenerator generator,
+- int viewDistance,
+- int simulationDistance,
+- boolean sync,
+- ChunkProgressListener progressListener,
+- ChunkStatusUpdateListener chunkStatusListener,
+- Supplier<DimensionDataStorage> overworldDataStorage
+- ) {
++ public ServerChunkCache(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, StructureTemplateManager structureManager, Executor dispatcher, ChunkGenerator generator, int viewDistance, int simulationDistance, boolean sync, ChunkProgressListener progressListener, ChunkStatusUpdateListener chunkStatusListener, Supplier<DimensionDataStorage> overworldDataStorage) {
+ this.level = level;
+ this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(level);
+ this.mainThread = Thread.currentThread();
+ File file = levelStorageAccess.getDimensionPath(level.dimension()).resolve("data").toFile();
++
+ file.mkdirs();
+ this.dataStorage = new DimensionDataStorage(file, fixerUpper);
+- this.chunkMap = new ChunkMap(
+- level,
+- levelStorageAccess,
+- fixerUpper,
+- structureManager,
+- dispatcher,
+- this.mainThreadProcessor,
+- this,
+- generator,
+- progressListener,
+- chunkStatusListener,
+- overworldDataStorage,
+- viewDistance,
+- sync
+- );
++ this.chunkMap = new ChunkMap(level, levelStorageAccess, fixerUpper, structureManager, dispatcher, this.mainThreadProcessor, this, generator, progressListener, chunkStatusListener, overworldDataStorage, viewDistance, sync);
+ this.lightEngine = this.chunkMap.getLightEngine();
+ this.distanceManager = this.chunkMap.getDistanceManager();
+ this.distanceManager.updateSimulationDistance(simulationDistance);
+ 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
++
+ @Override
+ public ThreadedLevelLightEngine getLightEngine() {
+ return this.lightEngine;
+@@ -121,51 +107,59 @@
+ return this.chunkMap.getTickingGenerated();
+ }
+
+- private void storeInCache(long chunkPos, ChunkAccess chunk, ChunkStatus chunkStatus) {
+- for (int i = 3; i > 0; i--) {
+- this.lastChunkPos[i] = this.lastChunkPos[i - 1];
+- this.lastChunkStatus[i] = this.lastChunkStatus[i - 1];
+- this.lastChunk[i] = this.lastChunk[i - 1];
++ private void storeInCache(long chunkPos, ChunkAccess ichunkaccess, ChunkStatus chunk) {
++ for (int j = 3; j > 0; --j) {
++ this.lastChunkPos[j] = this.lastChunkPos[j - 1];
++ this.lastChunkStatus[j] = this.lastChunkStatus[j - 1];
++ this.lastChunk[j] = this.lastChunk[j - 1];
+ }
+
+ this.lastChunkPos[0] = chunkPos;
+- this.lastChunkStatus[0] = chunkStatus;
+- this.lastChunk[0] = chunk;
++ this.lastChunkStatus[0] = chunk;
++ this.lastChunk[0] = ichunkaccess;
+ }
+
+ @Nullable
+ @Override
+ public ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus requiredStatus, boolean load) {
+ if (Thread.currentThread() != this.mainThread) {
+- return CompletableFuture.<ChunkAccess>supplyAsync(() -> this.getChunk(chunkX, chunkZ, requiredStatus, load), this.mainThreadProcessor).join();
++ return (ChunkAccess) CompletableFuture.supplyAsync(() -> {
++ return this.getChunk(chunkX, chunkZ, requiredStatus, load);
++ }, this.mainThreadProcessor).join();
+ } else {
+- ProfilerFiller profiler = this.level.getProfiler();
+- profiler.incrementCounter("getChunk");
+- long _long = ChunkPos.asLong(chunkX, chunkZ);
++ ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+- for (int i = 0; i < 4; i++) {
+- if (_long == this.lastChunkPos[i] && requiredStatus == this.lastChunkStatus[i]) {
+- ChunkAccess chunkAccess = this.lastChunk[i];
+- if (chunkAccess != null || !load) {
+- return chunkAccess;
++ gameprofilerfiller.incrementCounter("getChunk");
++ long k = ChunkPos.asLong(chunkX, chunkZ);
++
++ ChunkAccess ichunkaccess;
++
++ for (int l = 0; l < 4; ++l) {
++ if (k == this.lastChunkPos[l] && requiredStatus == this.lastChunkStatus[l]) {
++ ichunkaccess = this.lastChunk[l];
++ if (ichunkaccess != 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 ichunkaccess;
+ }
+ }
+ }
+
+- profiler.incrementCounter("getChunkCacheMiss");
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> chunkFutureMainThread = this.getChunkFutureMainThread(
+- chunkX, chunkZ, requiredStatus, load
+- );
+- this.mainThreadProcessor.managedBlock(chunkFutureMainThread::isDone);
+- ChunkAccess chunkAccess = chunkFutureMainThread.join().map(chunkAccess1 -> (ChunkAccess)chunkAccess1, chunkLoadingFailure -> {
++ gameprofilerfiller.incrementCounter("getChunkCacheMiss");
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = this.getChunkFutureMainThread(chunkX, chunkZ, requiredStatus, load);
++ ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
++
++ Objects.requireNonNull(completablefuture);
++ chunkproviderserver_b.managedBlock(completablefuture::isDone);
++ ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
++ return ichunkaccess1;
++ }, (playerchunk_failure) -> {
+ if (load) {
+- throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkLoadingFailure));
++ throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + playerchunk_failure));
+ } else {
+ return null;
+ }
+ });
+- this.storeInCache(_long, chunkAccess, requiredStatus);
+- return chunkAccess;
++ this.storeInCache(k, ichunkaccess, requiredStatus);
++ return ichunkaccess;
+ }
+ }
+
+@@ -176,28 +170,32 @@
+ return null;
+ } else {
+ this.level.getProfiler().incrementCounter("getChunkNow");
+- long _long = ChunkPos.asLong(chunkX, chunkZ);
++ long k = ChunkPos.asLong(chunkX, chunkZ);
+
+- for (int i = 0; i < 4; i++) {
+- if (_long == this.lastChunkPos[i] && this.lastChunkStatus[i] == ChunkStatus.FULL) {
+- ChunkAccess chunkAccess = this.lastChunk[i];
+- return chunkAccess instanceof LevelChunk ? (LevelChunk)chunkAccess : null;
++ for (int l = 0; l < 4; ++l) {
++ if (k == this.lastChunkPos[l] && this.lastChunkStatus[l] == ChunkStatus.FULL) {
++ ChunkAccess ichunkaccess = this.lastChunk[l];
++
++ return ichunkaccess instanceof LevelChunk ? (LevelChunk) ichunkaccess : null;
+ }
+ }
+
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(_long);
+- if (visibleChunkIfPresent == null) {
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
++
++ if (playerchunk == null) {
+ return null;
+ } else {
+- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = visibleChunkIfPresent.getFutureIfPresent(ChunkStatus.FULL).getNow(null);
++ Either<ChunkAccess, ChunkHolder.Failure> either = (Either) playerchunk.getFutureIfPresent(ChunkStatus.FULL).getNow(null); // CraftBukkit - decompile error
++
+ if (either == null) {
+ return null;
+ } else {
+- ChunkAccess chunkAccess1 = either.left().orElse(null);
+- if (chunkAccess1 != null) {
+- this.storeInCache(_long, chunkAccess1, ChunkStatus.FULL);
+- if (chunkAccess1 instanceof LevelChunk) {
+- return (LevelChunk)chunkAccess1;
++ ChunkAccess ichunkaccess1 = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error
++
++ if (ichunkaccess1 != null) {
++ this.storeInCache(k, ichunkaccess1, ChunkStatus.FULL);
++ if (ichunkaccess1 instanceof LevelChunk) {
++ return (LevelChunk) ichunkaccess1;
+ }
+ }
+
+@@ -209,85 +207,99 @@
+
+ private void clearCache() {
+ Arrays.fill(this.lastChunkPos, ChunkPos.INVALID_CHUNK_POS);
+- Arrays.fill(this.lastChunkStatus, null);
+- Arrays.fill(this.lastChunk, null);
++ Arrays.fill(this.lastChunkStatus, (Object) null);
++ Arrays.fill(this.lastChunk, (Object) null);
+ }
+
+- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFuture(int x, int y, ChunkStatus chunkStatus, boolean load) {
+- boolean flag = Thread.currentThread() == this.mainThread;
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> chunkFutureMainThread;
+- if (flag) {
+- chunkFutureMainThread = this.getChunkFutureMainThread(x, y, chunkStatus, load);
+- this.mainThreadProcessor.managedBlock(chunkFutureMainThread::isDone);
++ public CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getChunkFuture(int x, int y, ChunkStatus chunkStatus, boolean load) {
++ boolean flag1 = Thread.currentThread() == this.mainThread;
++ CompletableFuture completablefuture;
++
++ if (flag1) {
++ completablefuture = this.getChunkFutureMainThread(x, y, chunkStatus, load);
++ ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
++
++ Objects.requireNonNull(completablefuture);
++ chunkproviderserver_b.managedBlock(completablefuture::isDone);
+ } else {
+- chunkFutureMainThread = CompletableFuture.<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>>supplyAsync(
+- () -> this.getChunkFutureMainThread(x, y, chunkStatus, load), this.mainThreadProcessor
+- )
+- .thenCompose(completableFuture -> (CompletionStage<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>)completableFuture);
++ completablefuture = CompletableFuture.supplyAsync(() -> {
++ return this.getChunkFutureMainThread(x, y, chunkStatus, load);
++ }, this.mainThreadProcessor).thenCompose((completablefuture1) -> {
++ return completablefuture1;
++ });
+ }
+
+- return chunkFutureMainThread;
++ return completablefuture;
+ }
+
+- private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(
+- int x, int y, ChunkStatus chunkStatus, boolean load
+- ) {
+- ChunkPos chunkPos = new ChunkPos(x, y);
+- long l = chunkPos.toLong();
+- int i = ChunkLevel.byStatus(chunkStatus);
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(l);
+- if (load) {
+- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, i, chunkPos);
+- if (this.chunkAbsent(visibleChunkIfPresent, i)) {
+- ProfilerFiller profiler = this.level.getProfiler();
+- profiler.push("chunkLoad");
++ private CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getChunkFutureMainThread(int x, int y, ChunkStatus chunkStatus, boolean load) {
++ ChunkPos chunkcoordintpair = new ChunkPos(x, y);
++ long k = chunkcoordintpair.toLong();
++ int l = ChunkLevel.byStatus(chunkStatus);
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
++
++ // CraftBukkit start - don't add new ticket for currently unloading chunk
++ boolean currentlyUnloading = false;
++ if (playerchunk != null) {
++ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel);
++ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel());
++ currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
++ }
++ if (load && !currentlyUnloading) {
++ // CraftBukkit end
++ this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
++ if (this.chunkAbsent(playerchunk, l)) {
++ ProfilerFiller gameprofilerfiller = this.level.getProfiler();
++
++ gameprofilerfiller.push("chunkLoad");
+ this.runDistanceManagerUpdates();
+- visibleChunkIfPresent = this.getVisibleChunkIfPresent(l);
+- profiler.pop();
+- if (this.chunkAbsent(visibleChunkIfPresent, i)) {
+- throw (IllegalStateException)Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
++ playerchunk = this.getVisibleChunkIfPresent(k);
++ gameprofilerfiller.pop();
++ if (this.chunkAbsent(playerchunk, l)) {
++ throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
+ }
+ }
+ }
+
+- return this.chunkAbsent(visibleChunkIfPresent, i)
+- ? ChunkHolder.UNLOADED_CHUNK_FUTURE
+- : visibleChunkIfPresent.getOrScheduleFuture(chunkStatus, this.chunkMap);
++ return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkStatus, this.chunkMap);
+ }
+
+ 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
+ public boolean hasChunk(int x, int z) {
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(new ChunkPos(x, z).toLong());
+- int i = ChunkLevel.byStatus(ChunkStatus.FULL);
+- return !this.chunkAbsent(visibleChunkIfPresent, i);
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent((new ChunkPos(x, z)).toLong());
++ int k = ChunkLevel.byStatus(ChunkStatus.FULL);
++
++ return !this.chunkAbsent(playerchunk, k);
+ }
+
+ @Nullable
+ @Override
+ public LightChunk getChunkForLighting(int chunkX, int chunkZ) {
+- long _long = ChunkPos.asLong(chunkX, chunkZ);
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(_long);
+- if (visibleChunkIfPresent == null) {
++ long k = ChunkPos.asLong(chunkX, chunkZ);
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
++
++ if (playerchunk == null) {
+ return null;
+ } else {
+- int i = CHUNK_STATUSES.size() - 1;
++ int l = ServerChunkCache.CHUNK_STATUSES.size() - 1;
+
+ while (true) {
+- ChunkStatus chunkStatus = CHUNK_STATUSES.get(i);
+- Optional<ChunkAccess> optional = visibleChunkIfPresent.getFutureIfPresentUnchecked(chunkStatus).getNow(ChunkHolder.UNLOADED_CHUNK).left();
++ ChunkStatus chunkstatus = (ChunkStatus) ServerChunkCache.CHUNK_STATUSES.get(l);
++ Optional<ChunkAccess> optional = ((Either) playerchunk.getFutureIfPresentUnchecked(chunkstatus).getNow(ChunkHolder.UNLOADED_CHUNK)).left();
++
+ if (optional.isPresent()) {
+- return optional.get();
++ return (LightChunk) optional.get();
+ }
+
+- if (chunkStatus == ChunkStatus.INITIALIZE_LIGHT.getParent()) {
++ if (chunkstatus == ChunkStatus.INITIALIZE_LIGHT.getParent()) {
+ return null;
+ }
+
+- i--;
++ --l;
+ }
+ }
+ }
+@@ -304,6 +316,7 @@
+ boolean runDistanceManagerUpdates() {
+ boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
+ boolean flag1 = this.chunkMap.promoteChunkMap();
++
+ if (!flag && !flag1) {
+ return false;
+ } else {
+@@ -313,13 +326,15 @@
+ }
+
+ public boolean isPositionTicking(long chunkPos) {
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(chunkPos);
+- if (visibleChunkIfPresent == null) {
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkPos);
++
++ if (playerchunk == null) {
+ return false;
+ } else if (!this.level.shouldTickBlocksAt(chunkPos)) {
+ return false;
+ } else {
+- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = visibleChunkIfPresent.getTickingChunkFuture().getNow(null);
++ Either<LevelChunk, ChunkHolder.Failure> either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error
++
+ return either != null && either.left().isPresent();
+ }
+ }
+@@ -331,11 +346,31 @@
+
+ @Override
+ public void close() throws IOException {
+- this.save(true);
++ // CraftBukkit start
++ close(true);
++ }
++
++ public void close(boolean save) throws IOException {
++ if (save) {
++ this.save(true);
++ }
++ // CraftBukkit end
+ this.lightEngine.close();
+ this.chunkMap.close();
+ }
+
++ // CraftBukkit start - modelled on below
++ public void purgeUnload() {
++ this.level.getProfiler().push("purge");
++ this.distanceManager.purgeStaleTickets();
++ this.runDistanceManagerUpdates();
++ this.level.getProfiler().popPush("unload");
++ this.chunkMap.tick(() -> true);
++ this.level.getProfiler().pop();
++ this.clearCache();
++ }
++ // CraftBukkit end
++
+ @Override
+ public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) {
+ this.level.getProfiler().push("purge");
+@@ -354,68 +389,80 @@
+ }
+
+ private void tickChunks() {
+- long gameTime = this.level.getGameTime();
+- long l = gameTime - this.lastInhabitedUpdate;
+- this.lastInhabitedUpdate = gameTime;
++ long i = this.level.getGameTime();
++ long j = i - this.lastInhabitedUpdate;
++
++ this.lastInhabitedUpdate = i;
+ if (!this.level.isDebug()) {
+- ProfilerFiller profiler = this.level.getProfiler();
+- profiler.push("pollingChunks");
+- profiler.push("filteringLoadedChunks");
+- List<ServerChunkCache.ChunkAndHolder> list = Lists.newArrayListWithCapacity(this.chunkMap.size());
++ ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+- for (ChunkHolder chunkHolder : this.chunkMap.getChunks()) {
+- LevelChunk tickingChunk = chunkHolder.getTickingChunk();
+- if (tickingChunk != null) {
+- list.add(new ServerChunkCache.ChunkAndHolder(tickingChunk, chunkHolder));
++ gameprofilerfiller.push("pollingChunks");
++ gameprofilerfiller.push("filteringLoadedChunks");
++ List<ServerChunkCache.a> list = Lists.newArrayListWithCapacity(this.chunkMap.size());
++ Iterator iterator = this.chunkMap.getChunks().iterator();
++
++ while (iterator.hasNext()) {
++ ChunkHolder playerchunk = (ChunkHolder) iterator.next();
++ LevelChunk chunk = playerchunk.getTickingChunk();
++
++ if (chunk != null) {
++ list.add(new ServerChunkCache.a(chunk, playerchunk));
+ }
+ }
+
+ if (this.level.getServer().tickRateManager().runsNormally()) {
+- profiler.popPush("naturalSpawnCount");
+- int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
+- NaturalSpawner.SpawnState spawnState = NaturalSpawner.createState(
+- naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)
+- );
+- this.lastSpawnState = spawnState;
+- profiler.popPush("spawnAndTick");
+- boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
++ gameprofilerfiller.popPush("naturalSpawnCount");
++ int k = this.distanceManager.getNaturalSpawnChunkCount();
++ NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
++
++ this.lastSpawnState = spawnercreature_d;
++ gameprofilerfiller.popPush("spawnAndTick");
++ boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
++
+ Util.shuffle(list, this.level.random);
+- int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
+- boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L;
++ int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
++ boolean flag1 = 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
++ Iterator iterator1 = list.iterator();
+
+- for (ServerChunkCache.ChunkAndHolder chunkAndHolder : list) {
+- LevelChunk levelChunk = chunkAndHolder.chunk;
+- ChunkPos pos = levelChunk.getPos();
+- if (this.level.isNaturalSpawningAllowed(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos)) {
+- levelChunk.incrementInhabitedTime(l);
+- if (_boolean && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(pos)) {
+- NaturalSpawner.spawnForChunk(this.level, levelChunk, spawnState, this.spawnFriendlies, this.spawnEnemies, flag);
++ while (iterator1.hasNext()) {
++ ServerChunkCache.a chunkproviderserver_a = (ServerChunkCache.a) iterator1.next();
++ LevelChunk chunk1 = chunkproviderserver_a.chunk;
++ ChunkPos chunkcoordintpair = chunk1.getPos();
++
++ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
++ chunk1.incrementInhabitedTime(j);
++ if (flag && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) {
++ NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ }
+
+- if (this.level.shouldTickBlocksAt(pos.toLong())) {
+- this.level.tickChunk(levelChunk, _int);
++ if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
++ this.level.tickChunk(chunk1, l);
+ }
+ }
+ }
+
+- profiler.popPush("customSpawners");
+- if (_boolean) {
++ gameprofilerfiller.popPush("customSpawners");
++ if (flag) {
+ this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
+ }
+ }
+
+- profiler.popPush("broadcast");
+- list.forEach(chunkAndHolder1 -> chunkAndHolder1.holder.broadcastChanges(chunkAndHolder1.chunk));
+- profiler.pop();
+- profiler.pop();
++ gameprofilerfiller.popPush("broadcast");
++ list.forEach((chunkproviderserver_a1) -> {
++ chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
++ });
++ gameprofilerfiller.pop();
++ gameprofilerfiller.pop();
+ }
+ }
+
+- private void getFullChunk(long chunkPos, Consumer<LevelChunk> fullChunkGetter) {
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(chunkPos);
+- if (visibleChunkIfPresent != null) {
+- visibleChunkIfPresent.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).left().ifPresent(fullChunkGetter);
++ private void getFullChunk(long chunkPos, Consumer<LevelChunk> consumer) {
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(chunkPos);
++
++ if (playerchunk != null) {
++ ((Either) playerchunk.getFullChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left().ifPresent(consumer);
+ }
++
+ }
+
+ @Override
+@@ -447,20 +494,24 @@
+
+ public void blockChanged(BlockPos pos) {
+ int i = SectionPos.blockToSectionCoord(pos.getX());
+- int i1 = SectionPos.blockToSectionCoord(pos.getZ());
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(ChunkPos.asLong(i, i1));
+- if (visibleChunkIfPresent != null) {
+- visibleChunkIfPresent.blockChanged(pos);
++ int j = SectionPos.blockToSectionCoord(pos.getZ());
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(ChunkPos.asLong(i, j));
++
++ if (playerchunk != null) {
++ playerchunk.blockChanged(pos);
+ }
++
+ }
+
+ @Override
+- public void onLightUpdate(LightLayer type, SectionPos pos) {
++ public void onLightUpdate(EnumSkyBlock type, SectionPos pos) {
+ this.mainThreadProcessor.execute(() -> {
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(pos.chunk().toLong());
+- if (visibleChunkIfPresent != null) {
+- visibleChunkIfPresent.sectionLightChanged(type, pos.y());
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.chunk().toLong());
++
++ if (playerchunk != null) {
++ playerchunk.sectionLightChanged(type, pos.y());
+ }
++
+ });
+ }
+
+@@ -481,6 +532,7 @@
+ if (!player.isRemoved()) {
+ this.chunkMap.move(player);
+ }
++
+ }
+
+ public void removeEntity(Entity entity) {
+@@ -539,12 +591,10 @@
+ this.distanceManager.removeTicketsOnClosing();
+ }
+
+- static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
+- }
++ private final class MainThreadExecutor extends BlockableEventLoop<Runnable> {
+
+- final class MainThreadExecutor extends BlockableEventLoop<Runnable> {
+- MainThreadExecutor(Level level) {
+- super("Chunk source main thread executor for " + level.dimension().location());
++ MainThreadExecutor(Level world) {
++ super("Chunk source main thread executor for " + world.dimension().location());
+ }
+
+ @Override
+@@ -574,13 +624,23 @@
+ }
+
+ @Override
++ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
+ public boolean pollTask() {
++ try {
+ if (ServerChunkCache.this.runDistanceManagerUpdates()) {
+ return true;
+ } else {
+ ServerChunkCache.this.lightEngine.tryScheduleUpdate();
+ return super.pollTask();
+ }
++ } finally {
++ chunkMap.callbackExecutor.run();
+ }
++ // CraftBukkit end
++ }
+ }
++
++ private static record a(LevelChunk chunk, ChunkHolder holder) {
++
++ }
+ }