Re-arrange most chunk system patches to front (#8338)

* Re-arrange most chunk system patches to front

Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
This commit is contained in:
Spottedleaf 2022-09-01 09:51:59 -07:00
parent 61a8488806
commit 90da9124c5
38 changed files with 7792 additions and 795 deletions

View file

@ -9,18 +9,30 @@ sometimes it is.
This patch also prevents the saving/unloading of POI data when
world saving is disabled.
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/ChunkSystem.java
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
@@ -0,0 +0,0 @@ public final class ChunkSystem {
for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z);
}
+ chunkMap.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data
}
public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
@@ -0,0 +0,0 @@ public final class ChunkSystem {
for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
+ chunkMap.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
}
public static void onChunkBorder(LevelChunk chunk, ChunkHolder holder) {
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
}
// Paper end
}
+ this.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data
this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
this.modified = true;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
private void processUnloads(BooleanSupplier shouldKeepTicking) {
@ -30,22 +42,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
long j = longiterator.nextLong();
ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
// Paper end
+ this.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
if (ichunkaccess instanceof LevelChunk) {
((LevelChunk) ichunkaccess).setLoaded(false);
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
+ this.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
} // Paper end
} finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
this.poiManager.loadInData(pos, chunkHolder.poiData);

View file

@ -32,9 +32,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
public void placeNewPlayer(Connection connection, ServerPlayer player) {
player.isRealPlayer = true; // Paper - Chunk priority
+ player.loginTime = System.currentTimeMillis(); // Paper
GameProfile gameprofile = player.getGameProfile();
GameProfileCache usercache = this.server.getProfileCache();

View file

@ -122,6 +122,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end - Different message for short timeout
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
log.log( Level.SEVERE, "------------------------------" );
//

View file

@ -112,10 +112,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return true;
// Paper end
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
+ if (chunk != null) addTicket(x, z); // Paper
return java.util.concurrent.CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk());
}, net.minecraft.server.MinecraftServer.getServer());
}
net.minecraft.server.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> {
net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c;
+ if (chunk != null) addTicket(x, z); // Paper
ret.complete(chunk == null ? null : chunk.getBukkitChunk());
});
});

View file

@ -85,4 +85,4 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper

View file

@ -2703,9 +2703,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private static final Random rand = new Random();
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return this.spigot;
return ret;
}
// Spigot end
+
+ // Paper start - implement pointers
+ @Override
@ -2719,7 +2719,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ return this.adventure$pointers;
+ }
+ // Paper end
// Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644

View file

@ -16,7 +16,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.getProfileCache().save(false); // Paper
}
// Spigot end
com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.close(true, true); // Paper
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java

View file

@ -24,9 +24,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private boolean addEntity(T entity, boolean existing) {
+ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
if (!this.addEntityUuid(entity)) {
return false;
} else {
// Paper start - chunk system hooks
if (existing) {
// I don't want to know why this is a generic type.
@@ -0,0 +0,0 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
}

View file

@ -37,6 +37,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper
return;
}
// Paper start
@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
}
// Paper end
// CraftBukkit end
+ // Paper start - async tab completion
+ TAB_COMPLETE_EXECUTOR.execute(() -> {

View file

@ -2282,7 +2282,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.getProfileCache().save(false); // Paper
this.getProfileCache().save();
}
// Spigot end
-
@ -2302,6 +2302,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.poiManager.close();
} finally {
super.close();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected void saveAllChunks(boolean flush) {
+ // Paper start - do not overload I/O threads with too much work when saving
+ int[] saved = new int[1];
+ int maxAsyncSaves = 50;
+ Runnable onChunkSave = () -> {
+ if (++saved[0] >= maxAsyncSaves) {
+ saved[0] = 0;
+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.flush();
+ }
+ };
+ // Paper end - do not overload I/O threads with too much work when saving
if (flush) {
List<ChunkHolder> list = (List) net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}).filter((ichunkaccess) -> {
return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk;
}).filter(this::save).forEach((ichunkaccess) -> {
+ onChunkSave.run(); // Paper - do not overload I/O threads with too much work when saving
mutableboolean.setTrue();
});
} while (mutableboolean.isTrue());
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.processUnloads(() -> {
return true;
@ -2310,7 +2335,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ //this.flushWorker(); // Paper - nuke IOWorker
+ this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour
} else {
this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded);
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
protected void tick(BooleanSupplier shouldKeepTicking) {
@ -2824,7 +2849,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- 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, Tic
server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam", new Object[0]))); // Paper
this.disconnect(Component.translatable("disconnect.spam"));
return;
}
+ // Paper start
@ -2835,8 +2860,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ // Paper end
// CraftBukkit end
// Paper start - async tab completion
TAB_COMPLETE_EXECUTOR.execute(() -> {
StringReader stringreader = new StringReader(packet.getCommand());
diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
@ -3365,7 +3390,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
return regionfile;
} else {
if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - configurable
if (this.regionCache.size() >= 256) {
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
@ -3410,13 +3435,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit
+ RegionFile regionfile = this.getRegionFile(pos, false, true); // CraftBukkit // Paper
+ try { // Paper
int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper
if (nbt == null) {
regionfile.clear(pos);
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
net.minecraft.server.MinecraftServer.LOGGER.error("Failed to save chunk", laste);
}
}
// Paper end
+ } finally { // Paper start
+ regionfile.fileLock.unlock();
+ } // Paper end
@ -3554,95 +3579,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
+ // Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
public DragonBattle getEnderDragonBattle() {
return (this.getHandle().dragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().dragonFight());
}
+ // Paper start
+ @Override
+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(int x, int z, boolean gen, boolean urgent) {
+ if (Bukkit.isPrimaryThread()) {
+ net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z);
+ if (immediate != null) {
+ return java.util.concurrent.CompletableFuture.completedFuture(immediate.getBukkitChunk());
+ }
+ } else {
+ java.util.concurrent.CompletableFuture<Chunk> future = new java.util.concurrent.CompletableFuture<Chunk>();
+ world.getServer().execute(() -> {
+ getChunkAtAsync(x, z, gen, urgent).whenComplete((chunk, err) -> {
+ if (err != null) {
+ future.completeExceptionally(err);
+ } else {
+ future.complete(chunk);
+ }
+ });
+ });
+ return future;
+ }
+
+ return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
+ return java.util.concurrent.CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk());
+ }, net.minecraft.server.MinecraftServer.getServer());
+ }
+ // Paper end
@Override
public PersistentDataContainer getPersistentDataContainer() {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -0,0 +0,0 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
this.entity.setYHeadRot(yaw);
}
+ // Paper start
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location location, TeleportCause cause) {
+ Preconditions.checkArgument(location != null, "location");
+ location.checkFinite();
+ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call.
+
+ net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle();
+ java.util.concurrent.CompletableFuture<Boolean> ret = new java.util.concurrent.CompletableFuture<>();
+
+ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), location.getX(), location.getZ(), (list) -> {
+ net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource();
+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) {
+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
+ }
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
+ try {
+ ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE);
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ ret.completeExceptionally(throwable);
+ }
+ });
+ });
+
+ return ret;
+ }
+ // Paper end
+
@Override
public boolean teleport(Location location) {
return this.teleport(location, TeleportCause.PLUGIN);
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread
// Paper end - Different message for short timeout
//
log.log( Level.SEVERE, "------------------------------" );
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" );
+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
log.log( Level.SEVERE, "------------------------------" );

View file

@ -564,11 +564,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import com.destroystokyo.paper.profile.CraftPlayerProfile;
+import com.destroystokyo.paper.profile.PlayerProfile;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
import java.lang.ref.Cleaner;
@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@@ -0,0 +0,0 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.commons.lang.exception.ExceptionUtils;
+import com.mojang.authlib.GameProfile;
import org.bukkit.Location;

View file

@ -22,9 +22,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
import net.minecraft.core.Direction;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.DistanceManager;
@@ -0,0 +0,0 @@ public final class MCUtil {
}
}
@ -44,8 +44,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return null;
+ }
+
public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
public static ChunkStatus getChunkStatus(ChunkHolder chunk) {
return chunk.getChunkHolderStatus();
}
diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644

View file

@ -23,29 +23,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit
RegionFile regionfile = this.getRegionFile(pos, false, true); // CraftBukkit // Paper
try { // Paper
+ int attempts = 0; Exception laste = null; while (attempts++ < 5) { try { // Paper
if (nbt == null) {
regionfile.clear(pos);
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
dataoutputstream.close();
}
}
+ // Paper start
+ return;
+ // Paper start
+ return;
+ } catch (Exception ex) {
+ laste = ex;
+ }
+ }
+
+ if (laste != null) {
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(laste);
+ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to save chunk", laste);
+ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to save chunk " + pos, laste);
+ }
+ // Paper end
}
public void close() throws IOException {
} finally { // Paper start
regionfile.fileLock.unlock();
} // Paper end

