mirror of
https://github.com/PaperMC/Folia.git
synced 2024-07-06 12:16:32 +02:00
ce49776448
Perform thread checks on the chunk send and warn when the world is mismatched. I suspect that the world mismatches for an unknown reason, but need to confirm it to chase it down.
2351 lines
120 KiB
Diff
2351 lines
120 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
Date: Wed, 1 Feb 2023 21:06:31 -0800
|
|
Subject: [PATCH] New player chunk loader system
|
|
|
|
|
|
diff --git a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java
|
|
index 26fa2caa18a9194e57574a4a7fa9f7a4265740e0..5aa1c61a8a82c91514d35df62db7f4bd67aa27a1 100644
|
|
--- a/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java
|
|
+++ b/src/main/java/ca/spottedleaf/concurrentutil/executor/standard/PrioritisedThreadPool.java
|
|
@@ -71,6 +71,10 @@ public final class PrioritisedThreadPool {
|
|
return Arrays.copyOf(this.threads, this.threads.length, Thread[].class);
|
|
}
|
|
|
|
+ public int getThreadCount() {
|
|
+ return this.threads.length;
|
|
+ }
|
|
+
|
|
public PrioritisedPoolExecutor createExecutor(final String name, final int parallelism) {
|
|
synchronized (this.nonShutdownQueues) {
|
|
if (this.shutdown) {
|
|
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index c07eb451a576811a39021f6f97103c77488fd001..a2f71a6d1a9e98133dff6cd0f625da9435a8af14 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -164,9 +164,9 @@ public class TimingsExport extends Thread {
|
|
return pair(rule, world.getWorld().getGameRuleValue(rule));
|
|
})),
|
|
// Paper start - replace chunk loader system
|
|
- pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()),
|
|
- pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()),
|
|
- pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())
|
|
+ pair("ticking-distance", world.getWorld().getSimulationDistance()),
|
|
+ pair("no-ticking-distance", world.getWorld().getViewDistance()),
|
|
+ pair("sending-distance", world.getWorld().getSendViewDistance())
|
|
// Paper end - replace chunk loader system
|
|
));
|
|
}));
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
|
|
index 0e45a340ae534caf676b7f9d0adcbcee5829925e..61d03808c8d1ab822d9b2f31fab0de14089a3b15 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
|
|
@@ -129,15 +129,15 @@ public final class ChunkSystem {
|
|
}
|
|
|
|
public static int getSendViewDistance(final ServerPlayer player) {
|
|
- return io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player);
|
|
+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
|
|
}
|
|
|
|
public static int getLoadViewDistance(final ServerPlayer player) {
|
|
- return io.papermc.paper.chunk.PlayerChunkLoader.getLoadViewDistance(player);
|
|
+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getLoadViewDistance(player);
|
|
}
|
|
|
|
public static int getTickViewDistance(final ServerPlayer player) {
|
|
- return io.papermc.paper.chunk.PlayerChunkLoader.getTickViewDistance(player);
|
|
+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(player);
|
|
}
|
|
|
|
private ChunkSystem() {
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..63c69c4da5fcbd5901c9fc3427f69626e16492ee
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java
|
|
@@ -0,0 +1,1332 @@
|
|
+package io.papermc.paper.chunk.system;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.collection.SRSWLinkedQueue;
|
|
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
|
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
|
+import io.papermc.paper.chunk.system.io.RegionFileIOThread;
|
|
+import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager;
|
|
+import io.papermc.paper.configuration.GlobalConfiguration;
|
|
+import io.papermc.paper.util.CoordinateUtils;
|
|
+import io.papermc.paper.util.IntegerUtil;
|
|
+import io.papermc.paper.util.TickThread;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
+import it.unimi.dsi.fastutil.longs.LongComparator;
|
|
+import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
+import net.minecraft.network.protocol.Packet;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
|
|
+import net.minecraft.server.level.ChunkMap;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.server.level.TicketType;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.GameRules;
|
|
+import net.minecraft.world.level.chunk.ChunkAccess;
|
|
+import net.minecraft.world.level.chunk.ChunkStatus;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
|
+import org.apache.commons.lang3.mutable.MutableObject;
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
+import org.bukkit.entity.Player;
|
|
+import java.lang.invoke.VarHandle;
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
+
|
|
+public class RegionizedPlayerChunkLoader {
|
|
+
|
|
+ public static final TicketType<Long> REGION_PLAYER_TICKET = TicketType.create("region_player_ticket", Long::compareTo);
|
|
+
|
|
+ public static final int MIN_VIEW_DISTANCE = 2;
|
|
+ public static final int MAX_VIEW_DISTANCE = 32;
|
|
+
|
|
+ public static final int TICK_TICKET_LEVEL = 31;
|
|
+ public static final int GENERATED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL);
|
|
+ public static final int LOADED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.EMPTY);
|
|
+
|
|
+ public static final record ViewDistances(
|
|
+ int tickViewDistance,
|
|
+ int loadViewDistance,
|
|
+ int sendViewDistance
|
|
+ ) {
|
|
+ public ViewDistances setTickViewDistance(final int distance) {
|
|
+ return new ViewDistances(distance, this.loadViewDistance, this.sendViewDistance);
|
|
+ }
|
|
+
|
|
+ public ViewDistances setLoadViewDistance(final int distance) {
|
|
+ return new ViewDistances(this.tickViewDistance, distance, this.sendViewDistance);
|
|
+ }
|
|
+
|
|
+
|
|
+ public ViewDistances setSendViewDistance(final int distance) {
|
|
+ return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static int getAPITickViewDistance(final Player player) {
|
|
+ return getAPITickViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ public static int getAPITickViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level;
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPITickDistance();
|
|
+ }
|
|
+ return data.lastTickDistance;
|
|
+ }
|
|
+
|
|
+ public static int getAPIViewDistance(final Player player) {
|
|
+ return getAPIViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ public static int getAPIViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level;
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPIViewDistance();
|
|
+ }
|
|
+ // view distance = load distance + 1
|
|
+ return data.lastLoadDistance - 1;
|
|
+ }
|
|
+
|
|
+ public static int getLoadViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level;
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPIViewDistance();
|
|
+ }
|
|
+ // view distance = load distance + 1
|
|
+ return data.lastLoadDistance - 1;
|
|
+ }
|
|
+
|
|
+ public static int getAPISendViewDistance(final Player player) {
|
|
+ return getAPISendViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ public static int getAPISendViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level;
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPISendViewDistance();
|
|
+ }
|
|
+ return data.lastSendDistance;
|
|
+ }
|
|
+
|
|
+ private final ServerLevel world;
|
|
+
|
|
+ public RegionizedPlayerChunkLoader(final ServerLevel world) {
|
|
+ this.world = world;
|
|
+ }
|
|
+
|
|
+ public void addPlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread(player, "Cannot add player to player chunk loader async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (player.chunkLoader != null) {
|
|
+ throw new IllegalStateException("Player is already added to player chunk loader");
|
|
+ }
|
|
+
|
|
+ final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player);
|
|
+
|
|
+ player.chunkLoader = loader;
|
|
+ loader.add();
|
|
+ }
|
|
+
|
|
+ public void updatePlayer(final ServerPlayer player) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader != null) {
|
|
+ loader.update();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread(player, "Cannot remove player from player chunk loader async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+
|
|
+ if (loader == null) {
|
|
+ throw new IllegalStateException("Player is already removed from player chunk loader");
|
|
+ }
|
|
+
|
|
+ loader.remove();
|
|
+ player.chunkLoader = null;
|
|
+ }
|
|
+
|
|
+ public void setSendDistance(final int distance) {
|
|
+ this.world.setSendViewDistance(distance);
|
|
+ }
|
|
+
|
|
+ public void setLoadDistance(final int distance) {
|
|
+ this.world.setLoadViewDistance(distance);
|
|
+ }
|
|
+
|
|
+ public void setTickDistance(final int distance) {
|
|
+ this.world.setTickViewDistance(distance);
|
|
+ }
|
|
+
|
|
+ // Note: follow the player chunk loader so everything stays consistent...
|
|
+ public int getAPITickDistance() {
|
|
+ final ViewDistances distances = this.world.getViewDistances();
|
|
+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance);
|
|
+ return tickViewDistance;
|
|
+ }
|
|
+
|
|
+ public int getAPIViewDistance() {
|
|
+ final ViewDistances distances = this.world.getViewDistances();
|
|
+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance);
|
|
+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance);
|
|
+
|
|
+ // loadDistance = api view distance + 1
|
|
+ return loadDistance - 1;
|
|
+ }
|
|
+
|
|
+ public int getAPISendViewDistance() {
|
|
+ final ViewDistances distances = this.world.getViewDistances();
|
|
+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance);
|
|
+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance);
|
|
+ final int sendViewDistance = PlayerChunkLoaderData.getSendViewDistance(
|
|
+ loadDistance, -1, -1, distances.sendViewDistance
|
|
+ );
|
|
+
|
|
+ return sendViewDistance;
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) {
|
|
+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return loader.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if (!loader.sentChunks.contains(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ))) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public void tick() {
|
|
+ TickThread.ensureTickThread("Cannot tick player chunk loader async");
|
|
+ long currTime = System.nanoTime();
|
|
+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader == null || loader.world != this.world) {
|
|
+ // not our problem anymore
|
|
+ continue;
|
|
+ }
|
|
+ loader.update(); // can't invoke plugin logic
|
|
+ loader.updateQueues(currTime);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static long[] generateBFSOrder(final int radius) {
|
|
+ final LongArrayList chunks = new LongArrayList();
|
|
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
|
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
|
+
|
|
+ seen.add(CoordinateUtils.getChunkKey(0, 0));
|
|
+ queue.enqueue(CoordinateUtils.getChunkKey(0, 0));
|
|
+ while (!queue.isEmpty()) {
|
|
+ final long chunk = queue.dequeueLong();
|
|
+ final int chunkX = CoordinateUtils.getChunkX(chunk);
|
|
+ final int chunkZ = CoordinateUtils.getChunkZ(chunk);
|
|
+
|
|
+ // important that the addition to the list is here, rather than when enqueueing neighbours
|
|
+ // ensures the order is actually kept
|
|
+ chunks.add(chunk);
|
|
+
|
|
+ // -x
|
|
+ final long n1 = CoordinateUtils.getChunkKey(chunkX - 1, chunkZ);
|
|
+ // -z
|
|
+ final long n2 = CoordinateUtils.getChunkKey(chunkX, chunkZ - 1);
|
|
+ // +x
|
|
+ final long n3 = CoordinateUtils.getChunkKey(chunkX + 1, chunkZ);
|
|
+ // +z
|
|
+ final long n4 = CoordinateUtils.getChunkKey(chunkX, chunkZ + 1);
|
|
+
|
|
+ final long[] list = new long[] {n1, n2, n3, n4};
|
|
+
|
|
+ for (final long neighbour : list) {
|
|
+ final int neighbourX = CoordinateUtils.getChunkX(neighbour);
|
|
+ final int neighbourZ = CoordinateUtils.getChunkZ(neighbour);
|
|
+ if (Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)) > radius) {
|
|
+ // don't enqueue out of range
|
|
+ continue;
|
|
+ }
|
|
+ if (!seen.add(neighbour)) {
|
|
+ continue;
|
|
+ }
|
|
+ queue.enqueue(neighbour);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return chunks.toLongArray();
|
|
+ }
|
|
+
|
|
+ public static final class PlayerChunkLoaderData {
|
|
+
|
|
+ private static final AtomicLong ID_GENERATOR = new AtomicLong();
|
|
+ private final long id = ID_GENERATOR.incrementAndGet();
|
|
+ private final Long idBoxed = Long.valueOf(this.id);
|
|
+
|
|
+ // expected that this list returns for a given radius, the set of chunks ordered
|
|
+ // by manhattan distance
|
|
+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[65][];
|
|
+ static {
|
|
+ for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) {
|
|
+ // a BFS around -x, -z, +x, +z will give increasing manhatten distance
|
|
+ SEARCH_RADIUS_ITERATION_LIST[i] = generateBFSOrder(i);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static final long MAX_RATE = 10_000L;
|
|
+
|
|
+ private final ServerPlayer player;
|
|
+ private final ServerLevel world;
|
|
+
|
|
+ private int lastChunkX = Integer.MIN_VALUE;
|
|
+ private int lastChunkZ = Integer.MIN_VALUE;
|
|
+
|
|
+ private int lastSendDistance = Integer.MIN_VALUE;
|
|
+ private int lastLoadDistance = Integer.MIN_VALUE;
|
|
+ private int lastTickDistance = Integer.MIN_VALUE;
|
|
+
|
|
+ private int lastSentChunkCenterX = Integer.MIN_VALUE;
|
|
+ private int lastSentChunkCenterZ = Integer.MIN_VALUE;
|
|
+
|
|
+ private int lastSentChunkRadius = Integer.MIN_VALUE;
|
|
+ private int lastSentSimulationDistance = Integer.MIN_VALUE;
|
|
+
|
|
+ private boolean canGenerateChunks = true;
|
|
+
|
|
+ private final ArrayDeque<ChunkHolderManager.TicketOperation<?, ?>> delayedTicketOps = new ArrayDeque<>();
|
|
+ private final LongOpenHashSet sentChunks = new LongOpenHashSet();
|
|
+
|
|
+ private static final byte CHUNK_TICKET_STAGE_NONE = 0;
|
|
+ private static final byte CHUNK_TICKET_STAGE_LOADING = 1;
|
|
+ private static final byte CHUNK_TICKET_STAGE_LOADED = 2;
|
|
+ private static final byte CHUNK_TICKET_STAGE_GENERATING = 3;
|
|
+ private static final byte CHUNK_TICKET_STAGE_GENERATED = 4;
|
|
+ private static final byte CHUNK_TICKET_STAGE_TICK = 5;
|
|
+ private static final int[] TICKET_STAGE_TO_LEVEL = new int[] {
|
|
+ ChunkHolderManager.MAX_TICKET_LEVEL + 1,
|
|
+ LOADED_TICKET_LEVEL,
|
|
+ LOADED_TICKET_LEVEL,
|
|
+ GENERATED_TICKET_LEVEL,
|
|
+ GENERATED_TICKET_LEVEL,
|
|
+ TICK_TICKET_LEVEL
|
|
+ };
|
|
+ private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap();
|
|
+ {
|
|
+ this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE);
|
|
+ }
|
|
+
|
|
+ // rate limiting
|
|
+ private final AllocatingRateLimiter chunkSendLimiter = new AllocatingRateLimiter();
|
|
+ private final AllocatingRateLimiter chunkLoadTicketLimiter = new AllocatingRateLimiter();
|
|
+ private final AllocatingRateLimiter chunkGenerateTicketLimiter = new AllocatingRateLimiter();
|
|
+
|
|
+ // queues
|
|
+ private final LongComparator CLOSEST_MANHATTAN_DIST = (final long c1, final long c2) -> {
|
|
+ final int c1x = CoordinateUtils.getChunkX(c1);
|
|
+ final int c1z = CoordinateUtils.getChunkZ(c1);
|
|
+
|
|
+ final int c2x = CoordinateUtils.getChunkX(c2);
|
|
+ final int c2z = CoordinateUtils.getChunkZ(c2);
|
|
+
|
|
+ final int centerX = PlayerChunkLoaderData.this.lastChunkX;
|
|
+ final int centerZ = PlayerChunkLoaderData.this.lastChunkZ;
|
|
+
|
|
+ return Integer.compare(
|
|
+ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ),
|
|
+ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ)
|
|
+ );
|
|
+ };
|
|
+ private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue generatingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue genQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue loadingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue loadQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+
|
|
+ private volatile boolean removed;
|
|
+
|
|
+ public PlayerChunkLoaderData(final ServerLevel world, final ServerPlayer player) {
|
|
+ this.world = world;
|
|
+ this.player = player;
|
|
+ }
|
|
+
|
|
+ private void flushDelayedTicketOps() {
|
|
+ if (this.delayedTicketOps.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.pushDelayedTicketUpdates(this.delayedTicketOps);
|
|
+ this.delayedTicketOps.clear();
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.tryDrainTicketUpdates();
|
|
+ }
|
|
+
|
|
+ private void pushDelayedTicketOp(final ChunkHolderManager.TicketOperation<?, ?> op) {
|
|
+ this.delayedTicketOps.addLast(op);
|
|
+ }
|
|
+
|
|
+ private void sendChunk(final int chunkX, final int chunkZ) {
|
|
+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
+ this.world.getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded
|
|
+ return;
|
|
+ }
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ private void sendUnloadChunk(final int chunkX, final int chunkZ) {
|
|
+ if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
+ return;
|
|
+ }
|
|
+ this.sendUnloadChunkRaw(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) {
|
|
+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded
|
|
+ }
|
|
+
|
|
+ private final SingleUserAreaMap<PlayerChunkLoaderData> broadcastMap = new SingleUserAreaMap<>(this) {
|
|
+ @Override
|
|
+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) {
|
|
+ // do nothing, we only care about remove
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) {
|
|
+ parameter.sendUnloadChunk(chunkX, chunkZ);
|
|
+ }
|
|
+ };
|
|
+ private final SingleUserAreaMap<PlayerChunkLoaderData> loadTicketCleanup = new SingleUserAreaMap<>(this) {
|
|
+ @Override
|
|
+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) {
|
|
+ // do nothing, we only care about remove
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) {
|
|
+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+ final byte ticketStage = parameter.chunkTicketStage.remove(chunk);
|
|
+ final int level = TICKET_STAGE_TO_LEVEL[ticketStage];
|
|
+ if (level > ChunkHolderManager.MAX_TICKET_LEVEL) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove(
|
|
+ chunk,
|
|
+ TicketType.UNKNOWN, level, new ChunkPos(chunkX, chunkZ),
|
|
+ REGION_PLAYER_TICKET, level, parameter.idBoxed
|
|
+ ));
|
|
+ }
|
|
+ };
|
|
+ private final SingleUserAreaMap<PlayerChunkLoaderData> tickMap = new SingleUserAreaMap<>(this) {
|
|
+ @Override
|
|
+ protected void addCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) {
|
|
+ // do nothing, we will detect ticking chunks when we try to load them
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ protected void removeCallback(final PlayerChunkLoaderData parameter, final int chunkX, final int chunkZ) {
|
|
+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+ // note: by the time this is called, the tick cleanup should have ran - so, if the chunk is at
|
|
+ // the tick stage it was deemed in range for loading. Thus, we need to move it to generated
|
|
+ if (!parameter.chunkTicketStage.replace(chunk, CHUNK_TICKET_STAGE_TICK, CHUNK_TICKET_STAGE_GENERATED)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // Since we are possibly downgrading the ticket level, we add an unknown ticket so that
|
|
+ // the level is kept until tick().
|
|
+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addAndRemove(
|
|
+ chunk,
|
|
+ TicketType.UNKNOWN, TICK_TICKET_LEVEL, new ChunkPos(chunkX, chunkZ),
|
|
+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, parameter.idBoxed
|
|
+ ));
|
|
+ // keep chunk at new generated level
|
|
+ parameter.pushDelayedTicketOp(ChunkHolderManager.TicketOperation.addOp(
|
|
+ chunk,
|
|
+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, parameter.idBoxed
|
|
+ ));
|
|
+ }
|
|
+ };
|
|
+
|
|
+ private static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ,
|
|
+ final int sendRadius) {
|
|
+ // expect sendRadius to be = 1 + target viewable radius
|
|
+ return ChunkMap.isChunkInRange(chunkX, chunkZ, centerX, centerZ, sendRadius);
|
|
+ }
|
|
+
|
|
+ private static int getClientViewDistance(final ServerPlayer player) {
|
|
+ final Integer vd = player.clientViewDistance;
|
|
+ return vd == null ? -1 : Math.max(0, vd.intValue());
|
|
+ }
|
|
+
|
|
+ private static int getTickDistance(final int playerTickViewDistance, final int worldTickViewDistance) {
|
|
+ return playerTickViewDistance < 0 ? worldTickViewDistance : playerTickViewDistance;
|
|
+ }
|
|
+
|
|
+ private static int getLoadViewDistance(final int tickViewDistance, final int playerLoadViewDistance,
|
|
+ final int worldLoadViewDistance) {
|
|
+ return Math.max(tickViewDistance + 1, playerLoadViewDistance < 0 ? worldLoadViewDistance : playerLoadViewDistance);
|
|
+ }
|
|
+
|
|
+ private static int getSendViewDistance(final int loadViewDistance, final int clientViewDistance,
|
|
+ final int playerSendViewDistance, final int worldSendViewDistance) {
|
|
+ return Math.min(
|
|
+ loadViewDistance,
|
|
+ playerSendViewDistance < 0 ? (!GlobalConfiguration.get().chunkLoadingAdvanced.autoConfigSendDistance || clientViewDistance < 0 ? (worldSendViewDistance < 0 ? loadViewDistance : worldSendViewDistance) : clientViewDistance + 1) : playerSendViewDistance
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private Packet<?> updateClientChunkRadius(final int radius) {
|
|
+ this.lastSentChunkRadius = radius;
|
|
+ return new ClientboundSetChunkCacheRadiusPacket(radius);
|
|
+ }
|
|
+
|
|
+ private Packet<?> updateClientSimulationDistance(final int distance) {
|
|
+ this.lastSentSimulationDistance = distance;
|
|
+ return new ClientboundSetSimulationDistancePacket(distance);
|
|
+ }
|
|
+
|
|
+ private Packet<?> updateClientChunkCenter(final int chunkX, final int chunkZ) {
|
|
+ this.lastSentChunkCenterX = chunkX;
|
|
+ this.lastSentChunkCenterZ = chunkZ;
|
|
+ return new ClientboundSetChunkCacheCenterPacket(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ private boolean canPlayerGenerateChunks() {
|
|
+ return !this.player.isSpectator() || this.world.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS);
|
|
+ }
|
|
+
|
|
+ private double getMaxChunkLoadRate() {
|
|
+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
|
|
+
|
|
+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate);
|
|
+ }
|
|
+
|
|
+ private double getMaxChunkGenRate() {
|
|
+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
|
|
+
|
|
+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate);
|
|
+ }
|
|
+
|
|
+ private double getMaxChunkSendRate() {
|
|
+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
|
|
+
|
|
+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate);
|
|
+ }
|
|
+
|
|
+ private long getMaxChunkLoads() {
|
|
+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L);
|
|
+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
|
|
+ if (configLimit == 0L) {
|
|
+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active
|
|
+ configLimit = Math.max(5L, radiusChunks / 5L);
|
|
+ } else if (configLimit < 0L) {
|
|
+ configLimit = Integer.MAX_VALUE;
|
|
+ } // else: use the value configured
|
|
+ configLimit = configLimit - this.loadingQueue.size();
|
|
+
|
|
+ return configLimit;
|
|
+ }
|
|
+
|
|
+ private long getMaxChunkGenerates() {
|
|
+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L);
|
|
+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
|
|
+ if (configLimit == 0L) {
|
|
+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active
|
|
+ configLimit = Math.max(5L, radiusChunks / 5L);
|
|
+ } else if (configLimit < 0L) {
|
|
+ configLimit = Integer.MAX_VALUE;
|
|
+ } // else: use the value configured
|
|
+ configLimit = configLimit - this.generatingQueue.size();
|
|
+
|
|
+ return configLimit;
|
|
+ }
|
|
+
|
|
+ private boolean wantChunkSent(final int chunkX, final int chunkZ) {
|
|
+ final int dx = this.lastChunkX - chunkX;
|
|
+ final int dz = this.lastChunkZ - chunkZ;
|
|
+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastSendDistance && wantChunkLoaded(
|
|
+ this.lastChunkX, this.lastChunkZ, chunkX, chunkZ, this.lastSendDistance
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private boolean wantChunkTicked(final int chunkX, final int chunkZ) {
|
|
+ final int dx = this.lastChunkX - chunkX;
|
|
+ final int dz = this.lastChunkZ - chunkZ;
|
|
+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance;
|
|
+ }
|
|
+
|
|
+ void updateQueues(final long time) {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Ticking removed player chunk loader");
|
|
+ }
|
|
+ // update rate limits
|
|
+ final double loadRate = this.getMaxChunkLoadRate();
|
|
+ final double genRate = this.getMaxChunkGenRate();
|
|
+ final double sendRate = this.getMaxChunkSendRate();
|
|
+
|
|
+ this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate);
|
|
+ this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate);
|
|
+ this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate);
|
|
+
|
|
+ // try to progress chunk loads
|
|
+ while (!this.loadingQueue.isEmpty()) {
|
|
+ final long pendingLoadChunk = this.loadingQueue.firstLong();
|
|
+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingLoadChunk);
|
|
+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingLoadChunk);
|
|
+ final ChunkAccess pending = this.world.chunkSource.getChunkAtImmediately(pendingChunkX, pendingChunkZ);
|
|
+ if (pending == null) {
|
|
+ // nothing to do here
|
|
+ break;
|
|
+ }
|
|
+ // chunk has loaded, so we can take it out of the queue
|
|
+ this.loadingQueue.dequeueLong();
|
|
+
|
|
+ // try to move to generate queue
|
|
+ final byte prev = this.chunkTicketStage.put(pendingLoadChunk, CHUNK_TICKET_STAGE_LOADED);
|
|
+ if (prev != CHUNK_TICKET_STAGE_LOADING) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADING + ", not " + prev);
|
|
+ }
|
|
+
|
|
+ if (this.canGenerateChunks || this.isLoadedChunkGeneratable(pending)) {
|
|
+ this.genQueue.enqueue(pendingLoadChunk);
|
|
+ } // else: don't want to generate, so just leave it loaded
|
|
+ }
|
|
+
|
|
+ // try to push more chunk loads
|
|
+ final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads())));
|
|
+ final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads);
|
|
+ if (maxLoadsThisTick > 0) {
|
|
+ final LongArrayList chunks = new LongArrayList(maxLoadsThisTick);
|
|
+ for (int i = 0; i < maxLoadsThisTick; ++i) {
|
|
+ final long chunk = this.loadQueue.dequeueLong();
|
|
+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_LOADING);
|
|
+ if (prev != CHUNK_TICKET_STAGE_NONE) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_NONE + ", not " + prev);
|
|
+ }
|
|
+ this.pushDelayedTicketOp(
|
|
+ ChunkHolderManager.TicketOperation.addOp(
|
|
+ chunk,
|
|
+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed
|
|
+ )
|
|
+ );
|
|
+ chunks.add(chunk);
|
|
+ this.loadingQueue.enqueue(chunk);
|
|
+ }
|
|
+
|
|
+ // here we need to flush tickets, as scheduleChunkLoad requires tickets to be propagated with addTicket = false
|
|
+ this.flushDelayedTicketOps();
|
|
+ // we only need to call scheduleChunkLoad because the loaded ticket level is not enough to start the chunk
|
|
+ // load - only generate ticket levels start anything, but they start generation...
|
|
+ // propagate levels
|
|
+ // Note: this CAN call plugin logic, so it is VITAL that our bookkeeping logic is completely done by the time this is invoked
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates();
|
|
+
|
|
+ if (this.removed) {
|
|
+ // process ticket updates may invoke plugin logic, which may remove this player
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (int i = 0; i < maxLoadsThisTick; ++i) {
|
|
+ final long queuedLoadChunk = chunks.getLong(i);
|
|
+ final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk);
|
|
+ final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk);
|
|
+ this.world.chunkTaskScheduler.scheduleChunkLoad(
|
|
+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null
|
|
+ );
|
|
+ if (this.removed) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // try to progress chunk generations
|
|
+ while (!this.generatingQueue.isEmpty()) {
|
|
+ final long pendingGenChunk = this.generatingQueue.firstLong();
|
|
+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingGenChunk);
|
|
+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingGenChunk);
|
|
+ final LevelChunk pending = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingChunkX, pendingChunkZ);
|
|
+ if (pending == null) {
|
|
+ // nothing to do here
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ // chunk has generated, so we can take it out of queue
|
|
+ this.generatingQueue.dequeueLong();
|
|
+
|
|
+ final byte prev = this.chunkTicketStage.put(pendingGenChunk, CHUNK_TICKET_STAGE_GENERATED);
|
|
+ if (prev != CHUNK_TICKET_STAGE_GENERATING) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATING + ", not " + prev);
|
|
+ }
|
|
+
|
|
+ // try to move to send queue
|
|
+ if (this.wantChunkSent(pendingChunkX, pendingChunkZ)) {
|
|
+ this.sendQueue.enqueue(pendingGenChunk);
|
|
+ }
|
|
+ // try to move to tick queue
|
|
+ if (this.wantChunkTicked(pendingChunkX, pendingChunkZ)) {
|
|
+ this.tickingQueue.enqueue(pendingGenChunk);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // try to push more chunk generations
|
|
+ final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates())));
|
|
+ final int maxGensThisTick = (int)this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, maxGens);
|
|
+ for (int i = 0; i < maxGensThisTick; ++i) {
|
|
+ final long chunk = this.genQueue.dequeueLong();
|
|
+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_GENERATING);
|
|
+ if (prev != CHUNK_TICKET_STAGE_LOADED) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADED + ", not " + prev);
|
|
+ }
|
|
+ this.pushDelayedTicketOp(
|
|
+ ChunkHolderManager.TicketOperation.addAndRemove(
|
|
+ chunk,
|
|
+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed,
|
|
+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed
|
|
+ )
|
|
+ );
|
|
+ this.generatingQueue.enqueue(chunk);
|
|
+ }
|
|
+
|
|
+ // try to pull ticking chunks
|
|
+ tick_check_outer:
|
|
+ while (!this.tickingQueue.isEmpty()) {
|
|
+ final long pendingTicking = this.tickingQueue.firstLong();
|
|
+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingTicking);
|
|
+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingTicking);
|
|
+
|
|
+ final int tickingReq = 2;
|
|
+ for (int dz = -tickingReq; dz <= tickingReq; ++dz) {
|
|
+ for (int dx = -tickingReq; dx <= tickingReq; ++dx) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ continue;
|
|
+ }
|
|
+ final long neighbour = CoordinateUtils.getChunkKey(dx + pendingChunkX, dz + pendingChunkZ);
|
|
+ final byte stage = this.chunkTicketStage.get(neighbour);
|
|
+ if (stage != CHUNK_TICKET_STAGE_GENERATED && stage != CHUNK_TICKET_STAGE_TICK) {
|
|
+ break tick_check_outer;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // only gets here if all neighbours were marked as generated or ticking themselves
|
|
+ this.tickingQueue.dequeueLong();
|
|
+ this.pushDelayedTicketOp(
|
|
+ ChunkHolderManager.TicketOperation.addAndRemove(
|
|
+ pendingTicking,
|
|
+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, this.idBoxed,
|
|
+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed
|
|
+ )
|
|
+ );
|
|
+ // there is no queue to add after ticking
|
|
+ final byte prev = this.chunkTicketStage.put(pendingTicking, CHUNK_TICKET_STAGE_TICK);
|
|
+ if (prev != CHUNK_TICKET_STAGE_GENERATED) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATED + ", not " + prev);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // try to pull sending chunks
|
|
+ final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // no logic to track concurrent sends
|
|
+ final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size());
|
|
+ // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it
|
|
+ for (int i = 0; i < maxSendsThisTick; ++i) {
|
|
+ final long pendingSend = this.sendQueue.firstLong();
|
|
+ final int pendingSendX = CoordinateUtils.getChunkX(pendingSend);
|
|
+ final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend);
|
|
+ final LevelChunk chunk = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingSendX, pendingSendZ);
|
|
+ if (!chunk.areNeighboursLoaded(1) || !TickThread.isTickThreadFor(this.world, pendingSendX, pendingSendZ)) {
|
|
+ // nothing to do
|
|
+ // the target chunk may not be owned by this region, but this should be resolved in the future
|
|
+ break;
|
|
+ }
|
|
+ this.sendQueue.dequeueLong();
|
|
+
|
|
+ this.sendChunk(pendingSendX, pendingSendZ);
|
|
+ if (this.removed) {
|
|
+ // sendChunk may invoke plugin logic
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.flushDelayedTicketOps();
|
|
+ // we assume propagate ticket levels happens after this call
|
|
+ }
|
|
+
|
|
+ void add() {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Adding removed player chunk loader");
|
|
+ }
|
|
+ final ViewDistances playerDistances = this.player.getViewDistances();
|
|
+ final ViewDistances worldDistances = this.world.getViewDistances();
|
|
+ final int chunkX = this.player.chunkPosition().x;
|
|
+ final int chunkZ = this.player.chunkPosition().z;
|
|
+
|
|
+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance);
|
|
+ // load view cannot be less-than tick view + 1
|
|
+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance);
|
|
+ // send view cannot be greater-than load view
|
|
+ final int clientViewDistance = getClientViewDistance(this.player);
|
|
+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance);
|
|
+
|
|
+ // send view distances
|
|
+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance));
|
|
+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance));
|
|
+
|
|
+ // add to distance maps
|
|
+ this.broadcastMap.add(chunkX, chunkZ, sendViewDistance);
|
|
+ this.loadTicketCleanup.add(chunkX, chunkZ, loadViewDistance + 1);
|
|
+ this.tickMap.add(chunkX, chunkZ, tickViewDistance);
|
|
+
|
|
+ // update chunk center
|
|
+ this.player.connection.send(this.updateClientChunkCenter(chunkX, chunkZ));
|
|
+
|
|
+ // now we can update
|
|
+ this.update();
|
|
+ }
|
|
+
|
|
+ private boolean isLoadedChunkGeneratable(final int chunkX, final int chunkZ) {
|
|
+ return this.isLoadedChunkGeneratable(this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ private boolean isLoadedChunkGeneratable(final ChunkAccess chunkAccess) {
|
|
+ final BelowZeroRetrogen belowZeroRetrogen;
|
|
+ return chunkAccess != null && (
|
|
+ chunkAccess.getStatus() == ChunkStatus.FULL ||
|
|
+ ((belowZeroRetrogen = chunkAccess.getBelowZeroRetrogen()) != null && belowZeroRetrogen.targetStatus().isOrAfter(ChunkStatus.FULL))
|
|
+ );
|
|
+ }
|
|
+
|
|
+ void update() {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot update player asynchronously");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Updating removed player chunk loader");
|
|
+ }
|
|
+ final ViewDistances playerDistances = this.player.getViewDistances();
|
|
+ final ViewDistances worldDistances = this.world.getViewDistances();
|
|
+
|
|
+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance);
|
|
+ // load view cannot be less-than tick view + 1
|
|
+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance);
|
|
+ // send view cannot be greater-than load view
|
|
+ final int clientViewDistance = getClientViewDistance(this.player);
|
|
+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance);
|
|
+
|
|
+ final ChunkPos playerPos = this.player.chunkPosition();
|
|
+ final boolean canGenerateChunks = this.canPlayerGenerateChunks();
|
|
+ final int currentChunkX = playerPos.x;
|
|
+ final int currentChunkZ = playerPos.z;
|
|
+
|
|
+ final int prevChunkX = this.lastChunkX;
|
|
+ final int prevChunkZ = this.lastChunkZ;
|
|
+
|
|
+ if (
|
|
+ // has view distance stayed the same?
|
|
+ sendViewDistance == this.lastSendDistance
|
|
+ && loadViewDistance == this.lastLoadDistance
|
|
+ && tickViewDistance == this.lastTickDistance
|
|
+
|
|
+ // has our chunk stayed the same?
|
|
+ && prevChunkX == currentChunkX
|
|
+ && prevChunkZ == currentChunkZ
|
|
+
|
|
+ // can we still generate chunks?
|
|
+ && this.canGenerateChunks == canGenerateChunks
|
|
+ ) {
|
|
+ // nothing we care about changed, so we're not re-calculating
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // update distance maps
|
|
+ this.broadcastMap.update(currentChunkX, currentChunkZ, sendViewDistance);
|
|
+ this.loadTicketCleanup.update(currentChunkX, currentChunkZ, loadViewDistance + 1);
|
|
+ this.tickMap.update(currentChunkX, currentChunkZ, tickViewDistance);
|
|
+ if (sendViewDistance > loadViewDistance || tickViewDistance > (loadViewDistance - 1)) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ // update VDs for client
|
|
+ // this should be after the distance map updates, as they will send unload packets
|
|
+ if (this.lastSentChunkRadius != sendViewDistance) {
|
|
+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance));
|
|
+ }
|
|
+ if (this.lastSentSimulationDistance != tickViewDistance) {
|
|
+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance));
|
|
+ }
|
|
+
|
|
+ this.sendQueue.clear();
|
|
+ this.tickingQueue.clear();
|
|
+ this.generatingQueue.clear();
|
|
+ this.genQueue.clear();
|
|
+ this.loadingQueue.clear();
|
|
+ this.loadQueue.clear();
|
|
+
|
|
+ this.lastChunkX = currentChunkX;
|
|
+ this.lastChunkZ = currentChunkZ;
|
|
+ this.lastSendDistance = sendViewDistance;
|
|
+ this.lastLoadDistance = loadViewDistance;
|
|
+ this.lastTickDistance = tickViewDistance;
|
|
+ this.canGenerateChunks = canGenerateChunks;
|
|
+
|
|
+ // +1 since we need to load chunks +1 around the load view distance...
|
|
+ final long[] toIterate = SEARCH_RADIUS_ITERATION_LIST[loadViewDistance + 1];
|
|
+ // the iteration order is by increasing manhattan distance - so, we do NOT need to
|
|
+ // sort anything in the queue!
|
|
+ for (final long deltaChunk : toIterate) {
|
|
+ final int dx = CoordinateUtils.getChunkX(deltaChunk);
|
|
+ final int dz = CoordinateUtils.getChunkZ(deltaChunk);
|
|
+ final int chunkX = dx + currentChunkX;
|
|
+ final int chunkZ = dz + currentChunkZ;
|
|
+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz));
|
|
+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz);
|
|
+
|
|
+ // since chunk sending is not by radius alone, we need an extra check here to account for
|
|
+ // everything <= sendDistance
|
|
+ // Note: Vanilla may want to send chunks outside the send view distance, so we do need
|
|
+ // the dist <= view check
|
|
+ final boolean sendChunk = squareDistance <= sendViewDistance
|
|
+ && wantChunkLoaded(currentChunkX, currentChunkZ, chunkX, chunkZ, sendViewDistance);
|
|
+ final boolean sentChunk = sendChunk ? this.sentChunks.contains(chunk) : this.sentChunks.remove(chunk);
|
|
+
|
|
+ if (!sendChunk && sentChunk) {
|
|
+ // have sent the chunk, but don't want it anymore
|
|
+ // unload it now
|
|
+ this.sendUnloadChunkRaw(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ final byte stage = this.chunkTicketStage.get(chunk);
|
|
+ switch (stage) {
|
|
+ case CHUNK_TICKET_STAGE_NONE: {
|
|
+ // we want the chunk to be at least loaded
|
|
+ this.loadQueue.enqueue(chunk);
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_LOADING: {
|
|
+ this.loadingQueue.enqueue(chunk);
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_LOADED: {
|
|
+ if (canGenerateChunks || this.isLoadedChunkGeneratable(chunkX, chunkZ)) {
|
|
+ this.genQueue.enqueue(chunk);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_GENERATING: {
|
|
+ this.generatingQueue.enqueue(chunk);
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_GENERATED: {
|
|
+ if (sendChunk && !sentChunk) {
|
|
+ this.sendQueue.enqueue(chunk);
|
|
+ }
|
|
+ if (squareDistance <= tickViewDistance) {
|
|
+ this.tickingQueue.enqueue(chunk);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_TICK: {
|
|
+ if (sendChunk && !sentChunk) {
|
|
+ this.sendQueue.enqueue(chunk);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ default: {
|
|
+ throw new IllegalStateException("Unknown stage: " + stage);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // update the chunk center
|
|
+ // this must be done last so that the client does not ignore any of our unload chunk packets above
|
|
+ if (this.lastSentChunkCenterX != currentChunkX || this.lastSentChunkCenterZ != currentChunkZ) {
|
|
+ this.player.connection.send(this.updateClientChunkCenter(currentChunkX, currentChunkZ));
|
|
+ }
|
|
+
|
|
+ this.flushDelayedTicketOps();
|
|
+ }
|
|
+
|
|
+ void remove() {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Removing removed player chunk loader");
|
|
+ }
|
|
+ this.removed = true;
|
|
+ // sends the chunk unload packets
|
|
+ this.broadcastMap.remove();
|
|
+ // cleans up loading/generating tickets
|
|
+ this.loadTicketCleanup.remove();
|
|
+ // cleans up ticking tickets
|
|
+ this.tickMap.remove();
|
|
+
|
|
+ // purge queues
|
|
+ this.sendQueue.clear();
|
|
+ this.tickingQueue.clear();
|
|
+ this.generatingQueue.clear();
|
|
+ this.genQueue.clear();
|
|
+ this.loadingQueue.clear();
|
|
+ this.loadQueue.clear();
|
|
+
|
|
+ // flush ticket changes
|
|
+ this.flushDelayedTicketOps();
|
|
+
|
|
+ // now all tickets should be removed, which is all of our external state
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // TODO rebase into util patch
|
|
+ private static final class AllocatingRateLimiter {
|
|
+
|
|
+ // max difference granularity in ns
|
|
+ private static final long MAX_GRANULARITY = TimeUnit.SECONDS.toNanos(1L);
|
|
+
|
|
+ private double allocation;
|
|
+ private long lastAllocationUpdate;
|
|
+ private double takeCarry;
|
|
+ private long lastTakeUpdate;
|
|
+
|
|
+ // rate in units/s, and time in ns
|
|
+ public void tickAllocation(final long time, final double rate, final double maxAllocation) {
|
|
+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastAllocationUpdate);
|
|
+ this.lastAllocationUpdate = time;
|
|
+
|
|
+ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D));
|
|
+ }
|
|
+
|
|
+ // rate in units/s, and time in ns
|
|
+ public long takeAllocation(final long time, final double rate, final long maxTake) {
|
|
+ if (maxTake < 1L) {
|
|
+ return 0L;
|
|
+ }
|
|
+
|
|
+ double ret = this.takeCarry;
|
|
+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastTakeUpdate);
|
|
+ this.lastTakeUpdate = time;
|
|
+
|
|
+ // note: abs(takeCarry) <= 1.0
|
|
+ final double take = Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), rate * (diff*1.0E-9));
|
|
+
|
|
+ ret += take;
|
|
+ this.allocation -= take;
|
|
+
|
|
+ final long retInteger = (long)Math.floor(ret);
|
|
+ this.takeCarry = ret - (double)retInteger;
|
|
+
|
|
+ return retInteger;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static abstract class SingleUserAreaMap<T> {
|
|
+
|
|
+ private static final int NOT_SET = Integer.MIN_VALUE;
|
|
+
|
|
+ private final T parameter;
|
|
+ private int lastChunkX = NOT_SET;
|
|
+ private int lastChunkZ = NOT_SET;
|
|
+ private int distance = NOT_SET;
|
|
+
|
|
+ public SingleUserAreaMap(final T parameter) {
|
|
+ this.parameter = parameter;
|
|
+ }
|
|
+
|
|
+ /* math sign function except 0 returns 1 */
|
|
+ protected static int sign(int val) {
|
|
+ return 1 | (val >> (Integer.SIZE - 1));
|
|
+ }
|
|
+
|
|
+ protected abstract void addCallback(final T parameter, final int chunkX, final int chunkZ);
|
|
+
|
|
+ protected abstract void removeCallback(final T parameter, final int chunkX, final int chunkZ);
|
|
+
|
|
+ private void addToNew(final T parameter, final int chunkX, final int chunkZ, final int distance) {
|
|
+ final int maxX = chunkX + distance;
|
|
+ final int maxZ = chunkZ + distance;
|
|
+
|
|
+ for (int cx = chunkX - distance; cx <= maxX; ++cx) {
|
|
+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
|
|
+ this.addCallback(parameter, cx, cz);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void removeFromOld(final T parameter, final int chunkX, final int chunkZ, final int distance) {
|
|
+ final int maxX = chunkX + distance;
|
|
+ final int maxZ = chunkZ + distance;
|
|
+
|
|
+ for (int cx = chunkX - distance; cx <= maxX; ++cx) {
|
|
+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
|
|
+ this.removeCallback(parameter, cx, cz);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final boolean add(final int chunkX, final int chunkZ, final int distance) {
|
|
+ if (distance < 0) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ if (this.lastChunkX != NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+ this.lastChunkX = chunkX;
|
|
+ this.lastChunkZ = chunkZ;
|
|
+ this.distance = distance;
|
|
+
|
|
+ this.addToNew(this.parameter, chunkX, chunkZ, distance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final boolean update(final int toX, final int toZ, final int newViewDistance) {
|
|
+ if (newViewDistance < 0) {
|
|
+ throw new IllegalArgumentException(Integer.toString(newViewDistance));
|
|
+ }
|
|
+ final int fromX = this.lastChunkX;
|
|
+ final int fromZ = this.lastChunkZ;
|
|
+ final int oldViewDistance = this.distance;
|
|
+ if (fromX == NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ this.lastChunkX = toX;
|
|
+ this.lastChunkZ = toZ;
|
|
+
|
|
+ final T parameter = this.parameter;
|
|
+
|
|
+
|
|
+ final int dx = toX - fromX;
|
|
+ final int dz = toZ - fromZ;
|
|
+
|
|
+ final int totalX = IntegerUtil.branchlessAbs(fromX - toX);
|
|
+ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ);
|
|
+
|
|
+ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) {
|
|
+ // teleported?
|
|
+ this.removeFromOld(parameter, fromX, fromZ, oldViewDistance);
|
|
+ this.addToNew(parameter, toX, toZ, newViewDistance);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ if (oldViewDistance != newViewDistance) {
|
|
+ // remove loop
|
|
+
|
|
+ final int oldMinX = fromX - oldViewDistance;
|
|
+ final int oldMinZ = fromZ - oldViewDistance;
|
|
+ final int oldMaxX = fromX + oldViewDistance;
|
|
+ final int oldMaxZ = fromZ + oldViewDistance;
|
|
+ for (int currX = oldMinX; currX <= oldMaxX; ++currX) {
|
|
+ for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) {
|
|
+
|
|
+ // only remove if we're outside the new view distance...
|
|
+ if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) {
|
|
+ this.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // add loop
|
|
+
|
|
+ final int newMinX = toX - newViewDistance;
|
|
+ final int newMinZ = toZ - newViewDistance;
|
|
+ final int newMaxX = toX + newViewDistance;
|
|
+ final int newMaxZ = toZ + newViewDistance;
|
|
+ for (int currX = newMinX; currX <= newMaxX; ++currX) {
|
|
+ for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) {
|
|
+
|
|
+ // only add if we're outside the old view distance...
|
|
+ if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) {
|
|
+ this.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // x axis is width
|
|
+ // z axis is height
|
|
+ // right refers to the x axis of where we moved
|
|
+ // top refers to the z axis of where we moved
|
|
+
|
|
+ // same view distance
|
|
+
|
|
+ // used for relative positioning
|
|
+ final int up = sign(dz); // 1 if dz >= 0, -1 otherwise
|
|
+ final int right = sign(dx); // 1 if dx >= 0, -1 otherwise
|
|
+
|
|
+ // The area excluded by overlapping the two view distance squares creates four rectangles:
|
|
+ // Two on the left, and two on the right. The ones on the left we consider the "removed" section
|
|
+ // and on the right the "added" section.
|
|
+ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
|
|
+ // exclusive to the regions they surround.
|
|
+
|
|
+ // 4 points of the rectangle
|
|
+ int maxX; // exclusive
|
|
+ int minX; // inclusive
|
|
+ int maxZ; // exclusive
|
|
+ int minZ; // inclusive
|
|
+
|
|
+ if (dx != 0) {
|
|
+ // handle right addition
|
|
+
|
|
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
|
|
+ minX = fromX + (oldViewDistance * right) + right; // inclusive
|
|
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
|
|
+ minZ = toZ - (oldViewDistance * up); // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dz != 0) {
|
|
+ // handle up addition
|
|
+
|
|
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
|
|
+ minX = toX - (oldViewDistance * right); // inclusive
|
|
+ maxZ = toZ + (oldViewDistance * up) + up; // exclusive
|
|
+ minZ = fromZ + (oldViewDistance * up) + up; // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dx != 0) {
|
|
+ // handle left removal
|
|
+
|
|
+ maxX = toX - (oldViewDistance * right); // exclusive
|
|
+ minX = fromX - (oldViewDistance * right); // inclusive
|
|
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
|
|
+ minZ = toZ - (oldViewDistance * up); // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dz != 0) {
|
|
+ // handle down removal
|
|
+
|
|
+ maxX = fromX + (oldViewDistance * right) + right; // exclusive
|
|
+ minX = fromX - (oldViewDistance * right); // inclusive
|
|
+ maxZ = toZ - (oldViewDistance * up); // exclusive
|
|
+ minZ = fromZ - (oldViewDistance * up); // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final boolean remove() {
|
|
+ final int chunkX = this.lastChunkX;
|
|
+ final int chunkZ = this.lastChunkZ;
|
|
+ final int distance = this.distance;
|
|
+ if (chunkX == NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ this.lastChunkX = this.lastChunkZ = this.distance = NOT_SET;
|
|
+
|
|
+ this.removeFromOld(this.parameter, chunkX, chunkZ, distance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static final class CountedSRSWLinkedQueue<E> {
|
|
+
|
|
+ private final SRSWLinkedQueue<E> queue = new SRSWLinkedQueue<>();
|
|
+ private volatile long countAdded;
|
|
+ private volatile long countRemoved;
|
|
+
|
|
+ private static final VarHandle COUNT_ADDED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countAdded", long.class);
|
|
+ private static final VarHandle COUNT_REMOVED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countRemoved", long.class);
|
|
+
|
|
+ private long getCountAddedPlain() {
|
|
+ return (long)COUNT_ADDED_HANDLE.get(this);
|
|
+ }
|
|
+
|
|
+ private long getCountAddedAcquire() {
|
|
+ return (long)COUNT_ADDED_HANDLE.getAcquire(this);
|
|
+ }
|
|
+
|
|
+ private void setCountAddedRelease(final long to) {
|
|
+ COUNT_ADDED_HANDLE.setRelease(this, to);
|
|
+ }
|
|
+
|
|
+ private long getCountRemovedPlain() {
|
|
+ return (long)COUNT_REMOVED_HANDLE.get(this);
|
|
+ }
|
|
+
|
|
+ private long getCountRemovedAcquire() {
|
|
+ return (long)COUNT_REMOVED_HANDLE.getAcquire(this);
|
|
+ }
|
|
+
|
|
+ private void setCountRemovedRelease(final long to) {
|
|
+ COUNT_REMOVED_HANDLE.setRelease(this, to);
|
|
+ }
|
|
+
|
|
+ public void add(final E element) {
|
|
+ this.setCountAddedRelease(this.getCountAddedPlain() + 1L);
|
|
+ this.queue.addLast(element);
|
|
+ }
|
|
+
|
|
+ public E poll() {
|
|
+ final E ret = this.queue.poll();
|
|
+ if (ret != null) {
|
|
+ this.setCountRemovedRelease(this.getCountRemovedPlain() + 1L);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public long size() {
|
|
+ final long removed = this.getCountRemovedAcquire();
|
|
+ final long added = this.getCountAddedAcquire();
|
|
+
|
|
+ return added - removed;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
index e5d9c6f2cbe11c2ded6d8ad111fa6a8b2086dfba..c6d20bc2f0eab737338db6b88dacb63f0decb66c 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
@@ -1,5 +1,6 @@
|
|
package io.papermc.paper.chunk.system.scheduling;
|
|
|
|
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
|
|
import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
|
import ca.spottedleaf.concurrentutil.map.SWMRLong2ObjectHashTable;
|
|
import co.aikar.timings.Timing;
|
|
@@ -493,6 +494,21 @@ public final class ChunkHolderManager {
|
|
}
|
|
}
|
|
|
|
+ // atomic with respect to all add/remove/addandremove ticket calls for the given chunk
|
|
+ public <T, V> boolean addIfRemovedTicket(final long chunk, final TicketType<T> addType, final int addLevel, final T addIdentifier,
|
|
+ final TicketType<V> removeType, final int removeLevel, final V removeIdentifier) {
|
|
+ this.ticketLock.lock();
|
|
+ try {
|
|
+ if (this.removeTicketAtLevel(removeType, chunk, removeLevel, removeIdentifier)) {
|
|
+ this.addTicketAtLevel(addType, chunk, addLevel, addIdentifier);
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ } finally {
|
|
+ this.ticketLock.unlock();
|
|
+ }
|
|
+ }
|
|
+
|
|
public <T> void removeAllTicketsFor(final TicketType<T> ticketType, final int ticketLevel, final T ticketIdentifier) {
|
|
if (ticketLevel > MAX_TICKET_LEVEL) {
|
|
return;
|
|
@@ -900,6 +916,142 @@ public final class ChunkHolderManager {
|
|
}
|
|
}
|
|
|
|
+ public enum TicketOperationType {
|
|
+ ADD, REMOVE, ADD_IF_REMOVED, ADD_AND_REMOVE
|
|
+ }
|
|
+
|
|
+ public static record TicketOperation<T, V> (
|
|
+ TicketOperationType op, long chunkCoord,
|
|
+ TicketType<T> ticketType, int ticketLevel, T identifier,
|
|
+ TicketType<V> ticketType2, int ticketLevel2, V identifier2
|
|
+ ) {
|
|
+
|
|
+ private TicketOperation(TicketOperationType op, long chunkCoord,
|
|
+ TicketType<T> ticketType, int ticketLevel, T identifier) {
|
|
+ this(op, chunkCoord, ticketType, ticketLevel, identifier, null, 0, null);
|
|
+ }
|
|
+
|
|
+ public static <T> TicketOperation<T, T> addOp(final ChunkPos chunk, final TicketType<T> type, final int ticketLevel, final T identifier) {
|
|
+ return addOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public static <T> TicketOperation<T, T> addOp(final int chunkX, final int chunkZ, final TicketType<T> type, final int ticketLevel, final T identifier) {
|
|
+ return addOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public static <T> TicketOperation<T, T> addOp(final long chunk, final TicketType<T> type, final int ticketLevel, final T identifier) {
|
|
+ return new TicketOperation<>(TicketOperationType.ADD, chunk, type, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public static <T> TicketOperation<T, T> removeOp(final ChunkPos chunk, final TicketType<T> type, final int ticketLevel, final T identifier) {
|
|
+ return removeOp(CoordinateUtils.getChunkKey(chunk), type, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public static <T> TicketOperation<T, T> removeOp(final int chunkX, final int chunkZ, final TicketType<T> type, final int ticketLevel, final T identifier) {
|
|
+ return removeOp(CoordinateUtils.getChunkKey(chunkX, chunkZ), type, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public static <T> TicketOperation<T, T> removeOp(final long chunk, final TicketType<T> type, final int ticketLevel, final T identifier) {
|
|
+ return new TicketOperation<>(TicketOperationType.REMOVE, chunk, type, ticketLevel, identifier);
|
|
+ }
|
|
+
|
|
+ public static <T, V> TicketOperation<T, V> addIfRemovedOp(final long chunk,
|
|
+ final TicketType<T> addType, final int addLevel, final T addIdentifier,
|
|
+ final TicketType<V> removeType, final int removeLevel, final V removeIdentifier) {
|
|
+ return new TicketOperation<>(
|
|
+ TicketOperationType.ADD_IF_REMOVED, chunk, addType, addLevel, addIdentifier,
|
|
+ removeType, removeLevel, removeIdentifier
|
|
+ );
|
|
+ }
|
|
+
|
|
+ public static <T, V> TicketOperation<T, V> addAndRemove(final long chunk,
|
|
+ final TicketType<T> addType, final int addLevel, final T addIdentifier,
|
|
+ final TicketType<V> removeType, final int removeLevel, final V removeIdentifier) {
|
|
+ return new TicketOperation<>(
|
|
+ TicketOperationType.ADD_AND_REMOVE, chunk, addType, addLevel, addIdentifier,
|
|
+ removeType, removeLevel, removeIdentifier
|
|
+ );
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private final MultiThreadedQueue<TicketOperation<?, ?>> delayedTicketUpdates = new MultiThreadedQueue<>();
|
|
+
|
|
+ // note: MUST hold ticket lock, otherwise operation ordering is lost
|
|
+ private boolean drainTicketUpdates() {
|
|
+ boolean ret = false;
|
|
+
|
|
+ TicketOperation operation;
|
|
+ while ((operation = this.delayedTicketUpdates.poll()) != null) {
|
|
+ switch (operation.op) {
|
|
+ case ADD: {
|
|
+ ret |= this.addTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier);
|
|
+ break;
|
|
+ }
|
|
+ case REMOVE: {
|
|
+ ret |= this.removeTicketAtLevel(operation.ticketType, operation.chunkCoord, operation.ticketLevel, operation.identifier);
|
|
+ break;
|
|
+ }
|
|
+ case ADD_IF_REMOVED: {
|
|
+ ret |= this.addIfRemovedTicket(
|
|
+ operation.chunkCoord,
|
|
+ operation.ticketType, operation.ticketLevel, operation.identifier,
|
|
+ operation.ticketType2, operation.ticketLevel2, operation.identifier2
|
|
+ );
|
|
+ break;
|
|
+ }
|
|
+ case ADD_AND_REMOVE: {
|
|
+ ret = true;
|
|
+ this.addAndRemoveTickets(
|
|
+ operation.chunkCoord,
|
|
+ operation.ticketType, operation.ticketLevel, operation.identifier,
|
|
+ operation.ticketType2, operation.ticketLevel2, operation.identifier2
|
|
+ );
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public Boolean tryDrainTicketUpdates() {
|
|
+ final boolean acquired = this.ticketLock.tryLock();
|
|
+ try {
|
|
+ if (!acquired) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return Boolean.valueOf(this.drainTicketUpdates());
|
|
+ } finally {
|
|
+ if (acquired) {
|
|
+ this.ticketLock.unlock();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void pushDelayedTicketUpdate(final TicketOperation<?, ?> operation) {
|
|
+ this.delayedTicketUpdates.add(operation);
|
|
+ }
|
|
+
|
|
+ public void pushDelayedTicketUpdates(final Collection<TicketOperation<?, ?>> operations) {
|
|
+ this.delayedTicketUpdates.addAll(operations);
|
|
+ }
|
|
+
|
|
+ public Boolean tryProcessTicketUpdates() {
|
|
+ final boolean acquired = this.ticketLock.tryLock();
|
|
+ try {
|
|
+ if (!acquired) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ return Boolean.valueOf(this.processTicketUpdates(false, true, null));
|
|
+ } finally {
|
|
+ if (acquired) {
|
|
+ this.ticketLock.unlock();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
private final ThreadLocal<Boolean> BLOCK_TICKET_UPDATES = ThreadLocal.withInitial(() -> {
|
|
return Boolean.FALSE;
|
|
});
|
|
@@ -948,6 +1100,8 @@ public final class ChunkHolderManager {
|
|
|
|
this.ticketLock.lock();
|
|
try {
|
|
+ this.drainTicketUpdates();
|
|
+
|
|
final boolean levelsUpdated = this.ticketLevelPropagator.propagateUpdates();
|
|
if (levelsUpdated) {
|
|
// Unlike CB, ticket level updates cannot happen recursively. Thank god.
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java
|
|
index 84cc9397237fa0c17aa1012dfb5683c90eb6d3b8..e944bd32df98a1dd7dde8c5ce3698b6d81862cbf 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkTaskScheduler.java
|
|
@@ -48,6 +48,10 @@ public final class ChunkTaskScheduler {
|
|
|
|
public static ca.spottedleaf.concurrentutil.executor.standard.PrioritisedThreadPool workerThreads;
|
|
|
|
+ public static int getWorkerCount() {
|
|
+ return workerThreads.getThreadCount();
|
|
+ }
|
|
+
|
|
private static boolean initialised = false;
|
|
|
|
public static void init(final GlobalConfiguration.ChunkSystem config) {
|
|
diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
index 8d442c5a498ecf288a0cc0c54889c6e2fda849ce..9f5f0d8ddc8f480b48079c70e38c9c08eff403f6 100644
|
|
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
@@ -287,4 +287,43 @@ public class GlobalConfiguration extends ConfigurationPart {
|
|
public boolean useDimensionTypeForCustomSpawners = false;
|
|
public boolean strictAdvancementDimensionCheck = false;
|
|
}
|
|
+
|
|
+ public ChunkLoadingBasic chunkLoadingBasic;
|
|
+
|
|
+ public class ChunkLoadingBasic extends ConfigurationPart {
|
|
+ @Comment("The maximum rate in chunks per second that the server will send to any individual player. Set to -1 to disable this limit.")
|
|
+ public double playerMaxChunkSendRate = 75.0;
|
|
+
|
|
+ @Comment(
|
|
+ "The maximum rate at which chunks will load for any individual player. " +
|
|
+ "Note that this setting also affects chunk generations, since a chunk load is always first issued to test if a" +
|
|
+ "chunk is already generated. Set to -1 to disable this limit."
|
|
+ )
|
|
+ public double playerMaxChunkLoadRate = 100.0;
|
|
+
|
|
+ @Comment("The maximum rate at which chunks will generate for any individual player. Set to -1 to disable this limit.")
|
|
+ public double playerMaxChunkGenerateRate = -1.0;
|
|
+ }
|
|
+
|
|
+ public ChunkLoadingAdvanced chunkLoadingAdvanced;
|
|
+
|
|
+ public class ChunkLoadingAdvanced extends ConfigurationPart {
|
|
+ @Comment(
|
|
+ "Set to true if the server will match the chunk send radius that clients have configured" +
|
|
+ "in their view distance settings if the client is less-than the server's send distance."
|
|
+ )
|
|
+ public boolean autoConfigSendDistance = true;
|
|
+
|
|
+ @Comment(
|
|
+ "Specifies the maximum amount of concurrent chunk loads that an individual player can have." +
|
|
+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit."
|
|
+ )
|
|
+ public int playerMaxConcurrentChunkLoads = 0;
|
|
+
|
|
+ @Comment(
|
|
+ "Specifies the maximum amount of concurrent chunk generations that an individual player can have." +
|
|
+ "Set to 0 to let the server configure it automatically per player, or set it to -1 to disable the limit."
|
|
+ )
|
|
+ public int playerMaxConcurrentChunkGenerates = 0;
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
|
|
index cea9c098ade00ee87b8efc8164ab72f5279758f0..197224e31175252d8438a8df585bbb65f2288d7f 100644
|
|
--- a/src/main/java/io/papermc/paper/util/IntervalledCounter.java
|
|
+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
|
|
@@ -2,6 +2,8 @@ package io.papermc.paper.util;
|
|
|
|
public final class IntervalledCounter {
|
|
|
|
+ private static final int INITIAL_SIZE = 8;
|
|
+
|
|
protected long[] times;
|
|
protected long[] counts;
|
|
protected final long interval;
|
|
@@ -11,8 +13,8 @@ public final class IntervalledCounter {
|
|
protected int tail; // exclusive
|
|
|
|
public IntervalledCounter(final long interval) {
|
|
- this.times = new long[8];
|
|
- this.counts = new long[8];
|
|
+ this.times = new long[INITIAL_SIZE];
|
|
+ this.counts = new long[INITIAL_SIZE];
|
|
this.interval = interval;
|
|
}
|
|
|
|
@@ -67,13 +69,13 @@ public final class IntervalledCounter {
|
|
this.tail = nextTail;
|
|
}
|
|
|
|
- public void updateAndAdd(final int count) {
|
|
+ public void updateAndAdd(final long count) {
|
|
final long currTime = System.nanoTime();
|
|
this.updateCurrentTime(currTime);
|
|
this.addTime(currTime, count);
|
|
}
|
|
|
|
- public void updateAndAdd(final int count, final long currTime) {
|
|
+ public void updateAndAdd(final long count, final long currTime) {
|
|
this.updateCurrentTime(currTime);
|
|
this.addTime(currTime, count);
|
|
}
|
|
@@ -93,9 +95,13 @@ public final class IntervalledCounter {
|
|
this.tail = size;
|
|
|
|
if (tail >= head) {
|
|
+ // sequentially ordered from [head, tail)
|
|
System.arraycopy(oldElements, head, newElements, 0, size);
|
|
System.arraycopy(oldCounts, head, newCounts, 0, size);
|
|
} else {
|
|
+ // ordered from [head, length)
|
|
+ // then followed by [0, tail)
|
|
+
|
|
System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
|
|
System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
|
|
|
|
@@ -106,10 +112,18 @@ public final class IntervalledCounter {
|
|
|
|
// returns in units per second
|
|
public double getRate() {
|
|
- return this.size() / (this.interval * 1.0e-9);
|
|
+ return (double)this.sum / ((double)this.interval * 1.0E-9);
|
|
+ }
|
|
+
|
|
+ public long getInterval() {
|
|
+ return this.interval;
|
|
}
|
|
|
|
- public long size() {
|
|
+ public long getSum() {
|
|
return this.sum;
|
|
}
|
|
+
|
|
+ public int totalDataPoints() {
|
|
+ return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head));
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/io/papermc/paper/util/MCUtil.java b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
index 6efb8b10f17c70b05128039376d254e6beda3841..c856a9a0d085b278da416c59996fc131811f790c 100644
|
|
--- a/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
@@ -607,8 +607,8 @@ public final class MCUtil {
|
|
|
|
worldData.addProperty("is-loaded", loadedWorlds.contains(bukkitWorld));
|
|
worldData.addProperty("name", world.getWorld().getName());
|
|
- worldData.addProperty("view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace chunk loader system
|
|
- worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system
|
|
+ worldData.addProperty("view-distance", world.getWorld().getViewDistance()); // Paper - replace chunk loader system
|
|
+ worldData.addProperty("tick-view-distance", world.getWorld().getSimulationDistance()); // Paper - replace chunk loader system
|
|
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
|
|
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
index 904fcdeb7937d36208cc9a8d5eca9ef3a5b2cd9e..a7013fdd8d82f24cadd90ea7e9ebc7c1501042d3 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -128,6 +128,26 @@ public class ChunkHolder {
|
|
com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
|
|
// Paper end - optimise anyPlayerCloseEnoughForSpawning
|
|
|
|
+ // Paper start - replace player chunk loader
|
|
+ private final com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>();
|
|
+
|
|
+ public void addPlayer(ServerPlayer player) {
|
|
+ if (!this.playersSentChunkTo.add(player)) {
|
|
+ throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(ServerPlayer player) {
|
|
+ if (!this.playersSentChunkTo.remove(player)) {
|
|
+ throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean hasChunkBeenSent() {
|
|
+ return this.playersSentChunkTo.size() != 0;
|
|
+ }
|
|
+ // Paper end - replace player chunk loader
|
|
+
|
|
public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system
|
|
this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system
|
|
this.chunkToSaveHistory = null;
|
|
@@ -225,6 +245,11 @@ public class ChunkHolder {
|
|
// Paper - rewrite chunk system
|
|
|
|
public void blockChanged(BlockPos pos) {
|
|
+ // Paper start - replace player chunk loader
|
|
+ if (this.playersSentChunkTo.size() == 0) {
|
|
+ return;
|
|
+ }
|
|
+ // Paper end - replace player chunk loader
|
|
LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
|
|
|
|
if (chunk != null) {
|
|
@@ -251,7 +276,7 @@ public class ChunkHolder {
|
|
LevelChunk chunk = this.getSendingChunk();
|
|
// Paper end - no-tick view distance
|
|
|
|
- if (chunk != null) {
|
|
+ if (this.playersSentChunkTo.size() != 0 && chunk != null) { // Paper - replace player chunk loader
|
|
int j = this.lightEngine.getMinLightSection();
|
|
int k = this.lightEngine.getMaxLightSection();
|
|
|
|
@@ -351,27 +376,32 @@ public class ChunkHolder {
|
|
|
|
}
|
|
|
|
- public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
|
|
- // Paper start - per player view distance
|
|
- // there can be potential desync with player's last mapped section and the view distance map, so use the
|
|
- // view distance map here.
|
|
- com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
|
|
- if (players == null) {
|
|
- return;
|
|
- }
|
|
+ // Paper start - rewrite player chunk loader
|
|
+ public List<ServerPlayer> getPlayers(boolean onlyOnWatchDistanceEdge) {
|
|
+ List<ServerPlayer> ret = new java.util.ArrayList<>();
|
|
|
|
- Object[] backingSet = players.getBackingSet();
|
|
- for (int i = 0, len = backingSet.length; i < len; ++i) {
|
|
- if (!(backingSet[i] instanceof ServerPlayer player)) {
|
|
+ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) {
|
|
+ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i);
|
|
+ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
|
|
continue;
|
|
}
|
|
- if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
|
|
+ ret.add(player);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ // Paper end - rewrite player chunk loader
|
|
+
|
|
+ public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
|
|
+ // Paper start - rewrite player chunk loader - modeled after the above
|
|
+ for (int i = 0, len = this.playersSentChunkTo.size(); i < len; ++i) {
|
|
+ ServerPlayer player = this.playersSentChunkTo.getUnchecked(i);
|
|
+ if (onlyOnWatchDistanceEdge && !this.chunkMap.level.playerChunkLoader.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
|
|
continue;
|
|
}
|
|
player.connection.send(packet);
|
|
}
|
|
- // Paper end - per player view distance
|
|
+ // Paper end - rewrite player chunk loader
|
|
}
|
|
|
|
// Paper - rewrite chunk system
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index fbe209a66c77c47935ad026dd3e45e682af91fd8..78ccfe201f91b9d969766a6aed4ab033a8e1218f 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -199,7 +199,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper end - use distance map to optimise tracker
|
|
|
|
void addPlayerToDistanceMaps(ServerPlayer player) {
|
|
- this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
|
|
+ this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader
|
|
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
@@ -221,7 +221,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
void removePlayerFromDistanceMaps(ServerPlayer player) {
|
|
- this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
|
|
+ this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader
|
|
|
|
// Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
|
|
this.playerMobSpawnMap.remove(player);
|
|
@@ -244,7 +244,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
- this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
|
|
+ this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader
|
|
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
|
|
// Paper start - per player mob spawning
|
|
if (this.playerMobDistanceMap != null) {
|
|
@@ -816,7 +816,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
// Paper start - replace player loader system
|
|
public void setTickViewDistance(int distance) {
|
|
- this.playerChunkManager.setTickDistance(distance);
|
|
+ this.level.playerChunkLoader.setTickDistance(distance);
|
|
+ }
|
|
+
|
|
+ public void setSendViewDistance(int distance) {
|
|
+ this.level.playerChunkLoader.setSendDistance(distance);
|
|
}
|
|
// Paper end - replace player loader system
|
|
public void setViewDistance(int watchDistance) {
|
|
@@ -826,20 +830,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
int k = this.viewDistance;
|
|
|
|
this.viewDistance = j;
|
|
- this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system
|
|
+ this.level.playerChunkLoader.setLoadDistance(this.viewDistance); // Paper - replace player loader system
|
|
}
|
|
|
|
}
|
|
|
|
public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - public // Paper - Anti-Xray - Bypass
|
|
+ io.papermc.paper.util.TickThread.ensureTickThread(this.level, pos, "May not update chunk tracking for chunk async"); // Paper - replace chunk loader system
|
|
+ io.papermc.paper.util.TickThread.ensureTickThread(player, "May not update chunk tracking for player async"); // Paper - replace chunk loader system
|
|
if (player.level == this.level) {
|
|
+ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); // Paper - replace chunk loader system - move up
|
|
if (newWithinViewDistance && !oldWithinViewDistance) {
|
|
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
|
|
+ // Paper - replace chunk loader system - move up
|
|
|
|
if (playerchunk != null) {
|
|
LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system
|
|
|
|
if (chunk != null) {
|
|
+ playerchunk.addPlayer(player); // Paper - replace chunk loader system
|
|
this.playerLoadedChunk(player, packet, chunk);
|
|
}
|
|
|
|
@@ -848,10 +856,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
if (!newWithinViewDistance && oldWithinViewDistance) {
|
|
+ // Paper start - replace chunk loader system
|
|
+ if (playerchunk != null) {
|
|
+ playerchunk.removePlayer(player);
|
|
+ } else {
|
|
+ LOGGER.warn("ChunkHolder at " + pos + " in world '" + this.level.getWorld().getName() + "' does not exist to untrack chunk for " + player, new Throwable());
|
|
+ }
|
|
+ // Paper end - replace chunk loader system
|
|
player.untrackChunk(pos);
|
|
}
|
|
|
|
- }
|
|
+ } else { LOGGER.warn("Mismatch in world for chunk " + pos + " in world '" + this.level.getWorld().getName() + "' for player " + player, new Throwable()); } // Paper - replace chunk loader system
|
|
}
|
|
|
|
public int size() {
|
|
@@ -1151,34 +1166,18 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper - replaced by PlayerChunkLoader
|
|
|
|
this.updateMaps(player); // Paper - distance maps
|
|
- this.playerChunkManager.updatePlayer(player); // Paper - respond to movement immediately
|
|
|
|
}
|
|
|
|
@Override
|
|
public List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
|
|
// Paper start - per player view distance
|
|
- // there can be potential desync with player's last mapped section and the view distance map, so use the
|
|
- // view distance map here.
|
|
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos);
|
|
- if (players == null) {
|
|
- return java.util.Collections.emptyList();
|
|
- }
|
|
-
|
|
- List<ServerPlayer> ret = new java.util.ArrayList<>(players.size());
|
|
-
|
|
- Object[] backingSet = players.getBackingSet();
|
|
- for (int i = 0, len = backingSet.length; i < len; ++i) {
|
|
- if (!(backingSet[i] instanceof ServerPlayer player)) {
|
|
- continue;
|
|
- }
|
|
- if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) {
|
|
- continue;
|
|
- }
|
|
- ret.add(player);
|
|
+ ChunkHolder holder = this.getVisibleChunkIfPresent(chunkPos.toLong());
|
|
+ if (holder == null) {
|
|
+ return new java.util.ArrayList<>();
|
|
+ } else {
|
|
+ return holder.getPlayers(onlyOnWatchDistanceEdge);
|
|
}
|
|
-
|
|
- return ret;
|
|
// Paper end - per player view distance
|
|
}
|
|
|
|
@@ -1612,7 +1611,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
double vec3d_dx = player.getX() - this.entity.getX();
|
|
double vec3d_dz = player.getZ() - this.entity.getZ();
|
|
// Paper end - remove allocation of Vec3D here
|
|
- double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance
|
|
+ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player) * 16); // Paper - per player view distance
|
|
double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
|
|
double d2 = d0 * d0;
|
|
boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player);
|
|
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
index 52cba8f68d274cce106304aef1249a95474d3238..88fca8b160df6804f30ed2cf8cf1f645085434e2 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -184,17 +184,17 @@ public abstract class DistanceManager {
|
|
}
|
|
|
|
protected void updatePlayerTickets(int viewDistance) {
|
|
- this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager
|
|
+ this.chunkMap.setViewDistance(viewDistance);// Paper - route to player chunk manager
|
|
}
|
|
|
|
// Paper start
|
|
public int getSimulationDistance() {
|
|
- return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager
|
|
+ return this.chunkMap.level.playerChunkLoader.getAPITickDistance();
|
|
}
|
|
// Paper end
|
|
|
|
public void updateSimulationDistance(int simulationDistance) {
|
|
- this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager
|
|
+ this.chunkMap.level.playerChunkLoader.setTickDistance(simulationDistance); // Paper - route to player chunk manager
|
|
}
|
|
|
|
public int getNaturalSpawnChunkCount() {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index ca84eddbdb1e198b899750e5f6b3eafd25ce970f..5c7b8041e96ede9e662dfdb5285539bf51304650 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -645,7 +645,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
this.level.getProfiler().popPush("chunks");
|
|
if (tickChunks) {
|
|
this.level.timings.chunks.startTiming(); // Paper - timings
|
|
- this.chunkMap.playerChunkManager.tick(); // Paper - this is mostly is to account for view distance changes
|
|
+ this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes
|
|
this.tickChunks();
|
|
this.level.timings.chunks.stopTiming(); // Paper - timings
|
|
}
|
|
@@ -1001,7 +1001,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
@Override
|
|
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
public boolean pollTask() {
|
|
- ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick();
|
|
+ // Paper - replace player chunk loader
|
|
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
|
|
return true;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 5a5ff40df37db9cbd53c584ed26a3ce4888b29c0..bf1a77cf9bbea4e2104b2a8c61309e740f28d51b 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -521,6 +521,48 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
}
|
|
// Paper end - optimise get nearest players for entity AI
|
|
|
|
+ public final io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader playerChunkLoader = new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader(this);
|
|
+ private final java.util.concurrent.atomic.AtomicReference<io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances> viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1));
|
|
+
|
|
+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() {
|
|
+ return this.viewDistances.get();
|
|
+ }
|
|
+
|
|
+ private void updateViewDistance(final java.util.function.Function<io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances, io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances> update) {
|
|
+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) {
|
|
+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void setTickViewDistance(final int distance) {
|
|
+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) {
|
|
+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance);
|
|
+ }
|
|
+ this.updateViewDistance((input) -> {
|
|
+ return input.setTickViewDistance(distance);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public void setLoadViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.updateViewDistance((input) -> {
|
|
+ return input.setLoadViewDistance(distance);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public void setSendViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.updateViewDistance((input) -> {
|
|
+ return input.setSendViewDistance(distance);
|
|
+ });
|
|
+ }
|
|
+
|
|
// Add env and gen to constructor, IWorldDataServer -> WorldDataServer
|
|
public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
|
|
// IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index 1d4d02f26391ac55c7631817f09d05e2769b0d29..4231c3fe14a621d237bdc3d56c593f0adc0a7bc6 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -276,6 +276,48 @@ public class ServerPlayer extends Player {
|
|
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
|
|
public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event
|
|
|
|
+ private final java.util.concurrent.atomic.AtomicReference<io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances> viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1));
|
|
+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
|
|
+
|
|
+ public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances getViewDistances() {
|
|
+ return this.viewDistances.get();
|
|
+ }
|
|
+
|
|
+ private void updateViewDistance(final java.util.function.Function<io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances, io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances> update) {
|
|
+ for (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances curr = this.viewDistances.get();;) {
|
|
+ if (this.viewDistances.compareAndSet(curr, update.apply(curr))) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void setTickViewDistance(final int distance) {
|
|
+ if ((distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE)) {
|
|
+ throw new IllegalArgumentException("Tick view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE) + ", got: " + distance);
|
|
+ }
|
|
+ this.updateViewDistance((input) -> {
|
|
+ return input.setTickViewDistance(distance);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public void setLoadViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Load view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.updateViewDistance((input) -> {
|
|
+ return input.setLoadViewDistance(distance);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ public void setSendViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE || distance > io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Send view distance must be a number between " + io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MIN_VIEW_DISTANCE + " and " + (io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.updateViewDistance((input) -> {
|
|
+ return input.setSendViewDistance(distance);
|
|
+ });
|
|
+ }
|
|
+
|
|
public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
|
|
super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
|
|
this.chatVisibility = ChatVisiblity.FULL;
|
|
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
index c0c14766adaac855112f85a203a6163b8adfdded..0dbe182fbae5ce5ba182176eb5d5e3f1897e77f2 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -271,7 +271,7 @@ public abstract class PlayerList {
|
|
boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
|
|
|
|
// Spigot - view distance
|
|
- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.synchronizedRegistries, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management
|
|
+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.synchronizedRegistries, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management
|
|
player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
|
|
playerconnection.send(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(worldserver1.enabledFeatures())));
|
|
playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
|
|
@@ -905,8 +905,8 @@ public abstract class PlayerList {
|
|
// CraftBukkit start
|
|
LevelData worlddata = worldserver1.getLevelData();
|
|
entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), (byte) i, entityplayer1.getLastDeathLocation()));
|
|
- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management
|
|
- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management
|
|
+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getWorld().getSendViewDistance())); // Spigot // Paper - replace old player chunk management
|
|
+ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getWorld().getSimulationDistance())); // Spigot // Paper - replace old player chunk management
|
|
entityplayer1.spawnIn(worldserver1);
|
|
entityplayer1.unsetRemoved();
|
|
entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index 973ecd50f9cb6b86c353586e84d15dcb118ccb60..944da18bcc993ab0488a34cbbe9df134c355301a 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -635,7 +635,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
|
|
// Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance
|
|
// if copied from above
|
|
- } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(io.papermc.paper.util.MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management
|
|
+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0)) { // Paper - replace old player chunk management
|
|
((ServerLevel)this).getChunkSource().blockChanged(blockposition);
|
|
// Paper end - per player view distance
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
index 206dc04086a218b510930739a6c573f2653ab0fa..9599af33c683ec47e28b1c8e4dc965d30d9081a7 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -184,43 +184,6 @@ public class LevelChunk extends ChunkAccess {
|
|
|
|
protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
|
|
|
|
- // Paper start - no-tick view distance
|
|
- ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource();
|
|
- net.minecraft.server.level.ChunkMap chunkMap = chunkProviderServer.chunkMap;
|
|
- // this code handles the addition of ticking tickets - the distance map handles the removal
|
|
- if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
|
|
- if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Paper - replace old player chunk loading system
|
|
- // now we're ready for entity ticking
|
|
- chunkProviderServer.mainThreadProcessor.execute(() -> {
|
|
- // double check that this condition still holds.
|
|
- if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Paper - replace old player chunk loading system
|
|
- chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Paper - replace old player chunk
|
|
- chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update
|
|
- }
|
|
- });
|
|
- }
|
|
- }
|
|
-
|
|
- // this code handles the chunk sending
|
|
- if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
|
|
- // Paper start - replace old player chunk loading system
|
|
- if (chunkMap.playerChunkManager.isChunkNearPlayers(this.chunkPos.x, this.chunkPos.z)) {
|
|
- // the post processing is expensive, so we don't want to run it unless we're actually near
|
|
- // a player.
|
|
- chunkProviderServer.mainThreadProcessor.execute(() -> {
|
|
- if (!LevelChunk.this.areNeighboursLoaded(1)) {
|
|
- return;
|
|
- }
|
|
- LevelChunk.this.postProcessGeneration();
|
|
- if (!LevelChunk.this.areNeighboursLoaded(1)) {
|
|
- return;
|
|
- }
|
|
- chunkMap.playerChunkManager.onChunkSendReady(this.chunkPos.x, this.chunkPos.z);
|
|
- });
|
|
- }
|
|
- // Paper end - replace old player chunk loading system
|
|
- }
|
|
- // Paper end - no-tick view distance
|
|
}
|
|
|
|
public final boolean isAnyNeighborsLoaded() {
|
|
@@ -906,7 +869,6 @@ public class LevelChunk extends ChunkAccess {
|
|
// Paper - rewrite chunk system - move into separate callback
|
|
org.bukkit.Server server = this.level.getCraftServer();
|
|
// Paper - rewrite chunk system - move into separate callback
|
|
- ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Paper - rewrite player chunk management
|
|
if (server != null) {
|
|
/*
|
|
* If it's a new world, the first few chunks are generated inside
|
|
@@ -1086,6 +1048,7 @@ public class LevelChunk extends ChunkAccess {
|
|
BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, this.level, blockposition);
|
|
|
|
this.level.setBlock(blockposition, iblockdata1, 20);
|
|
+ if (iblockdata1 != iblockdata) this.level.chunkSource.blockChanged(blockposition); // Paper - replace player chunk loader - notify since we send before processing full updates
|
|
}
|
|
}
|
|
|
|
@@ -1105,7 +1068,6 @@ public class LevelChunk extends ChunkAccess {
|
|
this.upgradeData.upgrade(this);
|
|
} finally { // Paper start - replace chunk loader system
|
|
this.isPostProcessingDone = true;
|
|
- this.level.getChunkSource().chunkMap.playerChunkManager.onChunkPostProcessing(this.chunkPos.x, this.chunkPos.z);
|
|
}
|
|
// Paper end - replace chunk loader system
|
|
}
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 5e9055fdf411029ea2fed91acd6b981f79156418..ff6559bf563f2fdcc0f2843d4f4aa24d7ddfb6db 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -2273,12 +2273,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
// Spigot start
|
|
@Override
|
|
public int getViewDistance() {
|
|
- return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management
|
|
+ return this.getHandle().playerChunkLoader.getAPIViewDistance(); // Paper - replace player chunk loader
|
|
}
|
|
|
|
@Override
|
|
public int getSimulationDistance() {
|
|
- return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management
|
|
+ return this.getHandle().playerChunkLoader.getAPITickDistance(); // Paper - replace player chunk loader
|
|
}
|
|
// Spigot end
|
|
// Paper start - view distance api
|
|
@@ -2312,12 +2312,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
|
|
@Override
|
|
public int getSendViewDistance() {
|
|
- return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance();
|
|
+ return this.getHandle().playerChunkLoader.getAPISendViewDistance(); // Paper - replace player chunk loader
|
|
}
|
|
|
|
@Override
|
|
public void setSendViewDistance(int viewDistance) {
|
|
- getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance);
|
|
+ this.getHandle().chunkSource.chunkMap.setSendViewDistance(viewDistance); // Paper - replace player chunk loader
|
|
}
|
|
// Paper end - view distance api
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
index 0ae1fce0c1a2e3bfbbab756a088fc76545e263fa..fec408416aa63e1022802a644bb95131601f0c3b 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -189,44 +189,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
// Paper start - implement view distances
|
|
@Override
|
|
public int getViewDistance() {
|
|
- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
|
|
- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
- if (data == null) {
|
|
- return chunkMap.playerChunkManager.getTargetNoTickViewDistance();
|
|
- }
|
|
- return data.getTargetNoTickViewDistance();
|
|
+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPIViewDistance(this);
|
|
}
|
|
|
|
@Override
|
|
public void setViewDistance(int viewDistance) {
|
|
- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
|
|
- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
- if (data == null) {
|
|
- throw new IllegalStateException("Player is not attached to world");
|
|
- }
|
|
-
|
|
- data.setTargetNoTickViewDistance(viewDistance);
|
|
+ this.getHandle().setLoadViewDistance(viewDistance < 0 ? viewDistance : viewDistance + 1);
|
|
}
|
|
|
|
@Override
|
|
public int getSimulationDistance() {
|
|
- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
|
|
- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
- if (data == null) {
|
|
- return chunkMap.playerChunkManager.getTargetTickViewDistance();
|
|
- }
|
|
- return data.getTargetTickViewDistance();
|
|
+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPITickViewDistance(this);
|
|
}
|
|
|
|
@Override
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
|
|
- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
- if (data == null) {
|
|
- throw new IllegalStateException("Player is not attached to world");
|
|
- }
|
|
-
|
|
- data.setTargetTickViewDistance(simulationDistance);
|
|
+ this.getHandle().setTickViewDistance(simulationDistance);
|
|
}
|
|
|
|
@Override
|
|
@@ -241,23 +219,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
|
|
@Override
|
|
public int getSendViewDistance() {
|
|
- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
|
|
- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
- if (data == null) {
|
|
- return chunkMap.playerChunkManager.getTargetSendDistance();
|
|
- }
|
|
- return data.getTargetSendViewDistance();
|
|
+ return io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.getAPISendViewDistance(this);
|
|
}
|
|
|
|
@Override
|
|
public void setSendViewDistance(int viewDistance) {
|
|
- net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
|
|
- io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
|
|
- if (data == null) {
|
|
- throw new IllegalStateException("Player is not attached to world");
|
|
- }
|
|
-
|
|
- data.setTargetSendViewDistance(viewDistance);
|
|
+ this.getHandle().setSendViewDistance(viewDistance);
|
|
}
|
|
// Paper end - implement view distances
|
|
|