2367 lines
120 KiB
Diff
2367 lines
120 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
Date: Wed, 1 Feb 2023 21:06:31 -0800
|
|
Subject: [PATCH] New player chunk loader system
|
|
|
|
|
|
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index 43380d5e3a40b64bebdf3c0e7c48eca8998c8ac0..1080e1f67afe5574baca0df50cdb1d029a7a586a 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -164,9 +164,9 @@ public class TimingsExport extends Thread {
|
|
return pair(rule, world.getWorld().getGameRuleValue(rule));
|
|
})),
|
|
// Paper start - replace chunk loader system
|
|
- pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()),
|
|
- pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()),
|
|
- pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())
|
|
+ pair("ticking-distance", world.getWorld().getSimulationDistance()),
|
|
+ pair("no-ticking-distance", world.getWorld().getViewDistance()),
|
|
+ pair("sending-distance", world.getWorld().getSendViewDistance())
|
|
// Paper end - replace chunk loader system
|
|
));
|
|
}));
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
index e77972c4c264100ffdd824bfa2dac58dbbc6d678..562630db2cf5f923bf5b611b828a365e6d60fefb 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
@@ -41,12 +41,7 @@ public final class PlayerChunkLoader {
|
|
}
|
|
|
|
public static int getTickViewDistance(final ServerPlayer player) {
|
|
- final ServerLevel level = (ServerLevel)player.level;
|
|
- final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
|
|
- if (data == null) {
|
|
- return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance();
|
|
- }
|
|
- return data.getTargetTickViewDistance();
|
|
+ throw new UnsupportedOperationException();
|
|
}
|
|
|
|
public static int getLoadViewDistance(final Player player) {
|
|
@@ -54,12 +49,7 @@ public final class PlayerChunkLoader {
|
|
}
|
|
|
|
public static int getLoadViewDistance(final ServerPlayer player) {
|
|
- final ServerLevel level = (ServerLevel)player.level;
|
|
- final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
|
|
- if (data == null) {
|
|
- return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance();
|
|
- }
|
|
- return data.getLoadDistance();
|
|
+ throw new UnsupportedOperationException();
|
|
}
|
|
|
|
public static int getSendViewDistance(final Player player) {
|
|
@@ -67,12 +57,7 @@ public final class PlayerChunkLoader {
|
|
}
|
|
|
|
public static int getSendViewDistance(final ServerPlayer player) {
|
|
- final ServerLevel level = (ServerLevel)player.level;
|
|
- final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
|
|
- if (data == null) {
|
|
- return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance();
|
|
- }
|
|
- return data.getTargetSendViewDistance();
|
|
+ throw new UnsupportedOperationException();
|
|
}
|
|
|
|
protected final ChunkMap chunkMap;
|
|
@@ -872,7 +857,7 @@ public final class PlayerChunkLoader {
|
|
|
|
public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) {
|
|
if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
- this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
+ ((ServerLevel)this.player.level()).getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded
|
|
this.player.connection.connection.execute(onChunkSend);
|
|
} else {
|
|
@@ -882,7 +867,7 @@ public final class PlayerChunkLoader {
|
|
|
|
public void unloadChunk(final int chunkX, final int chunkZ) {
|
|
if (this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
- this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
+ ((ServerLevel)this.player.level()).getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded
|
|
}
|
|
}
|
|
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 0dc94dec1317b3f86d38074c6cbe41ab828cab1d..97edd4e8d3524c839a1765b6515deacae112ff4b 100644
|
|
--- a/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/ChunkSystem.java
|
|
@@ -119,15 +119,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..cf7610b3396d03bf79a899d5d9cfc6debb5b90be
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/chunk/system/RegionizedPlayerChunkLoader.java
|
|
@@ -0,0 +1,1332 @@
|
|
+package io.papermc.paper.chunk.system;
|
|
+
|
|
+import ca.spottedleaf.concurrentutil.collection.SRSWLinkedQueue;
|
|
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
|
|
+import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
|
|
+import io.papermc.paper.chunk.system.io.RegionFileIOThread;
|
|
+import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager;
|
|
+import io.papermc.paper.configuration.GlobalConfiguration;
|
|
+import io.papermc.paper.util.CoordinateUtils;
|
|
+import io.papermc.paper.util.IntegerUtil;
|
|
+import io.papermc.paper.util.TickThread;
|
|
+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
+import it.unimi.dsi.fastutil.longs.LongComparator;
|
|
+import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue;
|
|
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
+import net.minecraft.network.protocol.Packet;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
|
|
+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
|
|
+import net.minecraft.server.level.ChunkMap;
|
|
+import net.minecraft.server.level.ServerLevel;
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
+import net.minecraft.server.level.TicketType;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.GameRules;
|
|
+import net.minecraft.world.level.chunk.ChunkAccess;
|
|
+import net.minecraft.world.level.chunk.ChunkStatus;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
|
+import org.apache.commons.lang3.mutable.MutableObject;
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
+import org.bukkit.entity.Player;
|
|
+import java.lang.invoke.VarHandle;
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
+
|
|
+public class RegionizedPlayerChunkLoader {
|
|
+
|
|
+ public static final TicketType<Long> REGION_PLAYER_TICKET = TicketType.create("region_player_ticket", Long::compareTo);
|
|
+
|
|
+ public static final int MIN_VIEW_DISTANCE = 2;
|
|
+ public static final int MAX_VIEW_DISTANCE = 32;
|
|
+
|
|
+ public static final int TICK_TICKET_LEVEL = 31;
|
|
+ public static final int GENERATED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.FULL);
|
|
+ public static final int LOADED_TICKET_LEVEL = 33 + ChunkStatus.getDistance(ChunkStatus.EMPTY);
|
|
+
|
|
+ public static final record ViewDistances(
|
|
+ int tickViewDistance,
|
|
+ int loadViewDistance,
|
|
+ int sendViewDistance
|
|
+ ) {
|
|
+ public ViewDistances setTickViewDistance(final int distance) {
|
|
+ return new ViewDistances(distance, this.loadViewDistance, this.sendViewDistance);
|
|
+ }
|
|
+
|
|
+ public ViewDistances setLoadViewDistance(final int distance) {
|
|
+ return new ViewDistances(this.tickViewDistance, distance, this.sendViewDistance);
|
|
+ }
|
|
+
|
|
+
|
|
+ public ViewDistances setSendViewDistance(final int distance) {
|
|
+ return new ViewDistances(this.tickViewDistance, this.loadViewDistance, distance);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static int getAPITickViewDistance(final Player player) {
|
|
+ return getAPITickViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ public static int getAPITickViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level();
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPITickDistance();
|
|
+ }
|
|
+ return data.lastTickDistance;
|
|
+ }
|
|
+
|
|
+ public static int getAPIViewDistance(final Player player) {
|
|
+ return getAPIViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ public static int getAPIViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level();
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPIViewDistance();
|
|
+ }
|
|
+ // view distance = load distance + 1
|
|
+ return data.lastLoadDistance - 1;
|
|
+ }
|
|
+
|
|
+ public static int getLoadViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level();
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPIViewDistance();
|
|
+ }
|
|
+ // view distance = load distance + 1
|
|
+ return data.lastLoadDistance - 1;
|
|
+ }
|
|
+
|
|
+ public static int getAPISendViewDistance(final Player player) {
|
|
+ return getAPISendViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ public static int getAPISendViewDistance(final ServerPlayer player) {
|
|
+ final ServerLevel level = (ServerLevel)player.level();
|
|
+ final PlayerChunkLoaderData data = player.chunkLoader;
|
|
+ if (data == null) {
|
|
+ return level.playerChunkLoader.getAPISendViewDistance();
|
|
+ }
|
|
+ return data.lastSendDistance;
|
|
+ }
|
|
+
|
|
+ private final ServerLevel world;
|
|
+
|
|
+ public RegionizedPlayerChunkLoader(final ServerLevel world) {
|
|
+ this.world = world;
|
|
+ }
|
|
+
|
|
+ public void addPlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread(player, "Cannot add player to player chunk loader async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (player.chunkLoader != null) {
|
|
+ throw new IllegalStateException("Player is already added to player chunk loader");
|
|
+ }
|
|
+
|
|
+ final PlayerChunkLoaderData loader = new PlayerChunkLoaderData(this.world, player);
|
|
+
|
|
+ player.chunkLoader = loader;
|
|
+ loader.add();
|
|
+ }
|
|
+
|
|
+ public void updatePlayer(final ServerPlayer player) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader != null) {
|
|
+ loader.update();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread(player, "Cannot remove player from player chunk loader async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+
|
|
+ if (loader == null) {
|
|
+ throw new IllegalStateException("Player is already removed from player chunk loader");
|
|
+ }
|
|
+
|
|
+ loader.remove();
|
|
+ player.chunkLoader = null;
|
|
+ }
|
|
+
|
|
+ public void setSendDistance(final int distance) {
|
|
+ this.world.setSendViewDistance(distance);
|
|
+ }
|
|
+
|
|
+ public void setLoadDistance(final int distance) {
|
|
+ this.world.setLoadViewDistance(distance);
|
|
+ }
|
|
+
|
|
+ public void setTickDistance(final int distance) {
|
|
+ this.world.setTickViewDistance(distance);
|
|
+ }
|
|
+
|
|
+ // Note: follow the player chunk loader so everything stays consistent...
|
|
+ public int getAPITickDistance() {
|
|
+ final ViewDistances distances = this.world.getViewDistances();
|
|
+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance);
|
|
+ return tickViewDistance;
|
|
+ }
|
|
+
|
|
+ public int getAPIViewDistance() {
|
|
+ final ViewDistances distances = this.world.getViewDistances();
|
|
+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance);
|
|
+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance);
|
|
+
|
|
+ // loadDistance = api view distance + 1
|
|
+ return loadDistance - 1;
|
|
+ }
|
|
+
|
|
+ public int getAPISendViewDistance() {
|
|
+ final ViewDistances distances = this.world.getViewDistances();
|
|
+ final int tickViewDistance = PlayerChunkLoaderData.getTickDistance(-1, distances.tickViewDistance);
|
|
+ final int loadDistance = PlayerChunkLoaderData.getLoadViewDistance(tickViewDistance, -1, distances.loadViewDistance);
|
|
+ final int sendViewDistance = PlayerChunkLoaderData.getSendViewDistance(
|
|
+ loadDistance, -1, -1, distances.sendViewDistance
|
|
+ );
|
|
+
|
|
+ return sendViewDistance;
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) {
|
|
+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return loader.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ if (!loader.sentChunks.contains(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ))) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ public void tick() {
|
|
+ TickThread.ensureTickThread("Cannot tick player chunk loader async");
|
|
+ long currTime = System.nanoTime();
|
|
+ for (final ServerPlayer player : new java.util.ArrayList<>(this.world.players())) {
|
|
+ final PlayerChunkLoaderData loader = player.chunkLoader;
|
|
+ if (loader == null || loader.world != this.world) {
|
|
+ // not our problem anymore
|
|
+ continue;
|
|
+ }
|
|
+ loader.update(); // can't invoke plugin logic
|
|
+ loader.updateQueues(currTime);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static long[] generateBFSOrder(final int radius) {
|
|
+ final LongArrayList chunks = new LongArrayList();
|
|
+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue();
|
|
+ final LongOpenHashSet seen = new LongOpenHashSet();
|
|
+
|
|
+ seen.add(CoordinateUtils.getChunkKey(0, 0));
|
|
+ queue.enqueue(CoordinateUtils.getChunkKey(0, 0));
|
|
+ while (!queue.isEmpty()) {
|
|
+ final long chunk = queue.dequeueLong();
|
|
+ final int chunkX = CoordinateUtils.getChunkX(chunk);
|
|
+ final int chunkZ = CoordinateUtils.getChunkZ(chunk);
|
|
+
|
|
+ // important that the addition to the list is here, rather than when enqueueing neighbours
|
|
+ // ensures the order is actually kept
|
|
+ chunks.add(chunk);
|
|
+
|
|
+ // -x
|
|
+ final long n1 = CoordinateUtils.getChunkKey(chunkX - 1, chunkZ);
|
|
+ // -z
|
|
+ final long n2 = CoordinateUtils.getChunkKey(chunkX, chunkZ - 1);
|
|
+ // +x
|
|
+ final long n3 = CoordinateUtils.getChunkKey(chunkX + 1, chunkZ);
|
|
+ // +z
|
|
+ final long n4 = CoordinateUtils.getChunkKey(chunkX, chunkZ + 1);
|
|
+
|
|
+ final long[] list = new long[] {n1, n2, n3, n4};
|
|
+
|
|
+ for (final long neighbour : list) {
|
|
+ final int neighbourX = CoordinateUtils.getChunkX(neighbour);
|
|
+ final int neighbourZ = CoordinateUtils.getChunkZ(neighbour);
|
|
+ if (Math.max(Math.abs(neighbourX), Math.abs(neighbourZ)) > radius) {
|
|
+ // don't enqueue out of range
|
|
+ continue;
|
|
+ }
|
|
+ if (!seen.add(neighbour)) {
|
|
+ continue;
|
|
+ }
|
|
+ queue.enqueue(neighbour);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return chunks.toLongArray();
|
|
+ }
|
|
+
|
|
+ public static final class PlayerChunkLoaderData {
|
|
+
|
|
+ private static final AtomicLong ID_GENERATOR = new AtomicLong();
|
|
+ private final long id = ID_GENERATOR.incrementAndGet();
|
|
+ private final Long idBoxed = Long.valueOf(this.id);
|
|
+
|
|
+ // expected that this list returns for a given radius, the set of chunks ordered
|
|
+ // by manhattan distance
|
|
+ private static final long[][] SEARCH_RADIUS_ITERATION_LIST = new long[65][];
|
|
+ static {
|
|
+ for (int i = 0; i < SEARCH_RADIUS_ITERATION_LIST.length; ++i) {
|
|
+ // a BFS around -x, -z, +x, +z will give increasing manhatten distance
|
|
+ SEARCH_RADIUS_ITERATION_LIST[i] = generateBFSOrder(i);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static final long MAX_RATE = 10_000L;
|
|
+
|
|
+ private final ServerPlayer player;
|
|
+ private final ServerLevel world;
|
|
+
|
|
+ private int lastChunkX = Integer.MIN_VALUE;
|
|
+ private int lastChunkZ = Integer.MIN_VALUE;
|
|
+
|
|
+ private int lastSendDistance = Integer.MIN_VALUE;
|
|
+ private int lastLoadDistance = Integer.MIN_VALUE;
|
|
+ private int lastTickDistance = Integer.MIN_VALUE;
|
|
+
|
|
+ private int lastSentChunkCenterX = Integer.MIN_VALUE;
|
|
+ private int lastSentChunkCenterZ = Integer.MIN_VALUE;
|
|
+
|
|
+ private int lastSentChunkRadius = Integer.MIN_VALUE;
|
|
+ private int lastSentSimulationDistance = Integer.MIN_VALUE;
|
|
+
|
|
+ private boolean canGenerateChunks = true;
|
|
+
|
|
+ private final ArrayDeque<ChunkHolderManager.TicketOperation<?, ?>> delayedTicketOps = new ArrayDeque<>();
|
|
+ private final LongOpenHashSet sentChunks = new LongOpenHashSet();
|
|
+
|
|
+ private static final byte CHUNK_TICKET_STAGE_NONE = 0;
|
|
+ private static final byte CHUNK_TICKET_STAGE_LOADING = 1;
|
|
+ private static final byte CHUNK_TICKET_STAGE_LOADED = 2;
|
|
+ private static final byte CHUNK_TICKET_STAGE_GENERATING = 3;
|
|
+ private static final byte CHUNK_TICKET_STAGE_GENERATED = 4;
|
|
+ private static final byte CHUNK_TICKET_STAGE_TICK = 5;
|
|
+ private static final int[] TICKET_STAGE_TO_LEVEL = new int[] {
|
|
+ ChunkHolderManager.MAX_TICKET_LEVEL + 1,
|
|
+ LOADED_TICKET_LEVEL,
|
|
+ LOADED_TICKET_LEVEL,
|
|
+ GENERATED_TICKET_LEVEL,
|
|
+ GENERATED_TICKET_LEVEL,
|
|
+ TICK_TICKET_LEVEL
|
|
+ };
|
|
+ private final Long2ByteOpenHashMap chunkTicketStage = new Long2ByteOpenHashMap();
|
|
+ {
|
|
+ this.chunkTicketStage.defaultReturnValue(CHUNK_TICKET_STAGE_NONE);
|
|
+ }
|
|
+
|
|
+ // rate limiting
|
|
+ private final AllocatingRateLimiter chunkSendLimiter = new AllocatingRateLimiter();
|
|
+ private final AllocatingRateLimiter chunkLoadTicketLimiter = new AllocatingRateLimiter();
|
|
+ private final AllocatingRateLimiter chunkGenerateTicketLimiter = new AllocatingRateLimiter();
|
|
+
|
|
+ // queues
|
|
+ private final LongComparator CLOSEST_MANHATTAN_DIST = (final long c1, final long c2) -> {
|
|
+ final int c1x = CoordinateUtils.getChunkX(c1);
|
|
+ final int c1z = CoordinateUtils.getChunkZ(c1);
|
|
+
|
|
+ final int c2x = CoordinateUtils.getChunkX(c2);
|
|
+ final int c2z = CoordinateUtils.getChunkZ(c2);
|
|
+
|
|
+ final int centerX = PlayerChunkLoaderData.this.lastChunkX;
|
|
+ final int centerZ = PlayerChunkLoaderData.this.lastChunkZ;
|
|
+
|
|
+ return Integer.compare(
|
|
+ Math.abs(c1x - centerX) + Math.abs(c1z - centerZ),
|
|
+ Math.abs(c2x - centerX) + Math.abs(c2z - centerZ)
|
|
+ );
|
|
+ };
|
|
+ private final LongHeapPriorityQueue sendQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue tickingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue generatingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue genQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue loadingQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+ private final LongHeapPriorityQueue loadQueue = new LongHeapPriorityQueue(CLOSEST_MANHATTAN_DIST);
|
|
+
|
|
+ private volatile boolean removed;
|
|
+
|
|
+ public PlayerChunkLoaderData(final ServerLevel world, final ServerPlayer player) {
|
|
+ this.world = world;
|
|
+ this.player = player;
|
|
+ }
|
|
+
|
|
+ private void flushDelayedTicketOps() {
|
|
+ if (this.delayedTicketOps.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.pushDelayedTicketUpdates(this.delayedTicketOps);
|
|
+ this.delayedTicketOps.clear();
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.tryDrainTicketUpdates();
|
|
+ }
|
|
+
|
|
+ private void pushDelayedTicketOp(final ChunkHolderManager.TicketOperation<?, ?> op) {
|
|
+ this.delayedTicketOps.addLast(op);
|
|
+ }
|
|
+
|
|
+ private void sendChunk(final int chunkX, final int chunkZ) {
|
|
+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
+ this.world.getChunkSource().chunkMap.updateChunkTracking(this.player,
|
|
+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded
|
|
+ return;
|
|
+ }
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ private void sendUnloadChunk(final int chunkX, final int chunkZ) {
|
|
+ if (!this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
+ return;
|
|
+ }
|
|
+ this.sendUnloadChunkRaw(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ private void sendUnloadChunkRaw(final int chunkX, final int chunkZ) {
|
|
+ this.player.serverLevel().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, 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) : playerSendViewDistance
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private Packet<?> updateClientChunkRadius(final int radius) {
|
|
+ this.lastSentChunkRadius = radius;
|
|
+ return new ClientboundSetChunkCacheRadiusPacket(radius);
|
|
+ }
|
|
+
|
|
+ private Packet<?> updateClientSimulationDistance(final int distance) {
|
|
+ this.lastSentSimulationDistance = distance;
|
|
+ return new ClientboundSetSimulationDistancePacket(distance);
|
|
+ }
|
|
+
|
|
+ private Packet<?> updateClientChunkCenter(final int chunkX, final int chunkZ) {
|
|
+ this.lastSentChunkCenterX = chunkX;
|
|
+ this.lastSentChunkCenterZ = chunkZ;
|
|
+ return new ClientboundSetChunkCacheCenterPacket(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ private boolean canPlayerGenerateChunks() {
|
|
+ return !this.player.isSpectator() || this.world.getGameRules().getBoolean(GameRules.RULE_SPECTATORSGENERATECHUNKS);
|
|
+ }
|
|
+
|
|
+ private double getMaxChunkLoadRate() {
|
|
+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkLoadRate;
|
|
+
|
|
+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate);
|
|
+ }
|
|
+
|
|
+ private double getMaxChunkGenRate() {
|
|
+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkGenerateRate;
|
|
+
|
|
+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate);
|
|
+ }
|
|
+
|
|
+ private double getMaxChunkSendRate() {
|
|
+ final double configRate = GlobalConfiguration.get().chunkLoadingBasic.playerMaxChunkSendRate;
|
|
+
|
|
+ return configRate < 0.0 || configRate > (double)MAX_RATE ? (double)MAX_RATE : Math.max(1.0, configRate);
|
|
+ }
|
|
+
|
|
+ private long getMaxChunkLoads() {
|
|
+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L);
|
|
+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkLoads;
|
|
+ if (configLimit == 0L) {
|
|
+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active
|
|
+ configLimit = Math.max(5L, radiusChunks / 5L);
|
|
+ } else if (configLimit < 0L) {
|
|
+ configLimit = Integer.MAX_VALUE;
|
|
+ } // else: use the value configured
|
|
+ configLimit = configLimit - this.loadingQueue.size();
|
|
+
|
|
+ return configLimit;
|
|
+ }
|
|
+
|
|
+ private long getMaxChunkGenerates() {
|
|
+ final long radiusChunks = (2L * this.lastLoadDistance + 1L) * (2L * this.lastLoadDistance + 1L);
|
|
+ long configLimit = GlobalConfiguration.get().chunkLoadingAdvanced.playerMaxConcurrentChunkGenerates;
|
|
+ if (configLimit == 0L) {
|
|
+ // by default, only allow 1/5th of the chunks in the view distance to be concurrently active
|
|
+ configLimit = Math.max(5L, radiusChunks / 5L);
|
|
+ } else if (configLimit < 0L) {
|
|
+ configLimit = Integer.MAX_VALUE;
|
|
+ } // else: use the value configured
|
|
+ configLimit = configLimit - this.generatingQueue.size();
|
|
+
|
|
+ return configLimit;
|
|
+ }
|
|
+
|
|
+ private boolean wantChunkSent(final int chunkX, final int chunkZ) {
|
|
+ final int dx = this.lastChunkX - chunkX;
|
|
+ final int dz = this.lastChunkZ - chunkZ;
|
|
+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastSendDistance && wantChunkLoaded(
|
|
+ this.lastChunkX, this.lastChunkZ, chunkX, chunkZ, this.lastSendDistance
|
|
+ );
|
|
+ }
|
|
+
|
|
+ private boolean wantChunkTicked(final int chunkX, final int chunkZ) {
|
|
+ final int dx = this.lastChunkX - chunkX;
|
|
+ final int dz = this.lastChunkZ - chunkZ;
|
|
+ return Math.max(Math.abs(dx), Math.abs(dz)) <= this.lastTickDistance;
|
|
+ }
|
|
+
|
|
+ void updateQueues(final long time) {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot tick player chunk loader async");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Ticking removed player chunk loader");
|
|
+ }
|
|
+ // update rate limits
|
|
+ final double loadRate = this.getMaxChunkLoadRate();
|
|
+ final double genRate = this.getMaxChunkGenRate();
|
|
+ final double sendRate = this.getMaxChunkSendRate();
|
|
+
|
|
+ this.chunkLoadTicketLimiter.tickAllocation(time, loadRate, loadRate);
|
|
+ this.chunkGenerateTicketLimiter.tickAllocation(time, genRate, genRate);
|
|
+ this.chunkSendLimiter.tickAllocation(time, sendRate, sendRate);
|
|
+
|
|
+ // try to progress chunk loads
|
|
+ while (!this.loadingQueue.isEmpty()) {
|
|
+ final long pendingLoadChunk = this.loadingQueue.firstLong();
|
|
+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingLoadChunk);
|
|
+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingLoadChunk);
|
|
+ final ChunkAccess pending = this.world.chunkSource.getChunkAtImmediately(pendingChunkX, pendingChunkZ);
|
|
+ if (pending == null) {
|
|
+ // nothing to do here
|
|
+ break;
|
|
+ }
|
|
+ // chunk has loaded, so we can take it out of the queue
|
|
+ this.loadingQueue.dequeueLong();
|
|
+
|
|
+ // try to move to generate queue
|
|
+ final byte prev = this.chunkTicketStage.put(pendingLoadChunk, CHUNK_TICKET_STAGE_LOADED);
|
|
+ if (prev != CHUNK_TICKET_STAGE_LOADING) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADING + ", not " + prev);
|
|
+ }
|
|
+
|
|
+ if (this.canGenerateChunks || this.isLoadedChunkGeneratable(pending)) {
|
|
+ this.genQueue.enqueue(pendingLoadChunk);
|
|
+ } // else: don't want to generate, so just leave it loaded
|
|
+ }
|
|
+
|
|
+ // try to push more chunk loads
|
|
+ final long maxLoads = Math.max(0L, Math.min(MAX_RATE, Math.min(this.loadQueue.size(), this.getMaxChunkLoads())));
|
|
+ final int maxLoadsThisTick = (int)this.chunkLoadTicketLimiter.takeAllocation(time, loadRate, maxLoads);
|
|
+ if (maxLoadsThisTick > 0) {
|
|
+ final LongArrayList chunks = new LongArrayList(maxLoadsThisTick);
|
|
+ for (int i = 0; i < maxLoadsThisTick; ++i) {
|
|
+ final long chunk = this.loadQueue.dequeueLong();
|
|
+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_LOADING);
|
|
+ if (prev != CHUNK_TICKET_STAGE_NONE) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_NONE + ", not " + prev);
|
|
+ }
|
|
+ this.pushDelayedTicketOp(
|
|
+ ChunkHolderManager.TicketOperation.addOp(
|
|
+ chunk,
|
|
+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed
|
|
+ )
|
|
+ );
|
|
+ chunks.add(chunk);
|
|
+ this.loadingQueue.enqueue(chunk);
|
|
+ }
|
|
+
|
|
+ // here we need to flush tickets, as scheduleChunkLoad requires tickets to be propagated with addTicket = false
|
|
+ this.flushDelayedTicketOps();
|
|
+ // we only need to call scheduleChunkLoad because the loaded ticket level is not enough to start the chunk
|
|
+ // load - only generate ticket levels start anything, but they start generation...
|
|
+ // propagate levels
|
|
+ // Note: this CAN call plugin logic, so it is VITAL that our bookkeeping logic is completely done by the time this is invoked
|
|
+ this.world.chunkTaskScheduler.chunkHolderManager.processTicketUpdates();
|
|
+
|
|
+ if (this.removed) {
|
|
+ // process ticket updates may invoke plugin logic, which may remove this player
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (int i = 0; i < maxLoadsThisTick; ++i) {
|
|
+ final long queuedLoadChunk = chunks.getLong(i);
|
|
+ final int queuedChunkX = CoordinateUtils.getChunkX(queuedLoadChunk);
|
|
+ final int queuedChunkZ = CoordinateUtils.getChunkZ(queuedLoadChunk);
|
|
+ this.world.chunkTaskScheduler.scheduleChunkLoad(
|
|
+ queuedChunkX, queuedChunkZ, ChunkStatus.EMPTY, false, PrioritisedExecutor.Priority.NORMAL, null
|
|
+ );
|
|
+ if (this.removed) {
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // try to progress chunk generations
|
|
+ while (!this.generatingQueue.isEmpty()) {
|
|
+ final long pendingGenChunk = this.generatingQueue.firstLong();
|
|
+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingGenChunk);
|
|
+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingGenChunk);
|
|
+ final LevelChunk pending = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingChunkX, pendingChunkZ);
|
|
+ if (pending == null) {
|
|
+ // nothing to do here
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ // chunk has generated, so we can take it out of queue
|
|
+ this.generatingQueue.dequeueLong();
|
|
+
|
|
+ final byte prev = this.chunkTicketStage.put(pendingGenChunk, CHUNK_TICKET_STAGE_GENERATED);
|
|
+ if (prev != CHUNK_TICKET_STAGE_GENERATING) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATING + ", not " + prev);
|
|
+ }
|
|
+
|
|
+ // try to move to send queue
|
|
+ if (this.wantChunkSent(pendingChunkX, pendingChunkZ)) {
|
|
+ this.sendQueue.enqueue(pendingGenChunk);
|
|
+ }
|
|
+ // try to move to tick queue
|
|
+ if (this.wantChunkTicked(pendingChunkX, pendingChunkZ)) {
|
|
+ this.tickingQueue.enqueue(pendingGenChunk);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // try to push more chunk generations
|
|
+ final long maxGens = Math.max(0L, Math.min(MAX_RATE, Math.min(this.genQueue.size(), this.getMaxChunkGenerates())));
|
|
+ final int maxGensThisTick = (int)this.chunkGenerateTicketLimiter.takeAllocation(time, genRate, maxGens);
|
|
+ for (int i = 0; i < maxGensThisTick; ++i) {
|
|
+ final long chunk = this.genQueue.dequeueLong();
|
|
+ final byte prev = this.chunkTicketStage.put(chunk, CHUNK_TICKET_STAGE_GENERATING);
|
|
+ if (prev != CHUNK_TICKET_STAGE_LOADED) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_LOADED + ", not " + prev);
|
|
+ }
|
|
+ this.pushDelayedTicketOp(
|
|
+ ChunkHolderManager.TicketOperation.addAndRemove(
|
|
+ chunk,
|
|
+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed,
|
|
+ REGION_PLAYER_TICKET, LOADED_TICKET_LEVEL, this.idBoxed
|
|
+ )
|
|
+ );
|
|
+ this.generatingQueue.enqueue(chunk);
|
|
+ }
|
|
+
|
|
+ // try to pull ticking chunks
|
|
+ tick_check_outer:
|
|
+ while (!this.tickingQueue.isEmpty()) {
|
|
+ final long pendingTicking = this.tickingQueue.firstLong();
|
|
+ final int pendingChunkX = CoordinateUtils.getChunkX(pendingTicking);
|
|
+ final int pendingChunkZ = CoordinateUtils.getChunkZ(pendingTicking);
|
|
+
|
|
+ final int tickingReq = 2;
|
|
+ for (int dz = -tickingReq; dz <= tickingReq; ++dz) {
|
|
+ for (int dx = -tickingReq; dx <= tickingReq; ++dx) {
|
|
+ if ((dx | dz) == 0) {
|
|
+ continue;
|
|
+ }
|
|
+ final long neighbour = CoordinateUtils.getChunkKey(dx + pendingChunkX, dz + pendingChunkZ);
|
|
+ final byte stage = this.chunkTicketStage.get(neighbour);
|
|
+ if (stage != CHUNK_TICKET_STAGE_GENERATED && stage != CHUNK_TICKET_STAGE_TICK) {
|
|
+ break tick_check_outer;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // only gets here if all neighbours were marked as generated or ticking themselves
|
|
+ this.tickingQueue.dequeueLong();
|
|
+ this.pushDelayedTicketOp(
|
|
+ ChunkHolderManager.TicketOperation.addAndRemove(
|
|
+ pendingTicking,
|
|
+ REGION_PLAYER_TICKET, TICK_TICKET_LEVEL, this.idBoxed,
|
|
+ REGION_PLAYER_TICKET, GENERATED_TICKET_LEVEL, this.idBoxed
|
|
+ )
|
|
+ );
|
|
+ // there is no queue to add after ticking
|
|
+ final byte prev = this.chunkTicketStage.put(pendingTicking, CHUNK_TICKET_STAGE_TICK);
|
|
+ if (prev != CHUNK_TICKET_STAGE_GENERATED) {
|
|
+ throw new IllegalStateException("Previous state should be " + CHUNK_TICKET_STAGE_GENERATED + ", not " + prev);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // try to pull sending chunks
|
|
+ final long maxSends = Math.max(0L, Math.min(MAX_RATE, Integer.MAX_VALUE)); // no logic to track concurrent sends
|
|
+ final int maxSendsThisTick = Math.min((int)this.chunkSendLimiter.takeAllocation(time, sendRate, maxSends), this.sendQueue.size());
|
|
+ // we do not return sends that we took from the allocation back because we want to limit the max send rate, not target it
|
|
+ for (int i = 0; i < maxSendsThisTick; ++i) {
|
|
+ final long pendingSend = this.sendQueue.firstLong();
|
|
+ final int pendingSendX = CoordinateUtils.getChunkX(pendingSend);
|
|
+ final int pendingSendZ = CoordinateUtils.getChunkZ(pendingSend);
|
|
+ final LevelChunk chunk = this.world.chunkSource.getChunkAtIfLoadedMainThreadNoCache(pendingSendX, pendingSendZ);
|
|
+ if (!chunk.areNeighboursLoaded(1) || !TickThread.isTickThreadFor(this.world, pendingSendX, pendingSendZ)) {
|
|
+ // nothing to do
|
|
+ // the target chunk may not be owned by this region, but this should be resolved in the future
|
|
+ break;
|
|
+ }
|
|
+ this.sendQueue.dequeueLong();
|
|
+
|
|
+ this.sendChunk(pendingSendX, pendingSendZ);
|
|
+ if (this.removed) {
|
|
+ // sendChunk may invoke plugin logic
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ this.flushDelayedTicketOps();
|
|
+ // we assume propagate ticket levels happens after this call
|
|
+ }
|
|
+
|
|
+ void add() {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Adding removed player chunk loader");
|
|
+ }
|
|
+ final ViewDistances playerDistances = this.player.getViewDistances();
|
|
+ final ViewDistances worldDistances = this.world.getViewDistances();
|
|
+ final int chunkX = this.player.chunkPosition().x;
|
|
+ final int chunkZ = this.player.chunkPosition().z;
|
|
+
|
|
+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance);
|
|
+ // load view cannot be less-than tick view + 1
|
|
+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance);
|
|
+ // send view cannot be greater-than load view
|
|
+ final int clientViewDistance = getClientViewDistance(this.player);
|
|
+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance);
|
|
+
|
|
+ // send view distances
|
|
+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance));
|
|
+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance));
|
|
+
|
|
+ // add to distance maps
|
|
+ this.broadcastMap.add(chunkX, chunkZ, sendViewDistance);
|
|
+ this.loadTicketCleanup.add(chunkX, chunkZ, loadViewDistance + 1);
|
|
+ this.tickMap.add(chunkX, chunkZ, tickViewDistance);
|
|
+
|
|
+ // update chunk center
|
|
+ this.player.connection.send(this.updateClientChunkCenter(chunkX, chunkZ));
|
|
+
|
|
+ // now we can update
|
|
+ this.update();
|
|
+ }
|
|
+
|
|
+ private boolean isLoadedChunkGeneratable(final int chunkX, final int chunkZ) {
|
|
+ return this.isLoadedChunkGeneratable(this.world.chunkSource.getChunkAtImmediately(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ private boolean isLoadedChunkGeneratable(final ChunkAccess chunkAccess) {
|
|
+ final BelowZeroRetrogen belowZeroRetrogen;
|
|
+ return chunkAccess != null && (
|
|
+ chunkAccess.getStatus() == ChunkStatus.FULL ||
|
|
+ ((belowZeroRetrogen = chunkAccess.getBelowZeroRetrogen()) != null && belowZeroRetrogen.targetStatus().isOrAfter(ChunkStatus.FULL))
|
|
+ );
|
|
+ }
|
|
+
|
|
+ void update() {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot update player asynchronously");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Updating removed player chunk loader");
|
|
+ }
|
|
+ final ViewDistances playerDistances = this.player.getViewDistances();
|
|
+ final ViewDistances worldDistances = this.world.getViewDistances();
|
|
+
|
|
+ final int tickViewDistance = getTickDistance(playerDistances.tickViewDistance, worldDistances.tickViewDistance);
|
|
+ // load view cannot be less-than tick view + 1
|
|
+ final int loadViewDistance = getLoadViewDistance(tickViewDistance, playerDistances.loadViewDistance, worldDistances.loadViewDistance);
|
|
+ // send view cannot be greater-than load view
|
|
+ final int clientViewDistance = getClientViewDistance(this.player);
|
|
+ final int sendViewDistance = getSendViewDistance(loadViewDistance, clientViewDistance, playerDistances.sendViewDistance, worldDistances.sendViewDistance);
|
|
+
|
|
+ final ChunkPos playerPos = this.player.chunkPosition();
|
|
+ final boolean canGenerateChunks = this.canPlayerGenerateChunks();
|
|
+ final int currentChunkX = playerPos.x;
|
|
+ final int currentChunkZ = playerPos.z;
|
|
+
|
|
+ final int prevChunkX = this.lastChunkX;
|
|
+ final int prevChunkZ = this.lastChunkZ;
|
|
+
|
|
+ if (
|
|
+ // has view distance stayed the same?
|
|
+ sendViewDistance == this.lastSendDistance
|
|
+ && loadViewDistance == this.lastLoadDistance
|
|
+ && tickViewDistance == this.lastTickDistance
|
|
+
|
|
+ // has our chunk stayed the same?
|
|
+ && prevChunkX == currentChunkX
|
|
+ && prevChunkZ == currentChunkZ
|
|
+
|
|
+ // can we still generate chunks?
|
|
+ && this.canGenerateChunks == canGenerateChunks
|
|
+ ) {
|
|
+ // nothing we care about changed, so we're not re-calculating
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // update distance maps
|
|
+ this.broadcastMap.update(currentChunkX, currentChunkZ, sendViewDistance);
|
|
+ this.loadTicketCleanup.update(currentChunkX, currentChunkZ, loadViewDistance + 1);
|
|
+ this.tickMap.update(currentChunkX, currentChunkZ, tickViewDistance);
|
|
+ if (sendViewDistance > loadViewDistance || tickViewDistance > loadViewDistance) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ // update VDs for client
|
|
+ // this should be after the distance map updates, as they will send unload packets
|
|
+ if (this.lastSentChunkRadius != sendViewDistance) {
|
|
+ this.player.connection.send(this.updateClientChunkRadius(sendViewDistance));
|
|
+ }
|
|
+ if (this.lastSentSimulationDistance != tickViewDistance) {
|
|
+ this.player.connection.send(this.updateClientSimulationDistance(tickViewDistance));
|
|
+ }
|
|
+
|
|
+ this.sendQueue.clear();
|
|
+ this.tickingQueue.clear();
|
|
+ this.generatingQueue.clear();
|
|
+ this.genQueue.clear();
|
|
+ this.loadingQueue.clear();
|
|
+ this.loadQueue.clear();
|
|
+
|
|
+ this.lastChunkX = currentChunkX;
|
|
+ this.lastChunkZ = currentChunkZ;
|
|
+ this.lastSendDistance = sendViewDistance;
|
|
+ this.lastLoadDistance = loadViewDistance;
|
|
+ this.lastTickDistance = tickViewDistance;
|
|
+ this.canGenerateChunks = canGenerateChunks;
|
|
+
|
|
+ // +1 since we need to load chunks +1 around the load view distance...
|
|
+ final long[] toIterate = SEARCH_RADIUS_ITERATION_LIST[loadViewDistance + 1];
|
|
+ // the iteration order is by increasing manhattan distance - so, we do NOT need to
|
|
+ // sort anything in the queue!
|
|
+ for (final long deltaChunk : toIterate) {
|
|
+ final int dx = CoordinateUtils.getChunkX(deltaChunk);
|
|
+ final int dz = CoordinateUtils.getChunkZ(deltaChunk);
|
|
+ final int chunkX = dx + currentChunkX;
|
|
+ final int chunkZ = dz + currentChunkZ;
|
|
+ final long chunk = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz));
|
|
+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz);
|
|
+
|
|
+ // since chunk sending is not by radius alone, we need an extra check here to account for
|
|
+ // everything <= sendDistance
|
|
+ // Note: Vanilla may want to send chunks outside the send view distance, so we do need
|
|
+ // the dist <= view check
|
|
+ final boolean sendChunk = squareDistance <= sendViewDistance
|
|
+ && wantChunkLoaded(currentChunkX, currentChunkZ, chunkX, chunkZ, sendViewDistance);
|
|
+ final boolean sentChunk = sendChunk ? this.sentChunks.contains(chunk) : this.sentChunks.remove(chunk);
|
|
+
|
|
+ if (!sendChunk && sentChunk) {
|
|
+ // have sent the chunk, but don't want it anymore
|
|
+ // unload it now
|
|
+ this.sendUnloadChunkRaw(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ final byte stage = this.chunkTicketStage.get(chunk);
|
|
+ switch (stage) {
|
|
+ case CHUNK_TICKET_STAGE_NONE: {
|
|
+ // we want the chunk to be at least loaded
|
|
+ this.loadQueue.enqueue(chunk);
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_LOADING: {
|
|
+ this.loadingQueue.enqueue(chunk);
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_LOADED: {
|
|
+ if (canGenerateChunks || this.isLoadedChunkGeneratable(chunkX, chunkZ)) {
|
|
+ this.genQueue.enqueue(chunk);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_GENERATING: {
|
|
+ this.generatingQueue.enqueue(chunk);
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_GENERATED: {
|
|
+ if (sendChunk && !sentChunk) {
|
|
+ this.sendQueue.enqueue(chunk);
|
|
+ }
|
|
+ if (squareDistance <= tickViewDistance) {
|
|
+ this.tickingQueue.enqueue(chunk);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ case CHUNK_TICKET_STAGE_TICK: {
|
|
+ if (sendChunk && !sentChunk) {
|
|
+ this.sendQueue.enqueue(chunk);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ default: {
|
|
+ throw new IllegalStateException("Unknown stage: " + stage);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // update the chunk center
|
|
+ // this must be done last so that the client does not ignore any of our unload chunk packets above
|
|
+ if (this.lastSentChunkCenterX != currentChunkX || this.lastSentChunkCenterZ != currentChunkZ) {
|
|
+ this.player.connection.send(this.updateClientChunkCenter(currentChunkX, currentChunkZ));
|
|
+ }
|
|
+
|
|
+ this.flushDelayedTicketOps();
|
|
+ }
|
|
+
|
|
+ void remove() {
|
|
+ TickThread.ensureTickThread(this.player, "Cannot add player asynchronously");
|
|
+ if (this.removed) {
|
|
+ throw new IllegalStateException("Removing removed player chunk loader");
|
|
+ }
|
|
+ this.removed = true;
|
|
+ // sends the chunk unload packets
|
|
+ this.broadcastMap.remove();
|
|
+ // cleans up loading/generating tickets
|
|
+ this.loadTicketCleanup.remove();
|
|
+ // cleans up ticking tickets
|
|
+ this.tickMap.remove();
|
|
+
|
|
+ // purge queues
|
|
+ this.sendQueue.clear();
|
|
+ this.tickingQueue.clear();
|
|
+ this.generatingQueue.clear();
|
|
+ this.genQueue.clear();
|
|
+ this.loadingQueue.clear();
|
|
+ this.loadQueue.clear();
|
|
+
|
|
+ // flush ticket changes
|
|
+ this.flushDelayedTicketOps();
|
|
+
|
|
+ // now all tickets should be removed, which is all of our external state
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // TODO rebase into util patch
|
|
+ private static final class AllocatingRateLimiter {
|
|
+
|
|
+ // max difference granularity in ns
|
|
+ private static final long MAX_GRANULARITY = TimeUnit.SECONDS.toNanos(1L);
|
|
+
|
|
+ private double allocation;
|
|
+ private long lastAllocationUpdate;
|
|
+ private double takeCarry;
|
|
+ private long lastTakeUpdate;
|
|
+
|
|
+ // rate in units/s, and time in ns
|
|
+ public void tickAllocation(final long time, final double rate, final double maxAllocation) {
|
|
+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastAllocationUpdate);
|
|
+ this.lastAllocationUpdate = time;
|
|
+
|
|
+ this.allocation = Math.min(maxAllocation - this.takeCarry, this.allocation + rate * (diff*1.0E-9D));
|
|
+ }
|
|
+
|
|
+ // rate in units/s, and time in ns
|
|
+ public long takeAllocation(final long time, final double rate, final long maxTake) {
|
|
+ if (maxTake < 1L) {
|
|
+ return 0L;
|
|
+ }
|
|
+
|
|
+ double ret = this.takeCarry;
|
|
+ final long diff = Math.min(MAX_GRANULARITY, time - this.lastTakeUpdate);
|
|
+ this.lastTakeUpdate = time;
|
|
+
|
|
+ // note: abs(takeCarry) <= 1.0
|
|
+ final double take = Math.min(Math.min((double)maxTake - this.takeCarry, this.allocation), rate * (diff*1.0E-9));
|
|
+
|
|
+ ret += take;
|
|
+ this.allocation -= take;
|
|
+
|
|
+ final long retInteger = (long)Math.floor(ret);
|
|
+ this.takeCarry = ret - (double)retInteger;
|
|
+
|
|
+ return retInteger;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static abstract class SingleUserAreaMap<T> {
|
|
+
|
|
+ private static final int NOT_SET = Integer.MIN_VALUE;
|
|
+
|
|
+ private final T parameter;
|
|
+ private int lastChunkX = NOT_SET;
|
|
+ private int lastChunkZ = NOT_SET;
|
|
+ private int distance = NOT_SET;
|
|
+
|
|
+ public SingleUserAreaMap(final T parameter) {
|
|
+ this.parameter = parameter;
|
|
+ }
|
|
+
|
|
+ /* math sign function except 0 returns 1 */
|
|
+ protected static int sign(int val) {
|
|
+ return 1 | (val >> (Integer.SIZE - 1));
|
|
+ }
|
|
+
|
|
+ protected abstract void addCallback(final T parameter, final int chunkX, final int chunkZ);
|
|
+
|
|
+ protected abstract void removeCallback(final T parameter, final int chunkX, final int chunkZ);
|
|
+
|
|
+ private void addToNew(final T parameter, final int chunkX, final int chunkZ, final int distance) {
|
|
+ final int maxX = chunkX + distance;
|
|
+ final int maxZ = chunkZ + distance;
|
|
+
|
|
+ for (int cx = chunkX - distance; cx <= maxX; ++cx) {
|
|
+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
|
|
+ this.addCallback(parameter, cx, cz);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void removeFromOld(final T parameter, final int chunkX, final int chunkZ, final int distance) {
|
|
+ final int maxX = chunkX + distance;
|
|
+ final int maxZ = chunkZ + distance;
|
|
+
|
|
+ for (int cx = chunkX - distance; cx <= maxX; ++cx) {
|
|
+ for (int cz = chunkZ - distance; cz <= maxZ; ++cz) {
|
|
+ this.removeCallback(parameter, cx, cz);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public final boolean add(final int chunkX, final int chunkZ, final int distance) {
|
|
+ if (distance < 0) {
|
|
+ throw new IllegalArgumentException(Integer.toString(distance));
|
|
+ }
|
|
+ if (this.lastChunkX != NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+ this.lastChunkX = chunkX;
|
|
+ this.lastChunkZ = chunkZ;
|
|
+ this.distance = distance;
|
|
+
|
|
+ this.addToNew(this.parameter, chunkX, chunkZ, distance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final boolean update(final int toX, final int toZ, final int newViewDistance) {
|
|
+ if (newViewDistance < 0) {
|
|
+ throw new IllegalArgumentException(Integer.toString(newViewDistance));
|
|
+ }
|
|
+ final int fromX = this.lastChunkX;
|
|
+ final int fromZ = this.lastChunkZ;
|
|
+ final int oldViewDistance = this.distance;
|
|
+ if (fromX == NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ this.lastChunkX = toX;
|
|
+ this.lastChunkZ = toZ;
|
|
+
|
|
+ final T parameter = this.parameter;
|
|
+
|
|
+
|
|
+ final int dx = toX - fromX;
|
|
+ final int dz = toZ - fromZ;
|
|
+
|
|
+ final int totalX = IntegerUtil.branchlessAbs(fromX - toX);
|
|
+ final int totalZ = IntegerUtil.branchlessAbs(fromZ - toZ);
|
|
+
|
|
+ if (Math.max(totalX, totalZ) > (2 * Math.max(newViewDistance, oldViewDistance))) {
|
|
+ // teleported?
|
|
+ this.removeFromOld(parameter, fromX, fromZ, oldViewDistance);
|
|
+ this.addToNew(parameter, toX, toZ, newViewDistance);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ if (oldViewDistance != newViewDistance) {
|
|
+ // remove loop
|
|
+
|
|
+ final int oldMinX = fromX - oldViewDistance;
|
|
+ final int oldMinZ = fromZ - oldViewDistance;
|
|
+ final int oldMaxX = fromX + oldViewDistance;
|
|
+ final int oldMaxZ = fromZ + oldViewDistance;
|
|
+ for (int currX = oldMinX; currX <= oldMaxX; ++currX) {
|
|
+ for (int currZ = oldMinZ; currZ <= oldMaxZ; ++currZ) {
|
|
+
|
|
+ // only remove if we're outside the new view distance...
|
|
+ if (Math.max(IntegerUtil.branchlessAbs(currX - toX), IntegerUtil.branchlessAbs(currZ - toZ)) > newViewDistance) {
|
|
+ this.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // add loop
|
|
+
|
|
+ final int newMinX = toX - newViewDistance;
|
|
+ final int newMinZ = toZ - newViewDistance;
|
|
+ final int newMaxX = toX + newViewDistance;
|
|
+ final int newMaxZ = toZ + newViewDistance;
|
|
+ for (int currX = newMinX; currX <= newMaxX; ++currX) {
|
|
+ for (int currZ = newMinZ; currZ <= newMaxZ; ++currZ) {
|
|
+
|
|
+ // only add if we're outside the old view distance...
|
|
+ if (Math.max(IntegerUtil.branchlessAbs(currX - fromX), IntegerUtil.branchlessAbs(currZ - fromZ)) > oldViewDistance) {
|
|
+ this.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // x axis is width
|
|
+ // z axis is height
|
|
+ // right refers to the x axis of where we moved
|
|
+ // top refers to the z axis of where we moved
|
|
+
|
|
+ // same view distance
|
|
+
|
|
+ // used for relative positioning
|
|
+ final int up = sign(dz); // 1 if dz >= 0, -1 otherwise
|
|
+ final int right = sign(dx); // 1 if dx >= 0, -1 otherwise
|
|
+
|
|
+ // The area excluded by overlapping the two view distance squares creates four rectangles:
|
|
+ // Two on the left, and two on the right. The ones on the left we consider the "removed" section
|
|
+ // and on the right the "added" section.
|
|
+ // https://i.imgur.com/MrnOBgI.png is a reference image. Note that the outside border is not actually
|
|
+ // exclusive to the regions they surround.
|
|
+
|
|
+ // 4 points of the rectangle
|
|
+ int maxX; // exclusive
|
|
+ int minX; // inclusive
|
|
+ int maxZ; // exclusive
|
|
+ int minZ; // inclusive
|
|
+
|
|
+ if (dx != 0) {
|
|
+ // handle right addition
|
|
+
|
|
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
|
|
+ minX = fromX + (oldViewDistance * right) + right; // inclusive
|
|
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
|
|
+ minZ = toZ - (oldViewDistance * up); // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dz != 0) {
|
|
+ // handle up addition
|
|
+
|
|
+ maxX = toX + (oldViewDistance * right) + right; // exclusive
|
|
+ minX = toX - (oldViewDistance * right); // inclusive
|
|
+ maxZ = toZ + (oldViewDistance * up) + up; // exclusive
|
|
+ minZ = fromZ + (oldViewDistance * up) + up; // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.addCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dx != 0) {
|
|
+ // handle left removal
|
|
+
|
|
+ maxX = toX - (oldViewDistance * right); // exclusive
|
|
+ minX = fromX - (oldViewDistance * right); // inclusive
|
|
+ maxZ = fromZ + (oldViewDistance * up) + up; // exclusive
|
|
+ minZ = toZ - (oldViewDistance * up); // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (dz != 0) {
|
|
+ // handle down removal
|
|
+
|
|
+ maxX = fromX + (oldViewDistance * right) + right; // exclusive
|
|
+ minX = fromX - (oldViewDistance * right); // inclusive
|
|
+ maxZ = toZ - (oldViewDistance * up); // exclusive
|
|
+ minZ = fromZ - (oldViewDistance * up); // inclusive
|
|
+
|
|
+ for (int currX = minX; currX != maxX; currX += right) {
|
|
+ for (int currZ = minZ; currZ != maxZ; currZ += up) {
|
|
+ this.removeCallback(parameter, currX, currZ);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ public final boolean remove() {
|
|
+ final int chunkX = this.lastChunkX;
|
|
+ final int chunkZ = this.lastChunkZ;
|
|
+ final int distance = this.distance;
|
|
+ if (chunkX == NOT_SET) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ this.lastChunkX = this.lastChunkZ = this.distance = NOT_SET;
|
|
+
|
|
+ this.removeFromOld(this.parameter, chunkX, chunkZ, distance);
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ static final class CountedSRSWLinkedQueue<E> {
|
|
+
|
|
+ private final SRSWLinkedQueue<E> queue = new SRSWLinkedQueue<>();
|
|
+ private volatile long countAdded;
|
|
+ private volatile long countRemoved;
|
|
+
|
|
+ private static final VarHandle COUNT_ADDED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countAdded", long.class);
|
|
+ private static final VarHandle COUNT_REMOVED_HANDLE = ConcurrentUtil.getVarHandle(CountedSRSWLinkedQueue.class, "countRemoved", long.class);
|
|
+
|
|
+ private long getCountAddedPlain() {
|
|
+ return (long)COUNT_ADDED_HANDLE.get(this);
|
|
+ }
|
|
+
|
|
+ private long getCountAddedAcquire() {
|
|
+ return (long)COUNT_ADDED_HANDLE.getAcquire(this);
|
|
+ }
|
|
+
|
|
+ private void setCountAddedRelease(final long to) {
|
|
+ COUNT_ADDED_HANDLE.setRelease(this, to);
|
|
+ }
|
|
+
|
|
+ private long getCountRemovedPlain() {
|
|
+ return (long)COUNT_REMOVED_HANDLE.get(this);
|
|
+ }
|
|
+
|
|
+ private long getCountRemovedAcquire() {
|
|
+ return (long)COUNT_REMOVED_HANDLE.getAcquire(this);
|
|
+ }
|
|
+
|
|
+ private void setCountRemovedRelease(final long to) {
|
|
+ COUNT_REMOVED_HANDLE.setRelease(this, to);
|
|
+ }
|
|
+
|
|
+ public void add(final E element) {
|
|
+ this.setCountAddedRelease(this.getCountAddedPlain() + 1L);
|
|
+ this.queue.addLast(element);
|
|
+ }
|
|
+
|
|
+ public E poll() {
|
|
+ final E ret = this.queue.poll();
|
|
+ if (ret != null) {
|
|
+ this.setCountRemovedRelease(this.getCountRemovedPlain() + 1L);
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ public long size() {
|
|
+ final long removed = this.getCountRemovedAcquire();
|
|
+ final long added = this.getCountAddedAcquire();
|
|
+
|
|
+ return added - removed;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
index 748cc48c6c42c694d1c9b685e96fbe6d8337d3f3..ce26fdfa1afc74ba93d19157042f6c55778011e1 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;
|
|
@@ -500,6 +501,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;
|
|
@@ -907,6 +923,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;
|
|
});
|
|
@@ -953,6 +1105,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 52b02cb1f02d1c65b840f38cfc8baee500aa2259..09234062090c210227350cafeed141f8cb73108a 100644
|
|
--- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
+++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java
|
|
@@ -274,4 +274,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 d58d44faa40be2421f4cb54740a3abdbad72875c..2e830847155e7c43ef411d28e81592c21446143b 100644
|
|
--- a/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
+++ b/src/main/java/io/papermc/paper/util/MCUtil.java
|
|
@@ -583,8 +583,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 6b6d31e76f48a0de33ac528f990ce841dbd666f1..4b87f5d899e5ac033d78ccdbca21c9c50c46dcef 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -96,6 +96,25 @@ public class ChunkHolder {
|
|
|
|
public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system
|
|
|
|
+ // Paper start - replace player chunk loader
|
|
+ private final com.destroystokyo.paper.util.maplist.ReferenceList<ServerPlayer> playersSentChunkTo = new com.destroystokyo.paper.util.maplist.ReferenceList<>();
|
|
+
|
|
+ public void addPlayer(ServerPlayer player) {
|
|
+ if (!this.playersSentChunkTo.add(player)) {
|
|
+ throw new IllegalStateException("Already sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(ServerPlayer player) {
|
|
+ if (!this.playersSentChunkTo.remove(player)) {
|
|
+ throw new IllegalStateException("Have not sent chunk " + this.pos + " in world '" + this.chunkMap.level.getWorld().getName() + "' to player " + player);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean hasChunkBeenSent() {
|
|
+ return this.playersSentChunkTo.size() != 0;
|
|
+ }
|
|
+ // Paper end - replace player chunk loader
|
|
public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system
|
|
this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system
|
|
this.chunkToSaveHistory = null;
|
|
@@ -193,6 +212,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) {
|
|
@@ -219,7 +243,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();
|
|
|
|
@@ -316,25 +340,12 @@ public class ChunkHolder {
|
|
|
|
// Paper start - rewrite chunk system
|
|
public List<ServerPlayer> getPlayers(boolean onlyOnWatchDistanceEdge){
|
|
- // Paper start - per player view distance
|
|
List<ServerPlayer> ret = new java.util.ArrayList<>();
|
|
- // 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 ret;
|
|
- }
|
|
-
|
|
- 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)) {
|
|
- continue;
|
|
- }
|
|
-
|
|
ret.add(player);
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 284393fd4ae0a7562b6bc9b60cf2c141a2de3a58..a502d293cedb2f507e6cf1792429b36685ed1910 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -156,17 +156,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
// Paper start - distance maps
|
|
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
|
|
- public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader
|
|
|
|
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
|
|
}
|
|
|
|
void removePlayerFromDistanceMaps(ServerPlayer player) {
|
|
- this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
|
|
+ this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader
|
|
|
|
}
|
|
|
|
@@ -174,7 +173,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
|
|
}
|
|
// Paper end
|
|
// Paper start
|
|
@@ -573,7 +572,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) {
|
|
@@ -583,20 +586,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
int k = this.viewDistance;
|
|
|
|
this.viewDistance = j;
|
|
- this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system
|
|
+ this.level.playerChunkLoader.setLoadDistance(this.viewDistance); // Paper - replace player loader system
|
|
}
|
|
|
|
}
|
|
|
|
public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<ClientboundLevelChunkWithLightPacket> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - public
|
|
+ io.papermc.paper.util.TickThread.ensureTickThread(this.level, pos, "May not update chunk tracking for chunk async"); // Paper - replace chunk loader system
|
|
+ io.papermc.paper.util.TickThread.ensureTickThread(player, "May not update chunk tracking for player async"); // Paper - replace chunk loader system
|
|
if (player.level() == this.level) {
|
|
+ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); // Paper - replace chunk loader system - move up
|
|
if (newWithinViewDistance && !oldWithinViewDistance) {
|
|
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
|
|
+ // Paper - replace chunk loader system - move up
|
|
|
|
if (playerchunk != null) {
|
|
LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system
|
|
|
|
if (chunk != null) {
|
|
+ playerchunk.addPlayer(player); // Paper - replace chunk loader system
|
|
this.playerLoadedChunk(player, packet, chunk);
|
|
}
|
|
|
|
@@ -605,10 +612,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
}
|
|
|
|
if (!newWithinViewDistance && oldWithinViewDistance) {
|
|
+ // Paper start - replace chunk loader system
|
|
+ if (playerchunk != null) {
|
|
+ playerchunk.removePlayer(player);
|
|
+ } else {
|
|
+ LOGGER.warn("ChunkHolder at " + pos + " in world '" + this.level.getWorld().getName() + "' does not exist to untrack chunk for " + player, new Throwable());
|
|
+ }
|
|
+ // Paper end - replace chunk loader system
|
|
player.untrackChunk(pos);
|
|
}
|
|
|
|
- }
|
|
+ } else { LOGGER.warn("Mismatch in world for chunk " + pos + " in world '" + this.level.getWorld().getName() + "' for player " + player, new Throwable()); } // Paper - replace chunk loader system
|
|
}
|
|
|
|
public int size() {
|
|
@@ -842,34 +856,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
|
|
}
|
|
|
|
@@ -1179,7 +1177,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
|
|
if (player != this.entity) {
|
|
Vec3 vec3d = player.position().subtract(this.entity.position());
|
|
- 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.x * vec3d.x + vec3d.z * vec3d.z;
|
|
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 32a07573ee23f01c98e684aefb45ff72802c9db6..20d600d29c2f2e47c798721d1f151e625b12acc3 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -178,17 +178,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 bb7b7f904639ac964f9c2d1bd5a660d8b397f647..caff28e2446177d622c999b84d8889fbf61d0b3d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -661,7 +661,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
|
|
}
|
|
@@ -929,7 +929,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
@Override
|
|
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
public boolean pollTask() {
|
|
- ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick();
|
|
+ // Paper - replace player chunk loader
|
|
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
|
|
return true;
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index d7172df2489f2eb325120d950dcff32cc483db56..62a95a0fac59683948f34b202e6e3859b6652d6d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -423,6 +423,48 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
}
|
|
// Paper end - rewrite chunk system
|
|
|
|
+ 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, @Nullable RandomSequences randomsequences, 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 5872ead2fe3a64f02f8bc36603fbb856728fd255..32ef9f1ae0c35e927133572ebb6fbf50b0729a63 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -260,6 +260,48 @@ public class ServerPlayer extends Player {
|
|
public boolean isRealPlayer; // Paper
|
|
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
|
|
|
|
+ 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 d4f44635b3cb7ac890ea89b6f4454fa9d4375c08..0338a6b245ee482d470f5a80da712679ab9890fa 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -262,7 +262,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(), player.getPortalCooldown())); // 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(), player.getPortalCooldown())); // 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())));
|
|
@@ -800,8 +800,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.getPortalCooldown()));
|
|
- 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(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), 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 0276c32ef8323bcf82eb3400bb003a93b8a56de0..5988c0847af4e8f0094328e91f736f25d567db60 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -455,7 +455,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 41754f6c8162df07e2b22f4ffcacd8b6158a864e..3da04db71d6f33b2f466c11e031e0a11c298379b 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -172,43 +172,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() {
|
|
@@ -781,7 +744,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
|
|
@@ -956,6 +918,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
|
|
}
|
|
}
|
|
|
|
@@ -975,7 +938,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 49623627555cb2b18ea8f7e17d0f6c1db54c0be4..7c4d43096031a3c93d5f835922b19d5643005128 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -1947,12 +1947,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
|
|
@@ -1986,12 +1986,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 2f35f954eb2dcdc2de54b54c47c139908438e0f0..976eadd8200b2f4811d57b3c7fbd68cff1333924 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -187,44 +187,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
|
|
@@ -239,23 +217,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
|
|
|