View file

@ -148,14 +148,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ int ticking = 0;
+ int entityTicking = 0;
+
+ for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) {
+ for (final ChunkHolder chunk : net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world)) {
+ if (chunk.getFullChunkNowUnchecked() == null) {
+ continue;
+ }
+
+ ++total;
+
+ ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel());
+ ChunkHolder.FullChunkStatus state = chunk.getFullStatus();
+
+ switch (state) {
+ case INACCESSIBLE -> ++inactive;
@ -226,14 +226,22 @@ diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -0,0 +0,0 @@ import net.minecraft.core.BlockPos;
@@ -0,0 +0,0 @@
package net.minecraft.server;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
+import com.mojang.datafixers.util.Either;
import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
import java.lang.ref.Cleaner;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.server.level.DistanceManager;
+import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.server.level.Ticket;
@ -244,22 +252,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.commons.lang.exception.ExceptionUtils;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
import com.mojang.authlib.GameProfile;
+import com.mojang.datafixers.util.Either;
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.CraftWorld;
@@ -0,0 +0,0 @@ import org.spigotmc.AsyncCatcher;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.io.*;
+import java.util.ArrayList;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Queue;
+import java.util.Set;
@ -267,19 +267,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;
@@ -0,0 +0,0 @@ public final class MCUtil {
return null;
}
}
+ public static ChunkStatus getChunkStatus(ChunkHolder chunk) {
+ List<ChunkStatus> statuses = net.minecraft.server.level.ServerChunkCache.CHUNK_STATUSES;
+ for (int i = statuses.size() - 1; i >= 0; --i) {
+ ChunkStatus curr = statuses.get(i);
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = chunk.getFutureIfPresentUnchecked(curr);
+ if (future != ChunkHolder.UNLOADED_CHUNK_FUTURE) {
+ return curr;
+ }
+ }
+ return null; // unloaded
+ return chunk.getChunkHolderStatus();
+ }
+
+ public static void dumpChunks(File file) throws IOException {
@ -337,9 +329,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
+ ChunkMap chunkMap = world.getChunkSource().chunkMap;
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.visibleChunkMap;
+ DistanceManager chunkMapDistance = chunkMap.distanceManager;
+ List<ChunkHolder> allChunks = new ArrayList<>(visibleChunks.values());
+ List<ChunkHolder> allChunks = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world);
+ List<ServerPlayer> players = world.players;
+
+ int fullLoadedChunks = 0;
@ -362,7 +353,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
+ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
+ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
+ worldData.addProperty("visible-chunk-count", visibleChunks.size());
+ worldData.addProperty("visible-chunk-count", allChunks.size());
+ worldData.addProperty("loaded-chunk-count", chunkMap.entitiesInLevel.size());
+ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks);
+
@ -431,7 +422,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ String fileData = stringWriter.toString();
+
+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")) {
+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) {
+ out.print(fileData);
+ }
+ }

View file

@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ boolean isUpdateQueued = false; // Paper
private final ChunkMap chunkMap; // Paper
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
// Paper start
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

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
this.visibleChunkMap = this.updatingChunkMap.clone();
// Paper - don't copy
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@Nullable
@ -41,9 +41,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
boolean removed;
if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
} // Paper end
} else if (removed) { // Paper start
net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder);
} // Paper end
+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
}

View file

