mirror of
https://github.com/PaperMC/Folia.git
synced 2024-11-16 11:05:11 +01:00
2284 lines
116 KiB
Diff
2284 lines
116 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/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index 06bff37e4c1fddd3be6343049a66787c63fb420c..1be1fe766401221b6adb417175312007d29d347e 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -163,9 +163,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..99dc2038aa4fb0f70dc3670e451018ff541d3a85
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java
|
|
@@ -0,0 +1,1304 @@
|
|
+package io.papermc.paper.chunk.system;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
|
+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.IntervalledCounter;
|
|
+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.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 : this.world.players()) {
|
|
+ player.chunkLoader.update();
|
|
+ player.chunkLoader.midTickUpdate(currTime);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void tickMidTick() {
|
|
+ final long time = System.nanoTime();
|
|
+ for (final ServerPlayer player : this.world.players()) {
|
|
+ player.chunkLoader.midTickUpdate(time);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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 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 MultiIntervalledCounter chunkSendCounter = new MultiIntervalledCounter(
|
|
+ TimeUnit.MILLISECONDS.toNanos(50L), TimeUnit.MILLISECONDS.toNanos(250L), TimeUnit.SECONDS.toNanos(1L)
|
|
+ );
|
|
+ private final MultiIntervalledCounter chunkLoadTicketCounter = new MultiIntervalledCounter(
|
|
+ TimeUnit.MILLISECONDS.toNanos(50L), TimeUnit.MILLISECONDS.toNanos(250L), TimeUnit.SECONDS.toNanos(1L)
|
|
+ );
|
|
+ private final MultiIntervalledCounter chunkGenerateTicketCounter = new MultiIntervalledCounter(
|
|
+ TimeUnit.MILLISECONDS.toNanos(50L), TimeUnit.MILLISECONDS.toNanos(250L), TimeUnit.SECONDS.toNanos(1L)
|
|
+ );
|
|
+
|
|
+ // 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);
|
|
+
|
|
+ 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 int getMaxChunkLoads() {
|
|
+ final int radiusChunks = (2 * this.lastLoadDistance + 1) * (2 * this.lastLoadDistance + 1);
|
|
+ int configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
|
|
+ if (configLimit == 0) {
|
|
+ // by default, only allow 1/10th of the chunks in the view distance to be concurrently active
|
|
+ configLimit = Math.max(5, radiusChunks / 10);
|
|
+ } else if (configLimit < 0) {
|
|
+ configLimit = Integer.MAX_VALUE;
|
|
+ } // else: use the value configured
|
|
+ configLimit = configLimit - this.loadingQueue.size();
|
|
+
|
|
+ int rateLimit;
|
|
+ double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
|
|
+ if (configRate < 0.0 || configRate > (1000.0 * (double)radiusChunks)) {
|
|
+ // getMaxCountBeforeViolation may not work with large config rates, so by checking against the load count we ensure
|
|
+ // there are no issues with the cast to integer
|
|
+ rateLimit = Integer.MAX_VALUE;
|
|
+ } else {
|
|
+ rateLimit = (int)this.chunkLoadTicketCounter.getMaxCountBeforeViolation(configRate);
|
|
+ }
|
|
+
|
|
+ return Math.min(configLimit, rateLimit);
|
|
+ }
|
|
+
|
|
+ private int getMaxChunkGenerates() {
|
|
+ final int radiusChunks = (2 * this.lastLoadDistance + 1) * (2 * this.lastLoadDistance + 1);
|
|
+ int configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
|
|
+ if (configLimit == 0) {
|
|
+ // by default, only allow 1/10th of the chunks in the view distance to be concurrently active
|
|
+ configLimit = Math.max(5, radiusChunks / 10);
|
|
+ } else if (configLimit < 0) {
|
|
+ configLimit = Integer.MAX_VALUE;
|
|
+ } // else: use the value configured
|
|
+ configLimit = configLimit - this.generatingQueue.size();
|
|
+
|
|
+ int rateLimit;
|
|
+ double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
|
|
+ if (configRate < 0.0 || configRate > (1000.0 * (double)radiusChunks)) {
|
|
+ // getMaxCountBeforeViolation may not work with large config rates, so by checking against the load count we ensure
|
|
+ // there are no issues with the cast to integer
|
|
+ rateLimit = Integer.MAX_VALUE;
|
|
+ } else {
|
|
+ rateLimit = (int)this.chunkGenerateTicketCounter.getMaxCountBeforeViolation(configRate);
|
|
+ }
|
|
+
|
|
+ return Math.min(configLimit, rateLimit);
|
|
+ }
|
|
+
|
|
+ private int getMaxChunkSends() {
|
|
+ final int radiusChunks = (2 * this.lastSendDistance + 1) * (2 * this.lastSendDistance + 1);
|
|
+
|
|
+ int rateLimit;
|
|
+ double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
|
|
+ if (configRate < 0.0 || configRate > (1000.0 * (double)radiusChunks)) {
|
|
+ // getMaxCountBeforeViolation may not work with large config rates, so by checking against the load count we ensure
|
|
+ // there are no issues with the cast to integer
|
|
+ rateLimit = Integer.MAX_VALUE;
|
|
+ } else {
|
|
+ rateLimit = (int)this.chunkSendCounter.getMaxCountBeforeViolation(configRate);
|
|
+ }
|
|
+
|
|
+ return rateLimit;
|
|
+ }
|
|
+
|
|
+ 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 midTickUpdate(final long time) {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async");
|
|
+ // update rate limits
|
|
+ this.chunkSendCounter.update(time);
|
|
+ this.chunkGenerateTicketCounter.update(time);
|
|
+ this.chunkLoadTicketCounter.update(time);
|
|
+
|
|
+ // 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
|
|
+ int loadSlots;
|
|
+ while ((loadSlots = Math.min(this.getMaxChunkLoads(), this.loadQueue.size())) > 0) {
|
|
+ final LongArrayList chunks = new LongArrayList(loadSlots);
|
|
+ int actualLoadsQueued = 0;
|
|
+ for (int i = 0; i < loadSlots; ++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);
|
|
+
|
|
+ if (this.world.chunkSource.getChunkAtImmediately(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk)) == null) {
|
|
+ // this is a good enough approximation for counting, but NOT for actual state management
|
|
+ ++actualLoadsQueued;
|
|
+ }
|
|
+ }
|
|
+ if (actualLoadsQueued > 0) {
|
|
+ this.chunkLoadTicketCounter.addTime(time, (long)actualLoadsQueued);
|
|
+ }
|
|
+
|
|
+ // 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();
|
|
+
|
|
+ for (int i = 0; i < loadSlots; ++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
|
|
+ );
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // 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
|
|
+ int genSlots;
|
|
+ while ((genSlots = Math.min(this.getMaxChunkGenerates(), this.genQueue.size())) > 0) {
|
|
+ int actualGenerationsQueued = 0;
|
|
+ for (int i = 0; i < genSlots; ++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);
|
|
+ final ChunkAccess existingChunk = this.world.chunkSource.getChunkAtImmediately(CoordinateUtils.getChunkX(chunk), CoordinateUtils.getChunkZ(chunk));
|
|
+ if (existingChunk == null || !existingChunk.getStatus().isOrAfter(ChunkStatus.FULL)) {
|
|
+ // this is a good enough approximation for counting, but NOT for actual state management
|
|
+ ++actualGenerationsQueued;
|
|
+ }
|
|
+ }
|
|
+ if (actualGenerationsQueued > 0) {
|
|
+ this.chunkGenerateTicketCounter.addTime(time, (long)actualGenerationsQueued);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // 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 int maxSends = this.getMaxChunkSends();
|
|
+ final int sendSlots = Math.min(maxSends, this.sendQueue.size());
|
|
+ for (int i = 0; i < sendSlots; ++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)) {
|
|
+ // nothing to do
|
|
+ break;
|
|
+ }
|
|
+ this.sendQueue.dequeueLong();
|
|
+
|
|
+ this.sendChunk(pendingSendX, pendingSendZ);
|
|
+ }
|
|
+ if (sendSlots > 0) {
|
|
+ this.chunkSendCounter.addTime(time, sendSlots);
|
|
+ }
|
|
+
|
|
+ this.flushDelayedTicketOps();
|
|
+ // we assume propagate ticket levels happens after this call
|
|
+ }
|
|
+
|
|
+ void add() {
|
|
+ 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() {
|
|
+ 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() {
|
|
+ // 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
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static final class MultiIntervalledCounter {
|
|
+
|
|
+ private final IntervalledCounter[] counters;
|
|
+
|
|
+ public MultiIntervalledCounter(final long... intervals) {
|
|
+ final IntervalledCounter[] counters = new IntervalledCounter[intervals.length];
|
|
+ for (int i = 0; i < intervals.length; ++i) {
|
|
+ counters[i] = new IntervalledCounter(intervals[i]);
|
|
+ }
|
|
+ this.counters = counters;
|
|
+ }
|
|
+
|
|
+ public long getMaxCountBeforeViolation(final double rate) {
|
|
+ long count = Long.MAX_VALUE;
|
|
+ for (final IntervalledCounter counter : this.counters) {
|
|
+ final long sum = counter.getSum();
|
|
+ final long interval = counter.getInterval();
|
|
+ // rate = sum / interval
|
|
+ // so, sum = rate*interval
|
|
+ final long maxSum = (long)Math.ceil(rate * (1.0E-9 * (double)interval));
|
|
+ final long diff = maxSum - sum;
|
|
+ if (diff < count) {
|
|
+ count = diff;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return Math.max(0L, count);
|
|
+ }
|
|
+
|
|
+ public void update(final long time) {
|
|
+ for (final IntervalledCounter counter : this.counters) {
|
|
+ counter.updateCurrentTime(time);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void updateAndAdd(final long count, final long time) {
|
|
+ for (final IntervalledCounter counter : this.counters) {
|
|
+ counter.updateAndAdd(count, time);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void addTime(final long time, final long count) {
|
|
+ for (final IntervalledCounter counter : this.counters) {
|
|
+ counter.addTime(time, count);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public double getMaxRate() {
|
|
+ double ret = 0.0;
|
|
+
|
|
+ for (final IntervalledCounter counter : this.counters) {
|
|
+ final double counterRate = counter.getRate();
|
|
+ if (counterRate > ret) {
|
|
+ ret = counterRate;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // TODO rebase into util patch
|
|
+ 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;
|
|
+ }
|
|
+ }
|
|
+}
|
|
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/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..6fce2a9bce051e21eba8f331007a9752607f69f2 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 + " to player " + player);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(ServerPlayer player) {
|
|
+ if (!this.playersSentChunkTo.remove(player)) {
|
|
+ throw new IllegalStateException("Have not sent chunk " + this.pos + " 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..2d133ae656f7420d6ceec14bc591721cff815479 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,22 @@ 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
|
|
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,6 +854,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
if (!newWithinViewDistance && oldWithinViewDistance) {
|
|
+ // Paper start - replace chunk loader system
|
|
+ if (playerchunk != null) {
|
|
+ playerchunk.removePlayer(player);
|
|
+ }
|
|
+ // Paper end - replace chunk loader system
|
|
player.untrackChunk(pos);
|
|
}
|
|
|
|
@@ -1151,34 +1162,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 +1607,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..736f37979c882e41e7571202df38eb6a2923fcb0 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();
|
|
+ ServerChunkCache.this.chunkMap.level.playerChunkLoader.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 ca5291a9573a62cb5c19539cf5c7aceff11f9829..a3cef477646abf6172d4e50a28ed30f04c30d667 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -275,6 +275,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 1bada55af5d16437da4d16f9ded55f88a6121eb4..e769c1c2a99a13941124c7442d44a509e7859666 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
|
|
|