@ -8,32 +8,39 @@ tickDistanceManager call can take up quite a bit in
the function. I saw approximately 1/3rd of the function
on the copy.
diff --git a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
+++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
@@ -0,0 +0,0 @@ public final class ChunkDebugCommand implements PaperSubcommand {
int ticking = 0;
int entityTicking = 0;
--- a/src/main/java/net/minecraft/server/ChunkSystem.java
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
@@ -0,0 +0,0 @@ public final class ChunkSystem {
}
- for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) {
+ for (final ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Paper - change updating chunks map
if (chunk.getFullChunkNowUnchecked() == null) {
continue;
}
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -0,0 +0,0 @@ public final class MCUtil {
public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
- return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
+ if (Bukkit.isPrimaryThread()) {
+ return level.chunkSource.chunkMap.updatingChunks.getVisibleValuesCopy();
+ }
+ synchronized (level.chunkSource.chunkMap.updatingChunks) {
+ return level.chunkSource.chunkMap.updatingChunks.getVisibleValuesCopy();
+ }
}
ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
ChunkMap chunkMap = world.getChunkSource().chunkMap;
- Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.visibleChunkMap;
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.updatingChunks.getVisibleMap(); // Paper
DistanceManager chunkMapDistance = chunkMap.distanceManager;
List<ChunkHolder> allChunks = new ArrayList<>(visibleChunks.values());
List<ServerPlayer> players = world.players;
public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
- return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
+ return level.chunkSource.chunkMap.updatingChunks.getUpdatingValuesCopy();
}
public static int getVisibleChunkHolderCount(final ServerLevel level) {
- return level.chunkSource.chunkMap.visibleChunkMap.size();
+ return level.chunkSource.chunkMap.updatingChunks.getVisibleMap().size();
}
public static int getUpdatingChunkHolderCount(final ServerLevel level) {
- return level.chunkSource.chunkMap.updatingChunkMap.size();
+ return level.chunkSource.chunkMap.updatingChunks.getUpdatingMap().size();
}
public static boolean hasAnyChunkHolders(final ServerLevel level) {
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
@ -53,7 +60,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public final LongSet entitiesInLevel;
public final ServerLevel level;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
- this.visibleChunkMap = this.updatingChunkMap.clone();
@ -82,21 +89,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
protected IntSupplier getChunkQueueLevel(long pos) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
};
stringbuilder.append("Updating:").append(System.lineSeparator());
- this.updatingChunkMap.values().forEach(consumer);
+ this.updatingChunks.getUpdatingValuesCopy().forEach(consumer); // Paper
stringbuilder.append("Visible:").append(System.lineSeparator());
- this.visibleChunkMap.values().forEach(consumer);
+ this.updatingChunks.getVisibleValuesCopy().forEach(consumer); // Paper
CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
}
// Paper start
holder.onChunkAdd();
// Paper end
- this.updatingChunkMap.put(pos, holder);
+ this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
this.modified = true;
@ -104,32 +99,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
protected void saveAllChunks(boolean flush) {
if (flush) {
- List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
+ List<ChunkHolder> list = (List) this.updatingChunks.getVisibleValuesCopy().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
do {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
//this.flushWorker(); // Paper - nuke IOWorker
this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour
} else {
- this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
+ this.updatingChunks.getVisibleValuesCopy().forEach(this::saveChunkIfNeeded); // Paper
}
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public boolean hasWork() {
- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets();
+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunks.getUpdatingValuesCopy().isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper
}
private void processUnloads(BooleanSupplier shouldKeepTicking) {
LongIterator longiterator = this.toDrop.iterator();
for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
long j = longiterator.nextLong();
- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
@ -151,88 +120,3 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.modified = false;
return true;
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.viewDistance = j;
this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
- ObjectIterator objectiterator = this.updatingChunkMap.values().iterator();
+ Iterator objectiterator = this.updatingChunks.getVisibleValuesCopy().iterator(); // Paper
while (objectiterator.hasNext()) {
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public int size() {
- return this.visibleChunkMap.size();
+ return this.updatingChunks.getVisibleMap().size(); // Paper - Don't copy
}
public DistanceManager getDistanceManager() {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected Iterable<ChunkHolder> getChunks() {
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+ return Iterables.unmodifiableIterable(this.updatingChunks.getVisibleValuesCopy()); // Paper
}
void dumpChunks(Writer writer) throws IOException {
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
TickingTracker tickingtracker = this.distanceManager.tickingTracker();
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
+ ObjectBidirectionalIterator objectbidirectionaliterator = this.updatingChunks.getVisibleMap().clone().long2ObjectEntrySet().fastIterator(); // Paper
while (objectbidirectionaliterator.hasNext()) {
Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public int getTileEntityCount() {
// We don't use the full world tile entity list, so we must iterate chunks
- Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.visibleChunkMap;
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap(); // Paper - change updating chunks map
int size = 0;
for (ChunkHolder playerchunk : chunks.values()) {
net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk();
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
public int getChunkCount() {
int ret = 0;
- for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) {
+ for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().values()) { // Paper - change updating chunks map
if (chunkHolder.getTickingChunk() != null) {
++ret;
}
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public Chunk[] getLoadedChunks() {
- Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.visibleChunkMap;
+ // Paper start
+ if (Thread.currentThread() != world.getLevel().thread) {
+ // Paper start - change updating chunks map
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks;
+ synchronized (world.getChunkSource().chunkMap.updatingChunks) {
+ chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().clone();
+ }
+ return chunks.values().stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
+ // Paper end - change updating chunks map
+ }
+ // Paper end
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.updatingChunks.getVisibleMap(); // Paper - change updating chunks map
return chunks.values().stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
}
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean refreshChunk(int x, int z) {
- ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.visibleChunkMap.get(ChunkPos.asLong(x, z));
+ ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().get(ChunkPos.asLong(x, z));
if (playerChunk == null) return false;
playerChunk.getTickingChunkFuture().thenAccept(either -> {

View file

@ -1,42 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 16 Oct 2021 01:36:00 -0700
Subject: [PATCH] Do not overload I/O threads with chunk data while flush
saving
If the chunk count is high, then the memory used by the
chunks adds up and could cause problems. By flushing
every so many chunks, the server will not become
stressed for memory. It will also not increase the total
time to save, as flush saving performs a full flush at
the end anyways.
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
// Paper end
protected void saveAllChunks(boolean flush) {
+ // Paper start - do not overload I/O threads with too much work when saving
+ int[] saved = new int[1];
+ int maxAsyncSaves = 50;
+ Runnable onChunkSave = () -> {
+ if (++saved[0] >= maxAsyncSaves) {
+ saved[0] = 0;
+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.flush();
+ }
+ };
+ // Paper end - do not overload I/O threads with too much work when saving
if (flush) {
List<ChunkHolder> list = (List) this.updatingChunks.getVisibleValuesCopy().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}).filter((ichunkaccess) -> {
return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk;
}).filter(this::save).forEach((ichunkaccess) -> {
+ onChunkSave.run(); // Paper - do not overload I/O threads with too much work when saving
mutableboolean.setTrue();
});
} while (mutableboolean.isTrue());

View file

@ -32,6 +32,21 @@ But for those who are ok with leaving this inconsistent behavior, you may use WA
It is recommended you regenerate the entities, as these were legit entities, and deserve your love.
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/ChunkSystem.java
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
@@ -0,0 +0,0 @@ public final class ChunkSystem {
}
public static void onEntityPreAdd(final ServerLevel level, final Entity entity) {
-
+ if (net.minecraft.server.level.ChunkMap.checkDupeUUID(level, entity)) {
+ return;
+ }
}
public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
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
@ -49,22 +64,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
+ // Paper start
+ private static void checkDupeUUID(ServerLevel level, Entity entity) {
+ // rets true if to prevent the entity from being added
+ public static boolean checkDupeUUID(ServerLevel level, Entity entity) {
+ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode;
+ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN
+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE
+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) {
+ return;
+ return false;
+ }
+ Entity other = level.getEntity(entity.getUUID());
+
+ if (other == null || other == entity) {
+ return false;
+ }
+
+ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved()
+ && Objects.equals(other.getEncodeId(), entity.getEncodeId())
+ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange
+ ) {
+ if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + " because it was near the duplicate and likely an actual duplicate. See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+ entity.discard();
+ return;
+ return true;
+ }
+ if (other != null && !other.isRemoved()) {
+ switch (mode) {
@ -76,13 +96,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ case DELETE: {
+ if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+ entity.discard();
+ break;
+ return true;
+ }
+ default:
+ if (ServerLevel.DEBUG_ENTITIES) LOGGER.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", doing nothing to " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
+ break;
+ }
+ }
+ return false;
+ }
+ // Paper end
public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> prepareTickingChunk(ChunkHolder holder) {

View file

@ -136,11 +136,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -0,0 +0,0 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return this.spigot;
return ret;
}
// Spigot end
+
+ // Paper start
+ @Override
+ public Location getOrigin() {
+ Vector originVector = this.getHandle().getOriginVector();
@ -155,5 +154,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ //noinspection ConstantConditions
+ return originVector.toLocation(world);
+ }
+ // Paper end
// Paper end
}

View file

@ -67,6 +67,90 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
}
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/ChunkSystem.java
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
@@ -0,0 +0,0 @@ public final class ChunkSystem {
static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo);
+ // Paper start - priority
+ private static int getPriorityBoost(final PrioritisedExecutor.Priority priority) {
+ if (priority.isLowerOrEqualPriority(PrioritisedExecutor.Priority.NORMAL)) {
+ return 0;
+ }
+
+ int dist = PrioritisedExecutor.Priority.BLOCKING.ordinal() - PrioritisedExecutor.Priority.NORMAL.ordinal();
+
+
+ return (net.minecraft.server.level.DistanceManager.URGENT_PRIORITY * (priority.ordinal() - PrioritisedExecutor.Priority.NORMAL.ordinal())) / dist;
+ }
+ // Paper end - priority
+
private static long chunkLoadCounter = 0L;
public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
@@ -0,0 +0,0 @@ public final class ChunkSystem {
final int minLevel = 33 + ChunkStatus.getDistance(toStatus);
final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+ final int priorityBoost = getPriorityBoost(priority);
if (addTicket) {
level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
}
level.chunkSource.runDistanceManagerUpdates();
+ if (priorityBoost == net.minecraft.server.level.DistanceManager.URGENT_PRIORITY) {
+ level.chunkSource.markUrgent(chunkPos);
+ } else if (priorityBoost != 0) {
+ level.chunkSource.markHighPriority(chunkPos, priorityBoost);
+ }
+
final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
try {
if (onComplete != null) {
@@ -0,0 +0,0 @@ public final class ChunkSystem {
level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
}
+ if (priorityBoost == net.minecraft.server.level.DistanceManager.URGENT_PRIORITY) {
+ level.chunkSource.clearUrgent(chunkPos);
+ } else if (priorityBoost != 0) {
+ level.chunkSource.clearPriorityTickets(chunkPos);
+ }
}
};
@@ -0,0 +0,0 @@ public final class ChunkSystem {
final int radius = toStatus.ordinal() - 1;
final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+ final int priorityBoost = getPriorityBoost(priority);
if (addTicket) {
level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
}
level.chunkSource.runDistanceManagerUpdates();
+ if (priorityBoost != 0) {
+ level.chunkSource.markAreaHighPriority(chunkPos, priorityBoost, radius);
+ }
+
final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
try {
if (onComplete != null) {
@@ -0,0 +0,0 @@ public final class ChunkSystem {
level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
}
+ if (priorityBoost != 0) {
+ level.chunkSource.clearAreaPriorityTickets(chunkPos, radius);
+ }
}
};
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
@ -150,11 +234,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
final Optional<LevelChunk> left = either.left();
if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
// note: Here is a very good place to add callbacks to logic waiting on this.
LevelChunk fullChunk = either.left().get();
ChunkHolder.this.isFullChunkReady = true;
fullChunk.playerChunk = ChunkHolder.this;
+ this.chunkMap.distanceManager.clearPriorityTickets(pos);
net.minecraft.server.ChunkSystem.onChunkBorder(fullChunk, this);
+ this.chunkMap.distanceManager.clearPriorityTickets(pos); // Paper - chunk priority
}
});
this.updateChunkToSave(this.fullChunkFuture, "full");
@ -173,7 +256,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
either.ifLeft(chunk -> {
ChunkHolder.this.isEntityTickingReady = true;
// Paper start - entity ticking chunk set
net.minecraft.server.ChunkSystem.onChunkEntityTicking(chunk, this);
@@ -0,0 +0,0 @@ public class ChunkHolder {
this.demoteFullChunk(chunkStorage, playerchunk_state1);
}
@ -422,9 +505,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ // Paper end
+
// Paper start
public void updatePlayerMobTypeMap(Entity entity) {
if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
List<ChunkHolder> list1 = new ArrayList();
int j = centerChunk.x;
@ -542,7 +625,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public abstract class DistanceManager {
public boolean runAllUpdates(ChunkMap chunkStorage) {
//this.f.a(); // Paper - no longer used
this.naturalSpawnChunkCounter.runAllUpdates();
this.tickingTicketsTracker.runAllUpdates();
+ org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
this.playerTicketManager.runAllUpdates();
@ -737,6 +820,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ public void clearPriorityTickets(ChunkPos coords) {
+ this.distanceManager.clearPriorityTickets(coords);
+ }
+
+ public void clearUrgent(ChunkPos coords) {
+ this.distanceManager.clearUrgent(coords);
+ }
// Paper end - async chunk io
@ -808,8 +895,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private int lastSentFood = -99999999;
private boolean lastFoodSaturationZero = true;
@@ -0,0 +0,0 @@ public class ServerPlayer extends Player {
this.bukkitPickUpLoot = true;
this.maxHealthCache = this.getMaxHealth();
this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
}
+ // Paper start - Chunk priority
+ public BlockPos getPointInFront(double inFront) {
@ -1120,20 +1207,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public void placeNewPlayer(Connection connection, ServerPlayer player) {
+ player.isRealPlayer = true; // Paper - Chunk priority
ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper
if (prev != null) {
disconnectPendingPlayer(prev);
@@ -0,0 +0,0 @@ public abstract class PlayerList {
net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap;
net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager;
distanceManager.addTicket(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong());
- worldserver1.getChunkSource().runDistanceManagerUpdates();
- worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> {
+ worldserver1.getChunkSource().markAreaHighPriority(pos, 28, 3); // Paper - Chunk priority
+ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> { // Paper - Chunk priority
net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong());
if (updatingChunk != null) {
return updatingChunk.getEntityTickingChunkFuture();
GameProfile gameprofile = player.getGameProfile();
GameProfileCache usercache = this.server.getProfileCache();
Optional<GameProfile> optional = usercache.get(gameprofile.getId());
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
@ -1176,41 +1252,3 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
org.bukkit.Server server = this.level.getCraftServer();
org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isUnsaved());
server.getPluginManager().callEvent(unloadEvent);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return future;
}
+ // Paper start - Chunk priority
+ if (!urgent) {
+ // If not urgent, at least use a slightly boosted priority
+ world.getChunkSource().markHighPriority(new ChunkPos(x, z), 1);
+ }
+ // Paper end
return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
if (chunk != null) addTicket(x, z); // Paper
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead.");
}
+ // Paper start - Chunk priority
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location loc, @javax.annotation.Nonnull PlayerTeleportEvent.TeleportCause cause) {
+ ((CraftWorld)loc.getWorld()).getHandle().getChunkSource().markAreaHighPriority(
+ new net.minecraft.world.level.ChunkPos(net.minecraft.util.Mth.floor(loc.getX()) >> 4,
+ net.minecraft.util.Mth.floor(loc.getZ()) >> 4), 28, 3); // Load area high priority
+ return super.teleportAsync(loc, cause);
+ }
+ // Paper end
+
@Override
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) {
Preconditions.checkArgument(location != null, "location");

View file

@ -115,17 +115,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// private final Map<UUID, ServerStatisticManager> stats;
// private final Map<UUID, AdvancementDataPlayer> advancements;
@@ -0,0 +0,0 @@ public abstract class PlayerList {
}
public void placeNewPlayer(Connection connection, ServerPlayer player) {
+ ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper
player.isRealPlayer = true; // Paper - Chunk priority
player.loginTime = System.currentTimeMillis(); // Paper
+ // Paper start
+ ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);
+ if (prev != null) {
+ disconnectPendingPlayer(prev);
+ }
+ player.networkManager = connection; // Paper
player.loginTime = System.currentTimeMillis(); // Paper
+ player.networkManager = connection;
+ // Paper end
GameProfile gameprofile = player.getGameProfile();
GameProfileCache usercache = this.server.getProfileCache();
Optional<GameProfile> optional = usercache.get(gameprofile.getId());
@@ -0,0 +0,0 @@ public abstract class PlayerList {
if (nbttagcompound != null && nbttagcompound.contains("bukkit")) {
CompoundTag bukkit = nbttagcompound.getCompound("bukkit");
@ -164,31 +166,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ final net.minecraft.world.level.ChunkPos pos = new net.minecraft.world.level.ChunkPos(chunkX, chunkZ);
+ net.minecraft.server.level.ChunkMap playerChunkMap = worldserver1.getChunkSource().chunkMap;
+ net.minecraft.server.level.DistanceManager distanceManager = playerChunkMap.distanceManager;
+ distanceManager.addTicket(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong());
+ worldserver1.getChunkSource().runDistanceManagerUpdates();
+ worldserver1.getChunkSource().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> {
+ net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pos.toLong());
+ if (updatingChunk != null) {
+ return updatingChunk.getEntityTickingChunkFuture();
+ } else {
+ return java.util.concurrent.CompletableFuture.completedFuture(chunk);
+ }
+ }).thenAccept(chunk -> {
+ MinecraftServer.getServer().scheduleOnMain(() -> {
+ try {
+ if (!playerconnection.connection.isConnected()) {
+ return;
+ net.minecraft.server.ChunkSystem.scheduleTickingState(
+ worldserver1, chunkX, chunkZ, net.minecraft.server.level.ChunkHolder.FullChunkStatus.ENTITY_TICKING, true,
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHEST,
+ (chunk) -> {
+ MinecraftServer.getServer().scheduleOnMain(() -> {
+ try {
+ if (!playerconnection.connection.isConnected()) {
+ return;
+ }
+ PlayerList.this.postChunkLoadJoin(
+ player, finalWorldserver, connection, playerconnection,
+ nbttagcompound, s1, lastKnownName
+ );
+ distanceManager.addTicket(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong());
+ } finally {
+ finalWorldserver.pendingLogin.remove(player);
+ }
+ PlayerList.this.postChunkLoadJoin(
+ player, finalWorldserver, connection, playerconnection,
+ nbttagcompound, s1, lastKnownName
+ );
+ distanceManager.addTicket(net.minecraft.server.level.TicketType.LOGIN, pos, 31, pos.toLong());
+ } finally {
+ finalWorldserver.pendingLogin.remove(player);
+ }
+ });
+ });
+ });
+ }
+ );
+ }
+
+ public ServerPlayer getActivePlayer(UUID uuid) {

View file

@ -4528,6 +4528,281 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (packet.isSkippable()) {
throw new SkipPacketException(var10);
} else {
diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
@@ -0,0 +0,0 @@
+package net.minecraft.server;
+
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
+import com.destroystokyo.paper.util.SneakyThrow;
+import com.mojang.datafixers.util.Either;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.util.CoordinateUtils;
+import net.minecraft.server.level.ChunkHolder;
+import net.minecraft.server.level.ChunkMap;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.TicketType;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.bukkit.Bukkit;
+import org.slf4j.Logger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+public final class ChunkSystem {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+
+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run) {
+ scheduleChunkTask(level, chunkX, chunkZ, run, PrioritisedExecutor.Priority.NORMAL);
+ }
+
+ public static void scheduleChunkTask(final ServerLevel level, final int chunkX, final int chunkZ, final Runnable run, final PrioritisedExecutor.Priority priority) {
+ level.chunkSource.mainThreadProcessor.execute(run);
+ }
+
+ public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final boolean gen,
+ final ChunkStatus toStatus, final boolean addTicket, final PrioritisedExecutor.Priority priority,
+ final Consumer<ChunkAccess> onComplete) {
+ if (gen) {
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+ return;
+ }
+ scheduleChunkLoad(level, chunkX, chunkZ, ChunkStatus.EMPTY, addTicket, priority, (final ChunkAccess chunk) -> {
+ if (chunk == null) {
+ onComplete.accept(null);
+ } else {
+ if (chunk.getStatus().isOrAfter(toStatus)) {
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+ } else {
+ onComplete.accept(null);
+ }
+ }
+ });
+ }
+
+ static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo);
+
+ private static long chunkLoadCounter = 0L;
+ public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
+ final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
+ if (!Bukkit.isPrimaryThread()) {
+ scheduleChunkTask(level, chunkX, chunkZ, () -> {
+ scheduleChunkLoad(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+ }, priority);
+ return;
+ }
+
+ final int minLevel = 33 + ChunkStatus.getDistance(toStatus);
+ final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+
+ if (addTicket) {
+ level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+ }
+ level.chunkSource.runDistanceManagerUpdates();
+
+ final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
+ try {
+ if (onComplete != null) {
+ onComplete.accept(chunk);
+ }
+ } catch (final ThreadDeath death) {
+ throw death;
+ } catch (final Throwable thr) {
+ LOGGER.error("Exception handling chunk load callback", thr);
+ SneakyThrow.sneaky(thr);
+ } finally {
+ if (addTicket) {
+ level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
+ level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+ }
+ }
+ };
+
+ final ChunkHolder holder = level.chunkSource.chunkMap.getUpdatingChunkIfPresent(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+
+ if (holder == null || holder.getTicketLevel() > minLevel) {
+ loadCallback.accept(null);
+ return;
+ }
+
+ final CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> loadFuture = holder.getOrScheduleFuture(toStatus, level.chunkSource.chunkMap);
+
+ if (loadFuture.isDone()) {
+ loadCallback.accept(loadFuture.join().left().orElse(null));
+ return;
+ }
+
+ loadFuture.whenCompleteAsync((final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either, final Throwable thr) -> {
+ if (thr != null) {
+ loadCallback.accept(null);
+ return;
+ }
+ loadCallback.accept(either.left().orElse(null));
+ }, (final Runnable r) -> {
+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
+ });
+ }
+
+ public static void scheduleTickingState(final ServerLevel level, final int chunkX, final int chunkZ,
+ final ChunkHolder.FullChunkStatus toStatus, final boolean addTicket,
+ final PrioritisedExecutor.Priority priority, final Consumer<LevelChunk> onComplete) {
+ if (toStatus == ChunkHolder.FullChunkStatus.INACCESSIBLE) {
+ throw new IllegalArgumentException("Cannot wait for INACCESSIBLE status");
+ }
+
+ if (!Bukkit.isPrimaryThread()) {
+ scheduleChunkTask(level, chunkX, chunkZ, () -> {
+ scheduleTickingState(level, chunkX, chunkZ, toStatus, addTicket, priority, onComplete);
+ }, priority);
+ return;
+ }
+
+ final int minLevel = 33 - (toStatus.ordinal() - 1);
+ final int radius = toStatus.ordinal() - 1;
+ final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+
+ if (addTicket) {
+ level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+ }
+ level.chunkSource.runDistanceManagerUpdates();
+
+ final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
+ try {
+ if (onComplete != null) {
+ onComplete.accept(chunk);
+ }
+ } catch (final ThreadDeath death) {
+ throw death;
+ } catch (final Throwable thr) {
+ LOGGER.error("Exception handling chunk load callback", thr);
+ SneakyThrow.sneaky(thr);
+ } finally {
+ if (addTicket) {
+ level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
+ level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
+ }
+ }
+ };
+
+ final ChunkHolder holder = level.chunkSource.chunkMap.getUpdatingChunkIfPresent(CoordinateUtils.getChunkKey(chunkX, chunkZ));
+
+ if (holder == null || holder.getTicketLevel() > minLevel) {
+ loadCallback.accept(null);
+ return;
+ }
+
+ final CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> tickingState;
+ switch (toStatus) {
+ case BORDER: {
+ tickingState = holder.getFullChunkFuture();
+ break;
+ }
+ case TICKING: {
+ tickingState = holder.getTickingChunkFuture();
+ break;
+ }
+ case ENTITY_TICKING: {
+ tickingState = holder.getEntityTickingChunkFuture();
+ break;
+ }
+ default: {
+ throw new IllegalStateException("Cannot reach here");
+ }
+ }
+
+ if (tickingState.isDone()) {
+ loadCallback.accept(tickingState.join().left().orElse(null));
+ return;
+ }
+
+ tickingState.whenCompleteAsync((final Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either, final Throwable thr) -> {
+ if (thr != null) {
+ loadCallback.accept(null);
+ return;
+ }
+ loadCallback.accept(either.left().orElse(null));
+ }, (final Runnable r) -> {
+ scheduleChunkTask(level, chunkX, chunkZ, r, PrioritisedExecutor.Priority.HIGHEST);
+ });
+ }
+
+ public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
+ return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
+ }
+
+ public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
+ return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
+ }
+
+ public static int getVisibleChunkHolderCount(final ServerLevel level) {
+ return level.chunkSource.chunkMap.visibleChunkMap.size();
+ }
+
+ public static int getUpdatingChunkHolderCount(final ServerLevel level) {
+ return level.chunkSource.chunkMap.updatingChunkMap.size();
+ }
+
+ public static boolean hasAnyChunkHolders(final ServerLevel level) {
+ return getUpdatingChunkHolderCount(level) != 0;
+ }
+
+ public static void onEntityPreAdd(final ServerLevel level, final Entity entity) {
+
+ }
+
+ public static void onChunkHolderCreate(final ServerLevel level, final ChunkHolder holder) {
+ final ChunkMap chunkMap = level.chunkSource.chunkMap;
+ for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
+ chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z);
+ }
+ }
+
+ public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
+ final ChunkMap chunkMap = level.chunkSource.chunkMap;
+ for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
+ chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
+ }
+ }
+
+ public static void onChunkBorder(LevelChunk chunk, ChunkHolder holder) {
+ chunk.playerChunk = holder;
+ }
+
+ public static void onChunkNotBorder(LevelChunk chunk, ChunkHolder holder) {
+
+ }
+
+ public static void onChunkTicking(LevelChunk chunk, ChunkHolder holder) {
+ chunk.level.getChunkSource().tickingChunks.add(chunk);
+ }
+
+ public static void onChunkNotTicking(LevelChunk chunk, ChunkHolder holder) {
+ chunk.level.getChunkSource().tickingChunks.remove(chunk);
+ }
+
+ public static void onChunkEntityTicking(LevelChunk chunk, ChunkHolder holder) {
+ chunk.level.getChunkSource().entityTickingChunks.add(chunk);
+ }
+
+ public static void onChunkNotEntityTicking(LevelChunk chunk, ChunkHolder holder) {
+ chunk.level.getChunkSource().entityTickingChunks.remove(chunk);
+ }
+
+ private ChunkSystem() {
+ throw new RuntimeException();
+ }
+}
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
@ -4845,7 +5120,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @return
+ */
+ public static void ensureMain(String reason, Runnable run) {
+ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
+ if (!isMainThread()) {
+ if (reason != null) {
+ new IllegalStateException("Asynchronous " + reason + "!").printStackTrace();
+ }
@ -4870,7 +5145,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @return
+ */
+ public static <T> T ensureMain(String reason, Supplier<T> run) {
+ if (AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread) {
+ if (!isMainThread()) {
+ if (reason != null) {
+ new IllegalStateException("Asynchronous " + reason + "! Blocking thread until it returns ").printStackTrace();
+ }
@ -5118,6 +5393,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private CompletableFuture<Void> pendingFullStateConfirmation;
+ private final ChunkMap chunkMap; // Paper
+
+ // Paper start
+ public void onChunkAdd() {
+
+ }
+
+ public void onChunkRemove() {
+
+ }
+ // Paper end
+
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
@ -5236,16 +5521,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.fullChunkFuture.thenAccept(either -> {
+ final Optional<LevelChunk> left = either.left();
+ if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
+ // note: Here is a very good place to add callbacks to logic waiting on this.
+ LevelChunk fullChunk = either.left().get();
+ ChunkHolder.this.isFullChunkReady = true;
+ fullChunk.playerChunk = ChunkHolder.this;
+ net.minecraft.server.ChunkSystem.onChunkBorder(fullChunk, this);
+ }
+ });
this.updateChunkToSave(this.fullChunkFuture, "full");
}
if (flag2 && !flag3) {
+ // Paper start
+ if (this.isFullChunkReady) {
+ net.minecraft.server.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().left().get(), this); // Paper
+ }
+ // Paper end
this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+ ++this.fullChunkCreateCount; // Paper - cache ticking ready status
@ -5262,9 +5551,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ either.ifLeft(chunk -> {
+ // note: Here is a very good place to add callbacks to logic waiting on this.
+ ChunkHolder.this.isTickingReady = true;
+ // Paper start - ticking chunk set
+ ChunkHolder.this.chunkMap.level.getChunkSource().tickingChunks.add(chunk);
+ // Paper end - ticking chunk set
+ net.minecraft.server.ChunkSystem.onChunkTicking(chunk, this);
+ });
+ });
+ // Paper end
@ -5273,17 +5560,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (flag4 && !flag5) {
- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+ // Paper start
+ if (this.isTickingReady) {
+ net.minecraft.server.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().left().get(), this); // Paper
+ }
+ // Paper end
+ this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage
this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+ // Paper start - ticking chunk set
+ LevelChunk chunkIfCached = this.getFullChunkNowUnchecked();
+ if (chunkIfCached != null) {
+ this.chunkMap.level.getChunkSource().tickingChunks.remove(chunkIfCached);
+ }
+ // Paper end - ticking chunk set
}
boolean flag6 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.ENTITY_TICKING);
@@ -0,0 +0,0 @@ public class ChunkHolder {
this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this.pos);
@ -5292,9 +5577,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.entityTickingChunkFuture.thenAccept(either -> {
+ either.ifLeft(chunk -> {
+ ChunkHolder.this.isEntityTickingReady = true;
+ // Paper start - entity ticking chunk set
+ ChunkHolder.this.chunkMap.level.getChunkSource().entityTickingChunks.add(chunk);
+ // Paper end - entity ticking chunk set
+ net.minecraft.server.ChunkSystem.onChunkEntityTicking(chunk, this);
+ });
+ });
+ // Paper end
@ -5303,17 +5586,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (flag6 && !flag7) {
- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK);
+ // Paper start
+ if (this.isEntityTickingReady) {
+ net.minecraft.server.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().left().get(), this);
+ }
+ // Paper end
+ this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage
this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
+ // Paper start - entity ticking chunk set
+ LevelChunk chunkIfCached = this.getFullChunkNowUnchecked();
+ if (chunkIfCached != null) {
+ this.chunkMap.level.getChunkSource().entityTickingChunks.remove(chunkIfCached);
+ }
+ // Paper end - entity ticking chunk set
}
if (!playerchunk_state1.isOrAfter(playerchunk_state)) {
@@ -0,0 +0,0 @@ public class ChunkHolder {
}
};
@ -5428,18 +5709,77 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private CompletableFuture<Either<List<ChunkAccess>, ChunkHolder.ChunkLoadingFailure>> getChunkRangeFuture(ChunkPos centerChunk, int margin, IntFunction<ChunkStatus> distanceToStatus) {
List<CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> list = new ArrayList();
List<ChunkHolder> list1 = new ArrayList();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
};
stringbuilder.append("Updating:").append(System.lineSeparator());
- this.updatingChunkMap.values().forEach(consumer);
+ net.minecraft.server.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper
stringbuilder.append("Visible:").append(System.lineSeparator());
- this.visibleChunkMap.values().forEach(consumer);
+ net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper
CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading");
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
holder.setTicketLevel(level);
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
+ // Paper start
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
+ this.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z);
+ }
+ net.minecraft.server.ChunkSystem.onChunkHolderCreate(this.level, holder);
+ // Paper end
}
+ // Paper start
+ holder.onChunkAdd();
+ // Paper end
this.updatingChunkMap.put(pos, holder);
this.modified = true;
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
protected void saveAllChunks(boolean flush) {
if (flush) {
- List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
+ List<ChunkHolder> list = (List) net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
do {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
});
this.flushWorker();
} else {
- this.visibleChunkMap.values().forEach(this::saveChunkIfNeeded);
+ net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded);
}
}
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public boolean hasWork() {
- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets();
+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || net.minecraft.server.ChunkSystem.hasAnyChunkHolders(this.level) || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.queueSorter.hasWork() || this.distanceManager.hasTickets(); // Paper
}
private void processUnloads(BooleanSupplier shouldKeepTicking) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
if (playerchunk != null) {
+ playerchunk.onChunkRemove(); // Paper
this.pendingUnloads.put(j, playerchunk);
this.modified = true;
++i;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
int l = 0;
- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
+ Iterator objectiterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) {
if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (completablefuture1 != completablefuture) {
this.scheduleUnload(pos, holder);
@ -5448,9 +5788,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper start
+ boolean removed;
+ if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
+ this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
+ }
+ net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder);
+ // Paper end
if (ichunkaccess instanceof LevelChunk) {
((LevelChunk) ichunkaccess).setLoaded(false);
@ -5459,15 +5797,57 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.lightEngine.tryScheduleUpdate();
this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null);
this.chunkSaveCooldowns.remove(ichunkaccess.getPos().toLong());
- }
+ } else if (removed) { // Paper start
+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
+ this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
+ } // Paper end
+ net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder);
+ } // Paper end
}
};
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.viewDistance = j;
this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
- ObjectIterator objectiterator = this.updatingChunkMap.values().iterator();
+ Iterator objectiterator = net.minecraft.server.ChunkSystem.getUpdatingChunkHolders(this.level).iterator(); // Paper
while (objectiterator.hasNext()) {
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public int size() {
- return this.visibleChunkMap.size();
+ return net.minecraft.server.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper
}
public DistanceManager getDistanceManager() {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected Iterable<ChunkHolder> getChunks() {
- return Iterables.unmodifiableIterable(this.visibleChunkMap.values());
+ return Iterables.unmodifiableIterable(net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper
}
void dumpChunks(Writer writer) throws IOException {
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
TickingTracker tickingtracker = this.distanceManager.tickingTracker();
- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator();
+ Iterator<ChunkHolder> objectbidirectionaliterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (objectbidirectionaliterator.hasNext()) {
- Entry<ChunkHolder> entry = (Entry) objectbidirectionaliterator.next();
- long i = entry.getLongKey();
+ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper
+ long i = playerchunk.pos.toLong(); // Paper
ChunkPos chunkcoordintpair = new ChunkPos(i);
- ChunkHolder playerchunk = (ChunkHolder) entry.getValue();
+ // Paper
Optional<ChunkAccess> optional = Optional.ofNullable(playerchunk.getLastAvailable());
Optional<LevelChunk> optional1 = optional.flatMap((ichunkaccess) -> {
return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty();
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!flag1) {
this.distanceManager.addPlayer(SectionPos.of((EntityAccess) player), player);
}
@ -5597,7 +5977,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ LevelChunk cachedChunk = this.lastLoadedChunks[cacheKey];
+ if (cachedChunk != null && cachedChunk.locX == x & cachedChunk.locZ == z) {
+ return this.lastLoadedChunks[cacheKey];
+ return cachedChunk;
+ }
+
+ long chunkKey = ChunkPos.asLong(x, z);
@ -5623,80 +6003,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ long chunkFutureAwaitCounter; // Paper - private -> package private
+
+ public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer<LevelChunk> onLoad) {
+ if (Thread.currentThread() != this.mainThread) {
+ this.mainThreadProcessor.execute(() -> {
+ ServerChunkCache.this.getEntityTickingChunkAsync(x, z, onLoad);
+ });
+ return;
+ }
+ this.getChunkFutureAsynchronously(x, z, 31, ChunkHolder::getEntityTickingChunkFuture, onLoad);
+ net.minecraft.server.ChunkSystem.scheduleTickingState(
+ this.level, x, z, ChunkHolder.FullChunkStatus.ENTITY_TICKING, true,
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, onLoad
+ );
+ }
+
+ public void getTickingChunkAsync(int x, int z, java.util.function.Consumer<LevelChunk> onLoad) {
+ if (Thread.currentThread() != this.mainThread) {
+ this.mainThreadProcessor.execute(() -> {
+ ServerChunkCache.this.getTickingChunkAsync(x, z, onLoad);
+ });
+ return;
+ }
+ this.getChunkFutureAsynchronously(x, z, 32, ChunkHolder::getTickingChunkFuture, onLoad);
+ net.minecraft.server.ChunkSystem.scheduleTickingState(
+ this.level, x, z, ChunkHolder.FullChunkStatus.TICKING, true,
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, onLoad
+ );
+ }
+
+ public void getFullChunkAsync(int x, int z, java.util.function.Consumer<LevelChunk> onLoad) {
+ if (Thread.currentThread() != this.mainThread) {
+ this.mainThreadProcessor.execute(() -> {
+ ServerChunkCache.this.getFullChunkAsync(x, z, onLoad);
+ });
+ return;
+ }
+ this.getChunkFutureAsynchronously(x, z, 33, ChunkHolder::getFullChunkFuture, onLoad);
+ }
+
+ private void getChunkFutureAsynchronously(int x, int z, int ticketLevel, java.util.function.Function<ChunkHolder, CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>>> futureGet, java.util.function.Consumer<LevelChunk> onLoad) {
+ if (Thread.currentThread() != this.mainThread) {
+ throw new IllegalStateException();
+ }
+ ChunkPos chunkPos = new ChunkPos(x, z);
+ Long identifier = this.chunkFutureAwaitCounter++;
+ this.distanceManager.addTicket(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+ this.runDistanceManagerUpdates();
+
+ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong());
+
+ if (chunk == null) {
+ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.level.getWorld().getName() + "'");
+ }
+
+ CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> future = futureGet.apply(chunk);
+
+ future.whenCompleteAsync((either, throwable) -> {
+ try {
+ if (throwable != null) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", throwable);
+ } else if (either.right().isPresent()) {
+ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "': " + either.right().get().toString());
+ }
+
+ try {
+ if (onLoad != null) {
+ onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
+ }
+ } catch (Throwable thr) {
+ if (thr instanceof ThreadDeath) {
+ throw (ThreadDeath)thr;
+ }
+ net.minecraft.server.MinecraftServer.LOGGER.error("Load callback for future await failed " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", thr);
+ return;
+ }
+ } finally {
+ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback.
+ ServerChunkCache.this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
+ ServerChunkCache.this.distanceManager.removeTicket(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+ }
+ }, this.mainThreadProcessor);
+ net.minecraft.server.ChunkSystem.scheduleTickingState(
+ this.level, x, z, ChunkHolder.FullChunkStatus.BORDER, true,
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, onLoad
+ );
+ }
+ // Paper end
+
@ -5739,67 +6063,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ }
+
+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
+ java.util.function.Consumer<ChunkAccess> consumer) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, ticketLevel, (ChunkHolder chunkHolder) -> {
+ if (ticketLevel <= 33) {
+ return (CompletableFuture)chunkHolder.getFullChunkFuture();
+ } else {
+ return chunkHolder.getOrScheduleFuture(ChunkHolder.getStatus(ticketLevel), ServerChunkCache.this.chunkMap);
+ }
+ }, consumer);
+ }
+
+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
+ java.util.function.Function<ChunkHolder, CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>> function,
+ java.util.function.Consumer<ChunkAccess> consumer) {
+ if (Thread.currentThread() != this.mainThread) {
+ throw new IllegalStateException();
+ }
+
+ ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
+ Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++);
+ this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+ this.runDistanceManagerUpdates();
+
+ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong());
+
+ if (chunk == null) {
+ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.level.getWorld().getName() + "'");
+ }
+
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = function.apply(chunk);
+
+ future.whenCompleteAsync((either, throwable) -> {
+ try {
+ if (throwable != null) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ LOGGER.error("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", throwable);
+ } else if (either.right().isPresent()) {
+ LOGGER.error("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "': " + either.right().get().toString());
+ }
+
+ try {
+ if (consumer != null) {
+ consumer.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback.
+ }
+ } catch (Throwable thr) {
+ if (thr instanceof ThreadDeath) {
+ throw (ThreadDeath)thr;
+ }
+ LOGGER.error("Load callback for future await failed " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", thr);
+ return;
+ }
+ } finally {
+ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback.
+ ServerChunkCache.this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos);
+ ServerChunkCache.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier);
+ }
+ }, this.mainThreadProcessor);
+ }
+
+ public <T> void addTicketAtLevel(TicketType<T> ticketType, ChunkPos chunkPos, int ticketLevel, T identifier) {
+ this.distanceManager.addTicket(ticketType, chunkPos, ticketLevel, identifier);
+ }
@ -5808,74 +6071,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.distanceManager.removeTicket(ticketType, chunkPos, ticketLevel, identifier);
+ }
+
+ void chunkLoadAccept(int chunkX, int chunkZ, ChunkAccess chunk, java.util.function.Consumer<ChunkAccess> consumer) {
+ try {
+ consumer.accept(chunk);
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.level.getWorld().getName() + "' threw an exception", throwable);
+ }
+ }
+
+ public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer<ChunkAccess> onLoad) {
+ // try to fire sync
+ int chunkStatusTicketLevel = 33 + ChunkStatus.getDistance(status);
+ ChunkHolder playerChunk = this.chunkMap.getUpdatingChunkIfPresent(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+ if (playerChunk != null) {
+ ChunkStatus holderStatus = playerChunk.getChunkHolderStatus();
+ ChunkAccess immediate = playerChunk.getAvailableChunkNow();
+ if (immediate != null) {
+ if (allowSubTicketLevel ? immediate.getStatus().isOrAfter(status) : (playerChunk.getTicketLevel() <= chunkStatusTicketLevel && holderStatus != null && holderStatus.isOrAfter(status))) {
+ this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad);
+ return;
+ } else {
+ if (gen || (!allowSubTicketLevel && immediate.getStatus().isOrAfter(status))) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ } else {
+ this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
+ return;
+ }
+ }
+ }
+ }
+
+ // need to fire async
+
+ if (gen && !allowSubTicketLevel) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ }
+
+ this.getChunkAtAsynchronously(chunkX, chunkZ, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.EMPTY), (ChunkAccess chunk) -> {
+ if (chunk == null) {
+ throw new IllegalStateException("Chunk cannot be null");
+ }
+
+ if (!chunk.getStatus().isOrAfter(status)) {
+ if (gen) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ } else {
+ ServerChunkCache.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
+ return;
+ }
+ } else {
+ if (allowSubTicketLevel) {
+ ServerChunkCache.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad);
+ return;
+ } else {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ }
+ }
+ });
+ }
+
+ final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> tickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> tickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<LevelChunk> entityTickingChunks = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true);
+ // Paper end
public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
@ -5989,11 +6186,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return true;
+ }
+
+ public final void loadChunksForMoveAsync(AABB axisalignedbb, double toX, double toZ,
+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority,
+ java.util.function.Consumer<List<net.minecraft.world.level.chunk.ChunkAccess>> onLoad) {
+ if (Thread.currentThread() != this.thread) {
+ this.getChunkSource().mainThreadProcessor.execute(() -> {
+ this.loadChunksForMoveAsync(axisalignedbb, toX, toZ, onLoad);
+ this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad);
+ });
+ return;
+ }
@ -6043,7 +6240,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
+ chunkProvider.getChunkAtAsynchronously(cx, cz, net.minecraft.world.level.chunk.ChunkStatus.FULL, true, false, consumer);
+ net.minecraft.server.ChunkSystem.scheduleChunkLoad(
+ this, cx, cz, net.minecraft.world.level.chunk.ChunkStatus.FULL, true, priority, consumer
+ );
+ }
+ }
+ }
@ -6790,6 +6989,128 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@Override
public BlockState getBlockState(BlockPos pos) {
int i = pos.getY();
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<T extends EntityAccess> implements A
}
private boolean addEntity(T entity, boolean existing) {
+ // Paper start - chunk system hooks
+ if (existing) {
+ // I don't want to know why this is a generic type.
+ Entity entityCasted = (Entity)entity;
+ boolean wasRemoved = entityCasted.isRemoved();
+ net.minecraft.server.ChunkSystem.onEntityPreAdd((net.minecraft.server.level.ServerLevel)entityCasted.level, entityCasted);
+ if (!wasRemoved && entityCasted.isRemoved()) {
+ // removed by callback
+ return false;
+ }
+ }
+ // Paper end - chunk system hooks
if (!this.addEntityUuid(entity)) {
return false;
} else {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public Chunk[] getLoadedChunks() {
- Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = this.world.getChunkSource().chunkMap.visibleChunkMap;
- return chunks.values().stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
+ List<ChunkHolder> chunks = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.world); // Paper
+ return chunks.stream().map(ChunkHolder::getFullChunkNow).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new);
}
@Override
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean refreshChunk(int x, int z) {
- ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.visibleChunkMap.get(ChunkPos.asLong(x, z));
+ ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
if (playerChunk == null) return false;
playerChunk.getTickingChunkFuture().thenAccept(either -> {
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return this.spigot;
}
// Spigot end
+ // Paper start
+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(int x, int z, boolean gen, boolean urgent) {
+ if (Bukkit.isPrimaryThread()) {
+ net.minecraft.world.level.chunk.LevelChunk immediate = this.world.getChunkSource().getChunkAtIfLoadedImmediately(x, z);
+ if (immediate != null) {
+ return java.util.concurrent.CompletableFuture.completedFuture(immediate.getBukkitChunk());
+ }
+ }
+
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority;
+ if (urgent) {
+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER;
+ } else {
+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL;
+ }
+
+ java.util.concurrent.CompletableFuture<Chunk> ret = new java.util.concurrent.CompletableFuture<>();
+
+ net.minecraft.server.ChunkSystem.scheduleChunkLoad(this.getHandle(), x, z, gen, ChunkStatus.FULL, true, priority, (c) -> {
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)c;
+ ret.complete(chunk == null ? null : chunk.getBukkitChunk());
+ });
+ });
+
+ return ret;
+ }
+ // Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -0,0 +0,0 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return this.spigot;
}
// Spigot end
+
+ // Paper start
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location location, TeleportCause cause) {
+ Preconditions.checkArgument(location != null, "location");
+ location.checkFinite();
+ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call.
+
+ net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle();
+ java.util.concurrent.CompletableFuture<Boolean> ret = new java.util.concurrent.CompletableFuture<>();
+
+ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()),
+ this instanceof CraftPlayer ? ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER : ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, (list) -> {
+ net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource();
+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) {
+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId());
+ }
+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> {
+ try {
+ ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE);
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to teleport entity " + CraftEntity.this, throwable);
+ ret.completeExceptionally(throwable);
+ }
+ });
+ });
+
+ return ret;
+ }
+ // Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java

View file

@ -24,7 +24,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
-
for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
long j = longiterator.nextLong();
ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
}

View file

@ -88,9 +88,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
}
// Paper start - Chunk priority
@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) {
+ // Paper start - Teleport API

View file

@ -14,6 +14,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+package io.papermc.paper.util;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import org.bukkit.Bukkit;
+import java.util.concurrent.atomic.AtomicInteger;
@ -42,15 +43,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ }
+
+ public static void ensureTickThread(final int chunkX, final int chunkZ, final String reason) {
+ if (!isTickThreadFor(chunkX, chunkZ)) {
+ public static void ensureTickThread(final ServerLevel world, final int chunkX, final int chunkZ, final String reason) {
+ if (!isTickThreadFor(world, chunkX, chunkZ)) {
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
+ throw new IllegalStateException(reason);
+ }
+ }
+
+ public static void ensureTickThread(final Entity entity, final String reason) {
+ if (!isTickThreadFor(entity.chunkPosition().x, entity.chunkPosition().z)) {
+ if (!isTickThreadFor(entity)) {
+ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable());
+ throw new IllegalStateException(reason);
+ }
@ -81,7 +82,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return Bukkit.isPrimaryThread();
+ }
+
+ public static boolean isTickThreadFor(final int chunkX, final int chunkZ) {
+ public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ) {
+ return Bukkit.isPrimaryThread();
+ }
+

View file

@ -79,8 +79,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- }
+ // Paper - move into try block to only write if successfully serialized
}
// Paper start
// Paper start
return;
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
}

View file

@ -10,9 +10,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 {
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 anyPlayerCloseEnoughForSpawning
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.add(this);
@ -20,17 +20,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end - optimise chunk tick iteration
}
void onChunkRemove() {
public void onChunkRemove() {
@@ -0,0 +0,0 @@ public class ChunkHolder {
this.playersInMobSpawnRange = null;
this.playersInChunkTickRange = null;
// Paper end - optimise anyPlayerCloseEnoughForSpawning
+ // Paper start - optimise chunk tick iteration
+ if (this.needsBroadcastChanges()) {
+ this.chunkMap.needsChangeBroadcasting.remove(this);
+ }
+ // Paper end - optimise chunk tick iteration
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
long lastAutoSaveTime; // Paper - incremental autosave
// Paper end
@@ -0,0 +0,0 @@ public class ChunkHolder {
if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
@ -158,13 +160,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
+ // Paper start - optimise chunk tick iteration
+ }
}
+ }
+
+ } finally {
+ if (iterator1 instanceof io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator safeIterator) {
+ safeIterator.finishedIterating();
+ }
+ }
}
+ // Paper end - optimise chunk tick iteration
this.level.timings.chunkTicks.stopTiming(); // Paper
gameprofilerfiller.popPush("customSpawners");

View file

@ -24,7 +24,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end - optimise checkDespawn
}
void onChunkRemove() {
public void onChunkRemove() {
@@ -0,0 +0,0 @@ public class ChunkHolder {
this.chunkMap.needsChangeBroadcasting.remove(this);
}
@ -36,8 +36,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ // Paper end - optimise checkDespawn
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
long lastAutoSaveTime; // Paper - incremental autosave
// Paper end
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

View file

@ -10,37 +10,35 @@ 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 {
boolean isUpdateQueued = false; // Paper
private final ChunkMap chunkMap; // Paper
// Paper start
public void onChunkAdd() {
-
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ 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 anyPlayerCloseEnoughForSpawning
}
public void onChunkRemove() {
-
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ this.playersInMobSpawnRange = null;
+ this.playersInChunkTickRange = null;
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
}
// Paper end
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ // cached here to avoid a map lookup
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
+
+ void onChunkAdd() {
+ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
+ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
+ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ }
+
+ void onChunkRemove() {
+ this.playersInMobSpawnRange = null;
+ this.playersInChunkTickRange = null;
+ }
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+
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.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning
}
// Paper 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
@ -127,22 +125,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
protected ChunkGenerator generator() {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
holder = (ChunkHolder) this.pendingUnloads.remove(pos);
if (holder != null) {
holder.setTicketLevel(level);
+ holder.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning - PUT HERE AFTER RE-ADDING ONLY
} else {
holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
// Paper start
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
if (playerchunk != null) {
+ playerchunk.onChunkRemove(); // Paper
this.pendingUnloads.put(j, playerchunk);
this.modified = true;
++i;
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return this.anyPlayerCloseEnoughForSpawning(pos, false);
}
@ -243,8 +225,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
- this.naturalSpawnChunkCounter.runAllUpdates();
+ //this.f.a(); // Paper - no longer used
this.tickingTicketsTracker.runAllUpdates();
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
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();

View file

@ -42,9 +42,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ @Override
+ public int getTileEntityCount() {
+ // We don't use the full world tile entity list, so we must iterate chunks
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> chunks = world.getChunkSource().chunkMap.visibleChunkMap;
+ int size = 0;
+ for (ChunkHolder playerchunk : chunks.values()) {
+ for (ChunkHolder playerchunk : net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.world)) {
+ net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk();
+ if (chunk == null) {
+ continue;
@ -63,7 +62,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ public int getChunkCount() {
+ int ret = 0;
+
+ for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) {
+ for (ChunkHolder chunkHolder : net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.world)) {
+ if (chunkHolder.getTickingChunk() != null) {
+ ++ret;
+ }

View file

@ -1284,7 +1284,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
worldData.addProperty("visible-chunk-count", visibleChunks.size());
worldData.addProperty("visible-chunk-count", allChunks.size());
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
@ -1305,8 +1305,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ // Paper end - no-tick view distance
// Paper start - optimise anyPlayerCloseEnoughForSpawning
// cached here to avoid a map lookup
// Paper start
public void onChunkAdd() {
@@ -0,0 +0,0 @@ public class ChunkHolder {
public void blockChanged(BlockPos pos) {
@ -1440,7 +1440,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.viewDistance = j;
- this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
- Iterator objectiterator = this.updatingChunks.getVisibleValuesCopy().iterator(); // Paper
- Iterator objectiterator = net.minecraft.server.ChunkSystem.getUpdatingChunkHolders(this.level).iterator(); // Paper
-
- while (objectiterator.hasNext()) {
- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
@ -1484,7 +1484,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
- TickingTracker tickingtracker = this.distanceManager.tickingTracker();
+ // Paper - replace loader system
ObjectBidirectionalIterator objectbidirectionaliterator = this.updatingChunks.getVisibleMap().clone().long2ObjectEntrySet().fastIterator(); // Paper
Iterator<ChunkHolder> objectbidirectionaliterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
while (objectbidirectionaliterator.hasNext()) {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider

View file

@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable {
if (regionfile != null) {
// Paper end
return regionfile;
} else {
- if (this.regionCache.size() >= 256) {

View file

@ -99,6 +99,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
log.log( Level.SEVERE, "------------------------------" );
- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" );
+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
log.log( Level.SEVERE, "------------------------------" );
//

View file

@ -87,8 +87,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
return this.world.getChunk(x >> 4, z >> 4).getHeight(CraftHeightMap.toNMS(heightMap), x, z);
}
@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World {
// Spigot end
// Paper start
@Override
public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(int x, int z, boolean gen, boolean urgent) {
+ warnUnsafeChunk("getting a faraway chunk async", x, z); // Paper
if (Bukkit.isPrimaryThread()) {

View file

@ -304,9 +304,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
protected ChunkGenerator generator() {
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
});
}
}
// Paper end
+ // Paper start
+ public void updatePlayerMobTypeMap(Entity entity) {
+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) {
@ -331,10 +331,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return entityPlayer.mobCounts[mobCategory.ordinal()];
+ }
+ // Paper end
+
private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
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
@ -398,8 +397,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.maxHealthCache = this.getMaxHealth();
+ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
}
// Yes, this doesn't match Vanilla, but it's the best we can do for now.
// Paper start - Chunk priority
public BlockPos getPointInFront(double inFront) {
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java

View file

@ -57,8 +57,8 @@ 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.playersInChunkTickRange = null;
}
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
// Paper end - optimise anyPlayerCloseEnoughForSpawning
+ long lastAutoSaveTime; // Paper - incremental autosave
+ long inactiveTimeStart; // Paper - incremental autosave
@ -107,18 +107,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ }
+ // Paper end
}
+ }
+
+ // Paper start - incremental autosave
+ public boolean setHasBeenLoaded() {
+ this.wasAccessibleSinceLastSave = getFullChunkStatus(this.ticketLevel).isOrAfter(ChunkHolder.FullChunkStatus.BORDER);
+ return this.wasAccessibleSinceLastSave;
+ }
}
+ // Paper end
+
public void replaceProtoChunk(ImposterProtoChunk chunk) {
for (int i = 0; i < this.futures.length(); ++i) {
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(i);
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
@ -194,13 +193,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // Paper end
+
protected void saveAllChunks(boolean flush) {
if (flush) {
List<ChunkHolder> list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
// Paper start - do not overload I/O threads with too much work when saving
int[] saved = new int[1];
@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
int l = 0;
- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator();
- Iterator objectiterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
-
- while (l < 20 && shouldKeepTicking.getAsBoolean() && objectiterator.hasNext()) {
- if (this.saveChunkIfNeeded((ChunkHolder) objectiterator.next())) {