2243 lines
117 KiB
Diff
2243 lines
117 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Spottedleaf <spottedleaf@spottedleaf.dev>
|
|
Date: Sun, 24 Jan 2021 20:27:32 -0800
|
|
Subject: [PATCH] Replace player chunk loader system
|
|
|
|
The old one has undebuggable problems. Rewriting seems
|
|
the most sensible option.
|
|
|
|
This new player chunk manager will also strictly rate limit
|
|
chunk sends so that netty threads do not get overloaded, whether
|
|
it be from the anti-xray logic or the compression itself.
|
|
|
|
Chunk loading is also rate limited in the same manner, so this
|
|
will result in a maximum responsiveness for change.
|
|
|
|
Config:
|
|
```
|
|
chunk-loading:
|
|
min-load-radius: 2
|
|
max-concurrent-sends: 2
|
|
autoconfig-send-distance: true
|
|
target-player-chunk-send-rate: 100.0
|
|
global-max-chunk-send-rate: -1
|
|
enable-frustum-priority: false
|
|
global-max-chunk-load-rate: -1.0
|
|
player-max-concurrent-loads: 25.0
|
|
global-max-concurrent-loads: 500.0
|
|
```
|
|
|
|
min-load-radius - The radius of chunks around a player that
|
|
are not throttled for loading. The number of chunks
|
|
affected is actually the configured value plus one as this
|
|
config controls the chunks the client will be able to render.
|
|
|
|
max-concurrent-sends - The maximum number of chunks that
|
|
can be queued to send at any given time. Low values
|
|
are generally going to solve server-sided networking
|
|
bottlenecks like anti-xray and chunk compression. Client
|
|
side networking is unlikely to be helped (i.e this wont help
|
|
people running off McDonald's wifi).
|
|
|
|
autoconfig-send-distance - Whether to try to use the client's
|
|
view distance for the send view distance in the server. In the
|
|
case that no plugin has explicitly set the send distance and
|
|
the client view distance is less-than the server's send distance,
|
|
the client's view distance will be used. This will not affect
|
|
tick view distance or no-tick view distance.
|
|
|
|
target-player-chunk-send-rate - The maximum chunk send rate
|
|
an individual player will have. -1 means no limit
|
|
|
|
global-max-chunk-send-rate - The maximum chunk send rate for
|
|
the whole server. -1 means no limit
|
|
|
|
enable-frustum-priority - Whether chunks in front of a player
|
|
are prioritised to load/send first. Disabled by default
|
|
because the client can bug out due to the out of order
|
|
chunk sending.
|
|
|
|
global-max-chunk-load-rate - The maximum chunk load rate
|
|
for the whole server. -1 means no limit
|
|
|
|
player-max-concurrent-loads and global-max-concurrent-loads
|
|
The maximum number of concurrent loads for the server is
|
|
determined by the number of players on the server multiplied by the
|
|
`player-max-concurrent-loads`. It is then limited to
|
|
whatever `global-max-concurrent-loads` is configured to.
|
|
|
|
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
index 78280fb3bcd8d792a58ece6d735e0824ea4be536..06bff37e4c1fddd3be6343049a66787c63fb420c 100644
|
|
--- a/src/main/java/co/aikar/timings/TimingsExport.java
|
|
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
|
|
@@ -162,7 +162,11 @@ public class TimingsExport extends Thread {
|
|
pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
|
|
return pair(rule, world.getWorld().getGameRuleValue(rule));
|
|
})),
|
|
- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
|
|
+ // 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())
|
|
+ // 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
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..b53402903eb6845df361daf6b05a668608ad7b63
|
|
--- /dev/null
|
|
+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
|
|
@@ -0,0 +1,1128 @@
|
|
+package io.papermc.paper.chunk;
|
|
+
|
|
+import com.destroystokyo.paper.util.misc.PlayerAreaMap;
|
|
+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
|
|
+import io.papermc.paper.configuration.GlobalConfiguration;
|
|
+import io.papermc.paper.util.CoordinateUtils;
|
|
+import io.papermc.paper.util.IntervalledCounter;
|
|
+import io.papermc.paper.util.TickThread;
|
|
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap;
|
|
+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
|
+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.MCUtil;
|
|
+import net.minecraft.server.MinecraftServer;
|
|
+import net.minecraft.server.level.*;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.chunk.LevelChunk;
|
|
+import org.apache.commons.lang3.mutable.MutableObject;
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
+import org.bukkit.entity.Player;
|
|
+import java.util.ArrayDeque;
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+import java.util.TreeSet;
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
+
|
|
+public final class PlayerChunkLoader {
|
|
+
|
|
+ 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 LOADED_TICKET_LEVEL = 33;
|
|
+
|
|
+ public static int getTickViewDistance(final Player player) {
|
|
+ return getTickViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ 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();
|
|
+ }
|
|
+
|
|
+ public static int getLoadViewDistance(final Player player) {
|
|
+ return getLoadViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ 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();
|
|
+ }
|
|
+
|
|
+ public static int getSendViewDistance(final Player player) {
|
|
+ return getSendViewDistance(((CraftPlayer)player).getHandle());
|
|
+ }
|
|
+
|
|
+ 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();
|
|
+ }
|
|
+
|
|
+ protected final ChunkMap chunkMap;
|
|
+ protected final Reference2ObjectLinkedOpenHashMap<ServerPlayer, PlayerLoaderData> playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f);
|
|
+ protected final ReferenceLinkedOpenHashSet<PlayerLoaderData> chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f);
|
|
+
|
|
+ protected final TreeSet<PlayerLoaderData> chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
|
|
+ if (p1 == p2) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst();
|
|
+ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst();
|
|
+
|
|
+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority);
|
|
+
|
|
+ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad);
|
|
+
|
|
+ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) {
|
|
+ return priorityCompare;
|
|
+ }
|
|
+
|
|
+ if (lastLoadTimeCompare != 0) {
|
|
+ return lastLoadTimeCompare;
|
|
+ }
|
|
+
|
|
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
|
|
+
|
|
+ if (idCompare != 0) {
|
|
+ return idCompare;
|
|
+ }
|
|
+
|
|
+ // last resort
|
|
+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2));
|
|
+ });
|
|
+
|
|
+ protected final TreeSet<PlayerLoaderData> chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
|
|
+ if (p1 == p2) {
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget);
|
|
+ if (timeCompare != 0) {
|
|
+ return timeCompare;
|
|
+ }
|
|
+
|
|
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
|
|
+
|
|
+ if (idCompare != 0) {
|
|
+ return idCompare;
|
|
+ }
|
|
+
|
|
+ // last resort
|
|
+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2));
|
|
+ });
|
|
+
|
|
+
|
|
+ // no throttling is applied below this VD for loading
|
|
+
|
|
+ /**
|
|
+ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded.
|
|
+ */
|
|
+ public final PlayerAreaMap broadcastMap;
|
|
+
|
|
+ /**
|
|
+ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded.
|
|
+ */
|
|
+ public final PlayerAreaMap loadMap;
|
|
+
|
|
+ /**
|
|
+ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus,
|
|
+ * this map is always representing the chunks we are actually going to load.
|
|
+ */
|
|
+ public final PlayerAreaMap loadTicketCleanup;
|
|
+
|
|
+ /**
|
|
+ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen.
|
|
+ */
|
|
+ public final PlayerAreaMap tickMap;
|
|
+
|
|
+ /**
|
|
+ * -1 if defaulting to [load distance], else always in [2, load distance]
|
|
+ */
|
|
+ protected int rawSendDistance = -1;
|
|
+
|
|
+ /**
|
|
+ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1]
|
|
+ */
|
|
+ protected int rawLoadDistance = -1;
|
|
+
|
|
+ /**
|
|
+ * Never -1, always in [2, 32]
|
|
+ */
|
|
+ protected int rawTickDistance = -1;
|
|
+
|
|
+ // methods to bridge for API
|
|
+
|
|
+ public int getTargetTickViewDistance() {
|
|
+ return this.getTickDistance();
|
|
+ }
|
|
+
|
|
+ public void setTargetTickViewDistance(final int distance) {
|
|
+ this.setTickDistance(distance);
|
|
+ }
|
|
+
|
|
+ public int getTargetNoTickViewDistance() {
|
|
+ return this.getLoadDistance() - 1;
|
|
+ }
|
|
+
|
|
+ public void setTargetNoTickViewDistance(final int distance) {
|
|
+ this.setLoadDistance(distance == -1 ? -1 : distance + 1);
|
|
+ }
|
|
+
|
|
+ public int getTargetSendDistance() {
|
|
+ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance;
|
|
+ }
|
|
+
|
|
+ public void setTargetSendDistance(final int distance) {
|
|
+ this.setSendDistance(distance);
|
|
+ }
|
|
+
|
|
+ // internal methods
|
|
+
|
|
+ public int getSendDistance() {
|
|
+ final int loadDistance = this.getLoadDistance();
|
|
+ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance);
|
|
+ }
|
|
+
|
|
+ public void setSendDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Send distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance);
|
|
+ }
|
|
+ this.rawSendDistance = distance;
|
|
+ }
|
|
+
|
|
+ public int getLoadDistance() {
|
|
+ final int tickDistance = this.getTickDistance();
|
|
+ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance);
|
|
+ }
|
|
+
|
|
+ public void setLoadDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Load distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance);
|
|
+ }
|
|
+ this.rawLoadDistance = distance;
|
|
+ }
|
|
+
|
|
+ public int getTickDistance() {
|
|
+ return this.rawTickDistance;
|
|
+ }
|
|
+
|
|
+ public void setTickDistance(final int distance) {
|
|
+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) {
|
|
+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + ", got: " + distance);
|
|
+ }
|
|
+ this.rawTickDistance = distance;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ Players have 3 different types of view distance:
|
|
+ 1. Sending view distance
|
|
+ 2. Loading view distance
|
|
+ 3. Ticking view distance
|
|
+
|
|
+ But for configuration purposes (and API) there are:
|
|
+ 1. No-tick view distance
|
|
+ 2. Tick view distance
|
|
+ 3. Broadcast view distance
|
|
+
|
|
+ These aren't always the same as the types we represent internally.
|
|
+
|
|
+ Loading view distance is always max(no-tick + 1, tick + 1)
|
|
+ - no-tick has 1 added because clients need an extra radius to render chunks
|
|
+ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking
|
|
+
|
|
+ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means
|
|
+ it loads chunks in radius load-view-distance + 1.
|
|
+
|
|
+ The maximum value for send view distance is the load view distance. API can set it lower.
|
|
+ */
|
|
+
|
|
+ public PlayerChunkLoader(final ChunkMap chunkMap, final PooledLinkedHashSets<ServerPlayer> pooledHashSets) {
|
|
+ this.chunkMap = chunkMap;
|
|
+ this.broadcastMap = new PlayerAreaMap(pooledHashSets,
|
|
+ null,
|
|
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
|
+ PlayerChunkLoader.this.onChunkLeave(player, rangeX, rangeZ);
|
|
+ });
|
|
+ this.loadMap = new PlayerAreaMap(pooledHashSets,
|
|
+ null,
|
|
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
|
+ if (newState != null) {
|
|
+ return;
|
|
+ }
|
|
+ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(CoordinateUtils.getChunkKey(rangeX, rangeZ));
|
|
+ });
|
|
+ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets,
|
|
+ null,
|
|
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
|
+ if (newState != null) {
|
|
+ return;
|
|
+ }
|
|
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
|
|
+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos);
|
|
+ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.toLong())) {
|
|
+ --PlayerChunkLoader.this.concurrentChunkLoads;
|
|
+ }
|
|
+ });
|
|
+ this.tickMap = new PlayerAreaMap(pooledHashSets,
|
|
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
|
+ if (newState.size() != 1) {
|
|
+ return;
|
|
+ }
|
|
+ LevelChunk chunk = PlayerChunkLoader.this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
|
|
+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
|
|
+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
|
|
+ },
|
|
+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
|
|
+ if (newState != null) {
|
|
+ return;
|
|
+ }
|
|
+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
|
|
+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
|
|
+ });
|
|
+ }
|
|
+
|
|
+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet();
|
|
+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet();
|
|
+
|
|
+ public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) {
|
|
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ);
|
|
+
|
|
+ return playersInSendRange != null;
|
|
+ }
|
|
+
|
|
+ public void onChunkPostProcessing(final int chunkX, final int chunkZ) {
|
|
+ this.onChunkSendReady(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ private boolean chunkNeedsPostProcessing(final int chunkX, final int chunkZ) {
|
|
+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key);
|
|
+
|
|
+ if (chunk == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final LevelChunk levelChunk = chunk.getSendingChunk();
|
|
+
|
|
+ return levelChunk != null && !levelChunk.isPostProcessingDone;
|
|
+ }
|
|
+
|
|
+ // rets whether the chunk is at a loaded stage that is ready to be sent to players
|
|
+ public boolean isChunkPlayerLoaded(final int chunkX, final int chunkZ) {
|
|
+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
|
|
+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key);
|
|
+
|
|
+ if (chunk == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final LevelChunk levelChunk = chunk.getSendingChunk();
|
|
+
|
|
+ return levelChunk != null && levelChunk.isPostProcessingDone && this.isTargetedForPlayerLoad.contains(key);
|
|
+ }
|
|
+
|
|
+ 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 PlayerLoaderData data = this.playerMap.get(player);
|
|
+ if (data == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return data.hasSentChunk(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerLoaderData data = this.playerMap.get(player);
|
|
+ if (data == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final boolean center = data.hasSentChunk(chunkX, chunkZ);
|
|
+ if (!center) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return !(data.hasSentChunk(chunkX - 1, chunkZ) && data.hasSentChunk(chunkX + 1, chunkZ) &&
|
|
+ data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1));
|
|
+ }
|
|
+
|
|
+ protected int getMaxConcurrentChunkSends() {
|
|
+ return GlobalConfiguration.get().chunkLoading.maxConcurrentSends;
|
|
+ }
|
|
+
|
|
+ protected int getMaxChunkLoads() {
|
|
+ double config = GlobalConfiguration.get().chunkLoading.playerMaxConcurrentLoads;
|
|
+ double max = GlobalConfiguration.get().chunkLoading.globalMaxConcurrentLoads;
|
|
+ return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max));
|
|
+ }
|
|
+
|
|
+ protected long getTargetSendPerPlayerAddend() {
|
|
+ return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate);
|
|
+ }
|
|
+
|
|
+ protected long getMaxSendAddend() {
|
|
+ return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate);
|
|
+ }
|
|
+
|
|
+ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) {
|
|
+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
|
|
+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
|
|
+ }
|
|
+
|
|
+ public void onChunkSendReady(final int chunkX, final int chunkZ) {
|
|
+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ);
|
|
+
|
|
+ if (playersInSendRange == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final Object[] rawData = playersInSendRange.getBackingSet();
|
|
+ for (int i = 0, len = rawData.length; i < len; ++i) {
|
|
+ final Object raw = rawData[i];
|
|
+
|
|
+ if (!(raw instanceof ServerPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void onChunkSendReady(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerLoaderData data = this.playerMap.get(player);
|
|
+
|
|
+ if (data == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) {
|
|
+ // if we don't have player tickets, then the load logic will pick this up and queue to send
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (!data.chunksToBeSent.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
+ // don't queue to send, we don't want the chunk
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final long playerPos = this.broadcastMap.getLastCoordinate(player);
|
|
+ final int playerChunkX = CoordinateUtils.getChunkX(playerPos);
|
|
+ final int playerChunkZ = CoordinateUtils.getChunkZ(playerPos);
|
|
+ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ);
|
|
+
|
|
+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0);
|
|
+ data.sendQueue.add(holder);
|
|
+ }
|
|
+
|
|
+ public void onChunkLoad(final int chunkX, final int chunkZ) {
|
|
+ if (this.chunkTicketTracker.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
|
|
+ --this.concurrentChunkLoads;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void onChunkLeave(final ServerPlayer player, final int chunkX, final int chunkZ) {
|
|
+ final PlayerLoaderData data = this.playerMap.get(player);
|
|
+
|
|
+ if (data == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ data.unloadChunk(chunkX, chunkZ);
|
|
+ }
|
|
+
|
|
+ public void addPlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread("Cannot add player async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+ final PlayerLoaderData data = new PlayerLoaderData(player, this);
|
|
+ if (this.playerMap.putIfAbsent(player, data) == null) {
|
|
+ data.update();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void removePlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread("Cannot remove player async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final PlayerLoaderData loaderData = this.playerMap.remove(player);
|
|
+ if (loaderData == null) {
|
|
+ return;
|
|
+ }
|
|
+ loaderData.remove();
|
|
+ this.chunkLoadQueue.remove(loaderData);
|
|
+ this.chunkSendQueue.remove(loaderData);
|
|
+ this.chunkSendWaitQueue.remove(loaderData);
|
|
+ synchronized (this.sendingChunkCounts) {
|
|
+ final int count = this.sendingChunkCounts.removeInt(loaderData);
|
|
+ if (count != 0) {
|
|
+ concurrentChunkSends.getAndAdd(-count);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void updatePlayer(final ServerPlayer player) {
|
|
+ TickThread.ensureTickThread("Cannot update player async");
|
|
+ if (!player.isRealPlayer) {
|
|
+ return;
|
|
+ }
|
|
+ final PlayerLoaderData loaderData = this.playerMap.get(player);
|
|
+ if (loaderData != null) {
|
|
+ loaderData.update();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public PlayerLoaderData getData(final ServerPlayer player) {
|
|
+ return this.playerMap.get(player);
|
|
+ }
|
|
+
|
|
+ public void tick() {
|
|
+ TickThread.ensureTickThread("Cannot tick async");
|
|
+ for (final PlayerLoaderData data : this.playerMap.values()) {
|
|
+ data.update();
|
|
+ }
|
|
+ this.tickMidTick();
|
|
+ }
|
|
+
|
|
+ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger();
|
|
+ protected final Reference2IntOpenHashMap<PlayerLoaderData> sendingChunkCounts = new Reference2IntOpenHashMap<>();
|
|
+ private static long nextChunkSend;
|
|
+ private void trySendChunks() {
|
|
+ final long time = System.nanoTime();
|
|
+ if (time < nextChunkSend) {
|
|
+ return;
|
|
+ }
|
|
+ // drain entries from wait queue
|
|
+ while (!this.chunkSendWaitQueue.isEmpty()) {
|
|
+ final PlayerLoaderData data = this.chunkSendWaitQueue.first();
|
|
+
|
|
+ if (data.nextChunkSendTarget > time) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ this.chunkSendWaitQueue.pollFirst();
|
|
+
|
|
+ this.chunkSendQueue.add(data);
|
|
+ }
|
|
+
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int maxSends = this.getMaxConcurrentChunkSends();
|
|
+ final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time;
|
|
+ for (;;) {
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ break;
|
|
+ }
|
|
+ final int currSends = concurrentChunkSends.get();
|
|
+ if (currSends >= maxSends) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // send chunk
|
|
+
|
|
+ final PlayerLoaderData data = this.chunkSendQueue.removeFirst();
|
|
+
|
|
+ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst();
|
|
+ if (queuedSend == null) {
|
|
+ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease
|
|
+ // stop iterating over players who have nothing to send
|
|
+ if (this.chunkSendQueue.isEmpty()) {
|
|
+ // nothing left
|
|
+ break;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+
|
|
+ data.nextChunkSendTarget = nextPlayerDeadline;
|
|
+ this.chunkSendWaitQueue.add(data);
|
|
+
|
|
+ synchronized (this.sendingChunkCounts) {
|
|
+ this.sendingChunkCounts.addTo(data, 1);
|
|
+ }
|
|
+
|
|
+ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> {
|
|
+ synchronized (this.sendingChunkCounts) {
|
|
+ final int count = this.sendingChunkCounts.getInt(data);
|
|
+ if (count == 0) {
|
|
+ // disconnected, so we don't need to decrement: it will be decremented for us
|
|
+ return;
|
|
+ }
|
|
+ if (count == 1) {
|
|
+ this.sendingChunkCounts.removeInt(data);
|
|
+ } else {
|
|
+ this.sendingChunkCounts.put(data, count - 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ concurrentChunkSends.getAndDecrement();
|
|
+ });
|
|
+
|
|
+ nextChunkSend = this.getMaxSendAddend() + time;
|
|
+ if (time < nextChunkSend) {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected int concurrentChunkLoads;
|
|
+ // this interval prevents bursting a lot of chunk loads
|
|
+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
|
|
+ // this interval ensures the rate is kept between ticks correctly
|
|
+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
|
|
+ private void tryLoadChunks() {
|
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int maxLoads = this.getMaxChunkLoads();
|
|
+ final long time = System.nanoTime();
|
|
+ boolean updatedCounters = false;
|
|
+ for (;;) {
|
|
+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
|
|
+
|
|
+ data.lastChunkLoad = time;
|
|
+
|
|
+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
|
|
+ if (queuedLoad == null) {
|
|
+ if (this.chunkLoadQueue.isEmpty()) {
|
|
+ break;
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!updatedCounters) {
|
|
+ updatedCounters = true;
|
|
+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time);
|
|
+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time);
|
|
+ data.ticketAdditionCounterShort.updateCurrentTime(time);
|
|
+ data.ticketAdditionCounterLong.updateCurrentTime(time);
|
|
+ }
|
|
+
|
|
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
|
+ // already loaded!
|
|
+ data.loadQueue.pollFirst(); // already loaded so we just skip
|
|
+ this.chunkLoadQueue.add(data);
|
|
+
|
|
+ // ensure the chunk is queued to send
|
|
+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final long chunkKey = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+
|
|
+ final double priority = queuedLoad.priority;
|
|
+ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present.
|
|
+ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the
|
|
+ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they
|
|
+ // aren't required, it bypasses the limiter system.
|
|
+ boolean unloadedTargetChunk = false;
|
|
+ unloaded_check:
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ final int offX = queuedLoad.chunkX + dx;
|
|
+ final int offZ = queuedLoad.chunkZ + dz;
|
|
+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) {
|
|
+ unloadedTargetChunk = true;
|
|
+ break unloaded_check;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ if (unloadedTargetChunk && priority >= 0.0) {
|
|
+ // priority >= 0.0 implies rate limited chunks
|
|
+
|
|
+ final int currentChunkLoads = this.concurrentChunkLoads;
|
|
+ if (currentChunkLoads >= maxLoads || (GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate))
|
|
+ || (GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate))) {
|
|
+ // don't poll, we didn't load it
|
|
+ this.chunkLoadQueue.add(data);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // can only poll after we decide to load
|
|
+ data.loadQueue.pollFirst();
|
|
+
|
|
+ // now that we've polled we can re-add to load queue
|
|
+ this.chunkLoadQueue.add(data);
|
|
+
|
|
+ // add necessary tickets to load chunk up to send-ready
|
|
+ for (int dz = -1; dz <= 1; ++dz) {
|
|
+ for (int dx = -1; dx <= 1; ++dx) {
|
|
+ final int offX = queuedLoad.chunkX + dx;
|
|
+ final int offZ = queuedLoad.chunkZ + dz;
|
|
+ final ChunkPos chunkPos = new ChunkPos(offX, offZ);
|
|
+
|
|
+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos);
|
|
+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (priority > 0.0 && this.chunkTicketTracker.add(CoordinateUtils.getChunkKey(offX, offZ))) {
|
|
+ // won't reach here if unloadedTargetChunk is false
|
|
+ ++this.concurrentChunkLoads;
|
|
+ TICKET_ADDITION_COUNTER_SHORT.addTime(time);
|
|
+ TICKET_ADDITION_COUNTER_LONG.addTime(time);
|
|
+ data.ticketAdditionCounterShort.addTime(time);
|
|
+ data.ticketAdditionCounterLong.addTime(time);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // mark that we've added tickets here
|
|
+ this.isTargetedForPlayerLoad.add(chunkKey);
|
|
+
|
|
+ // it's possible all we needed was the player tickets to queue up the send.
|
|
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
|
+ // yup, all we needed.
|
|
+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+ } else if (this.chunkNeedsPostProcessing(queuedLoad.chunkX, queuedLoad.chunkZ)) {
|
|
+ // requires post processing
|
|
+ this.chunkMap.mainThreadExecutor.execute(() -> {
|
|
+ final long key = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ);
|
|
+ final ChunkHolder holder = PlayerChunkLoader.this.chunkMap.getVisibleChunkIfPresent(key);
|
|
+
|
|
+ if (holder == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final LevelChunk chunk = holder.getSendingChunk();
|
|
+
|
|
+ if (chunk != null && !chunk.isPostProcessingDone) {
|
|
+ chunk.postProcessGeneration();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public void tickMidTick() {
|
|
+ // try to send more chunks
|
|
+ this.trySendChunks();
|
|
+
|
|
+ // try to queue more chunks to load
|
|
+ this.tryLoadChunks();
|
|
+ }
|
|
+
|
|
+ static final class ChunkPriorityHolder {
|
|
+ public final int chunkX;
|
|
+ public final int chunkZ;
|
|
+ public final int manhattanDistanceToPlayer;
|
|
+ public final double priority;
|
|
+
|
|
+ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) {
|
|
+ this.chunkX = chunkX;
|
|
+ this.chunkZ = chunkZ;
|
|
+ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer;
|
|
+ this.priority = priority;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static final class PlayerLoaderData {
|
|
+
|
|
+ protected static final float FOV = 110.0f;
|
|
+ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0;
|
|
+
|
|
+ // Player max sprint speed is approximately 8m/s
|
|
+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0);
|
|
+ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f;
|
|
+
|
|
+ protected double lastLocX = Double.NEGATIVE_INFINITY;
|
|
+ protected double lastLocZ = Double.NEGATIVE_INFINITY;
|
|
+
|
|
+ protected int lastChunkX = Integer.MIN_VALUE;
|
|
+ protected int lastChunkZ = Integer.MIN_VALUE;
|
|
+
|
|
+ // this is corrected so that 0 is along the positive x-axis
|
|
+ protected float lastYaw = Float.NEGATIVE_INFINITY;
|
|
+
|
|
+ protected int lastSendDistance = Integer.MIN_VALUE;
|
|
+ protected int lastLoadDistance = Integer.MIN_VALUE;
|
|
+ protected int lastTickDistance = Integer.MIN_VALUE;
|
|
+ protected boolean usingLookingPriority;
|
|
+
|
|
+ protected final ServerPlayer player;
|
|
+ protected final PlayerChunkLoader loader;
|
|
+
|
|
+ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field
|
|
+ // in a comparator!
|
|
+ protected final ArrayDeque<ChunkPriorityHolder> loadQueue = new ArrayDeque<>();
|
|
+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet();
|
|
+ protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet();
|
|
+
|
|
+ protected final TreeSet<ChunkPriorityHolder> sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
|
|
+ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer);
|
|
+ if (distanceCompare != 0) {
|
|
+ return distanceCompare;
|
|
+ }
|
|
+
|
|
+ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX);
|
|
+ if (coordinateXCompare != 0) {
|
|
+ return coordinateXCompare;
|
|
+ }
|
|
+
|
|
+ return Integer.compare(p1.chunkZ, p2.chunkZ);
|
|
+ });
|
|
+
|
|
+ protected int sendViewDistance = -1;
|
|
+ protected int loadViewDistance = -1;
|
|
+ protected int tickViewDistance = -1;
|
|
+
|
|
+ protected long nextChunkSendTarget;
|
|
+
|
|
+ // this interval prevents bursting a lot of chunk loads
|
|
+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
|
|
+ // this ensures the rate is kept between ticks correctly
|
|
+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
|
|
+
|
|
+ public long lastChunkLoad;
|
|
+
|
|
+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) {
|
|
+ this.player = player;
|
|
+ this.loader = loader;
|
|
+ }
|
|
+
|
|
+ // these view distance methods are for api
|
|
+ public int getTargetSendViewDistance() {
|
|
+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
|
|
+ final int clientViewDistance = this.getClientViewDistance();
|
|
+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance);
|
|
+ return sendViewDistance;
|
|
+ }
|
|
+
|
|
+ public void setTargetSendViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
|
|
+ throw new IllegalArgumentException("Send view distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.sendViewDistance = distance;
|
|
+ }
|
|
+
|
|
+ public int getTargetNoTickViewDistance() {
|
|
+ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1;
|
|
+ }
|
|
+
|
|
+ public void setTargetNoTickViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) {
|
|
+ throw new IllegalArgumentException("Simulation distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.loadViewDistance = distance == -1 ? -1 : distance + 1;
|
|
+ }
|
|
+
|
|
+ public int getTargetTickViewDistance() {
|
|
+ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+ }
|
|
+
|
|
+ public void setTargetTickViewDistance(final int distance) {
|
|
+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) {
|
|
+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance);
|
|
+ }
|
|
+ this.tickViewDistance = distance;
|
|
+ }
|
|
+
|
|
+ protected int getLoadDistance() {
|
|
+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+
|
|
+ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
|
|
+ }
|
|
+
|
|
+ public boolean hasSentChunk(final int chunkX, final int chunkZ) {
|
|
+ return this.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+ }
|
|
+
|
|
+ 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,
|
|
+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded
|
|
+ this.player.connection.connection.execute(onChunkSend);
|
|
+ } else {
|
|
+ throw new IllegalStateException();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ 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,
|
|
+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded
|
|
+ }
|
|
+ }
|
|
+
|
|
+ protected 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);
|
|
+ }
|
|
+
|
|
+ protected static boolean triangleIntersects(final double p1x, final double p1z, // triangle point
|
|
+ final double p2x, final double p2z, // triangle point
|
|
+ final double p3x, final double p3z, // triangle point
|
|
+
|
|
+ final double targetX, final double targetZ) { // point
|
|
+ // from barycentric coordinates:
|
|
+ // targetX = a*p1x + b*p2x + c*p3x
|
|
+ // targetZ = a*p1z + b*p2z + c*p3z
|
|
+ // 1.0 = a*1.0 + b*1.0 + c*1.0
|
|
+ // where a, b, c >= 0.0
|
|
+ // so, if any of a, b, c are less-than zero then there is no intersection.
|
|
+
|
|
+ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z))
|
|
+ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d
|
|
+ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d
|
|
+ // c = 1.0 - a - b
|
|
+
|
|
+ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z);
|
|
+ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d;
|
|
+
|
|
+ if (a < 0.0 || a > 1.0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d;
|
|
+ if (b < 0.0 || b > 1.0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ final double c = 1.0 - a - b;
|
|
+
|
|
+ return c >= 0.0 && c <= 1.0;
|
|
+ }
|
|
+
|
|
+ public void remove() {
|
|
+ this.loader.broadcastMap.remove(this.player);
|
|
+ this.loader.loadMap.remove(this.player);
|
|
+ this.loader.loadTicketCleanup.remove(this.player);
|
|
+ this.loader.tickMap.remove(this.player);
|
|
+ }
|
|
+
|
|
+ protected int getClientViewDistance() {
|
|
+ return this.player.clientViewDistance == null ? -1 : Math.max(0, this.player.clientViewDistance.intValue());
|
|
+ }
|
|
+
|
|
+ public void update() {
|
|
+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
|
|
+ // load view cannot be less-than tick view + 1
|
|
+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
|
|
+ // send view cannot be greater-than load view
|
|
+ final int clientViewDistance = this.getClientViewDistance();
|
|
+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance);
|
|
+
|
|
+ final double posX = this.player.getX();
|
|
+ final double posZ = this.player.getZ();
|
|
+ final float yaw = MCUtil.normalizeYaw(this.player.yRot + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis
|
|
+
|
|
+ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them.
|
|
+ final boolean useLookPriority = GlobalConfiguration.get().chunkLoading.enableFrustumPriority && (this.player.getDeltaMovement().horizontalDistanceSqr() > LOOK_PRIORITY_SPEED_THRESHOLD ||
|
|
+ this.player.getAbilities().flying);
|
|
+
|
|
+ // make sure we're in the send queue
|
|
+ this.loader.chunkSendWaitQueue.add(this);
|
|
+
|
|
+ if (
|
|
+ // has view distance stayed the same?
|
|
+ sendViewDistance == this.lastSendDistance
|
|
+ && loadViewDistance == this.lastLoadDistance
|
|
+ && tickViewDistance == this.lastTickDistance
|
|
+
|
|
+ && (this.usingLookingPriority ? (
|
|
+ // has our block stayed the same (this also accounts for chunk change)?
|
|
+ Mth.floor(this.lastLocX) == Mth.floor(posX)
|
|
+ && Mth.floor(this.lastLocZ) == Mth.floor(posZ)
|
|
+ ) : (
|
|
+ // has our chunk stayed the same
|
|
+ (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4)
|
|
+ && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4)
|
|
+ ))
|
|
+
|
|
+ // has our decision about look priority changed?
|
|
+ && this.usingLookingPriority == useLookPriority
|
|
+
|
|
+ // if we are currently using look priority, has our yaw stayed within recalc threshold?
|
|
+ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD)
|
|
+ ) {
|
|
+ // nothing we care about changed, so we're not re-calculating
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ final int centerChunkX = Mth.floor(posX) >> 4;
|
|
+ final int centerChunkZ = Mth.floor(posZ) >> 4;
|
|
+
|
|
+ final boolean needsChunkCenterUpdate = (centerChunkX != this.lastChunkX) || (centerChunkZ != this.lastChunkZ);
|
|
+ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance);
|
|
+ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance);
|
|
+ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1);
|
|
+ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance);
|
|
+
|
|
+ if (sendViewDistance != this.lastSendDistance) {
|
|
+ // update the view radius for client
|
|
+ // note that this should be after the map calls because the client wont expect unload calls not in its VD
|
|
+ // and it's possible we decreased VD here
|
|
+ this.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance));
|
|
+ }
|
|
+ if (tickViewDistance != this.lastTickDistance) {
|
|
+ this.player.connection.send(new ClientboundSetSimulationDistancePacket(tickViewDistance));
|
|
+ }
|
|
+
|
|
+ this.lastLocX = posX;
|
|
+ this.lastLocZ = posZ;
|
|
+ this.lastYaw = yaw;
|
|
+ this.lastSendDistance = sendViewDistance;
|
|
+ this.lastLoadDistance = loadViewDistance;
|
|
+ this.lastTickDistance = tickViewDistance;
|
|
+ this.usingLookingPriority = useLookPriority;
|
|
+
|
|
+ this.lastChunkX = centerChunkX;
|
|
+ this.lastChunkZ = centerChunkZ;
|
|
+
|
|
+ // points for player "view" triangle:
|
|
+
|
|
+ // obviously, the player pos is a vertex
|
|
+ final double p1x = posX;
|
|
+ final double p1z = posZ;
|
|
+
|
|
+ // to the left of the looking direction
|
|
+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1x; // offset vector
|
|
+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1z; // offset vector
|
|
+
|
|
+ // to the right of the looking direction
|
|
+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1x; // offset vector
|
|
+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
|
|
+ + p1z; // offset vector
|
|
+
|
|
+ // now that we have all of our points, we can recalculate the load queue
|
|
+
|
|
+ final List<ChunkPriorityHolder> loadQueue = new ArrayList<>();
|
|
+
|
|
+ // clear send queue, we are re-sorting
|
|
+ this.sendQueue.clear();
|
|
+ // clear chunk want set, vd/position might have changed
|
|
+ this.chunksToBeSent.clear();
|
|
+
|
|
+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance);
|
|
+
|
|
+ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) {
|
|
+ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) {
|
|
+ final int chunkX = dx + centerChunkX;
|
|
+ final int chunkZ = dz + centerChunkZ;
|
|
+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz));
|
|
+ final boolean sendChunk = squareDistance <= sendViewDistance && wantChunkLoaded(centerChunkX, centerChunkZ, chunkX, chunkZ, sendViewDistance);
|
|
+
|
|
+ if (this.hasSentChunk(chunkX, chunkZ)) {
|
|
+ // already sent (which means it is also loaded)
|
|
+ if (!sendChunk) {
|
|
+ // have sent the chunk, but don't want it anymore
|
|
+ // unload it now
|
|
+ this.unloadChunk(chunkX, chunkZ);
|
|
+ }
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final boolean loadChunk = squareDistance <= loadViewDistance;
|
|
+
|
|
+ final boolean prioritised = useLookPriority && triangleIntersects(
|
|
+ // prioritisation triangle
|
|
+ p1x, p1z, p2x, p2z, p3x, p3z,
|
|
+
|
|
+ // center of chunk
|
|
+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8)
|
|
+ );
|
|
+
|
|
+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz);
|
|
+
|
|
+ final double priority;
|
|
+
|
|
+ if (squareDistance <= GlobalConfiguration.get().chunkLoading.minLoadRadius) {
|
|
+ // priority should be negative, and we also want to order it from center outwards
|
|
+ // so we want (0,0) to be the smallest, and (minLoadedRadius,minLoadedRadius) to be the greatest
|
|
+ priority = -((2 * GlobalConfiguration.get().chunkLoading.minLoadRadius + 1) - manhattanDistance);
|
|
+ } else {
|
|
+ if (prioritised) {
|
|
+ // we don't prioritise these chunks above others because we also want to make sure some chunks
|
|
+ // will be loaded if the player changes direction
|
|
+ priority = (double)manhattanDistance / 6.0;
|
|
+ } else {
|
|
+ priority = (double)manhattanDistance;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority);
|
|
+
|
|
+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) {
|
|
+ if (loadChunk) {
|
|
+ loadQueue.add(holder);
|
|
+ if (sendChunk) {
|
|
+ this.chunksToBeSent.add(CoordinateUtils.getChunkKey(chunkX, chunkZ));
|
|
+ }
|
|
+ }
|
|
+ } else {
|
|
+ // loaded but not sent: so queue it!
|
|
+ if (sendChunk) {
|
|
+ this.sendQueue.add(holder);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
|
|
+ return Double.compare(p1.priority, p2.priority);
|
|
+ });
|
|
+
|
|
+ // we're modifying loadQueue, must remove
|
|
+ this.loader.chunkLoadQueue.remove(this);
|
|
+
|
|
+ this.loadQueue.clear();
|
|
+ this.loadQueue.addAll(loadQueue);
|
|
+
|
|
+ // must re-add
|
|
+ this.loader.chunkLoadQueue.add(this);
|
|
+
|
|
+ // update the chunk center
|
|
+ // this must be done last so that the client does not ignore any of our unload chunk packets
|
|
+ if (needsChunkCenterUpdate) {
|
|
+ this.player.connection.send(new ClientboundSetChunkCacheCenterPacket(centerChunkX, centerChunkZ));
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
|
|
index c561a2f28fa1e9a327982c4587c4552cd85a7336..868d4913f63b3f4752f7f1cdcd81e118e482c474 100644
|
|
--- a/src/main/java/net/minecraft/network/Connection.java
|
|
+++ b/src/main/java/net/minecraft/network/Connection.java
|
|
@@ -100,6 +100,28 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
|
public boolean queueImmunity = false;
|
|
public ConnectionProtocol protocol;
|
|
// Paper end
|
|
+ // Paper start - add pending task queue
|
|
+ private final Queue<Runnable> pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
|
+ public void execute(final Runnable run) {
|
|
+ if (this.channel == null || !this.channel.isRegistered()) {
|
|
+ run.run();
|
|
+ return;
|
|
+ }
|
|
+ final boolean queue = !this.queue.isEmpty();
|
|
+ if (!queue) {
|
|
+ this.channel.eventLoop().execute(run);
|
|
+ } else {
|
|
+ this.pendingTasks.add(run);
|
|
+ if (this.queue.isEmpty()) {
|
|
+ // something flushed async, dump tasks now
|
|
+ Runnable r;
|
|
+ while ((r = this.pendingTasks.poll()) != null) {
|
|
+ this.channel.eventLoop().execute(r);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end - add pending task queue
|
|
|
|
// Paper start - allow controlled flushing
|
|
volatile boolean canFlush = true;
|
|
@@ -488,6 +510,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
|
return false;
|
|
}
|
|
private boolean processQueue() {
|
|
+ try { // Paper - add pending task queue
|
|
if (this.queue.isEmpty()) return true;
|
|
// Paper start - make only one flush call per sendPacketQueue() call
|
|
final boolean needsFlush = this.canFlush;
|
|
@@ -519,6 +542,12 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
|
}
|
|
}
|
|
return true;
|
|
+ } finally { // Paper start - add pending task queue
|
|
+ Runnable r;
|
|
+ while ((r = this.pendingTasks.poll()) != null) {
|
|
+ this.channel.eventLoop().execute(r);
|
|
+ }
|
|
+ } // Paper end - add pending task queue
|
|
}
|
|
// Paper end
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
|
index 1eb71004a19866590a3d27fa6e72842934989177..7034af8ad42940c5af6b9032b9873ce36c55a2a7 100644
|
|
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
|
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
|
@@ -647,7 +647,8 @@ public final class MCUtil {
|
|
});
|
|
|
|
worldData.addProperty("name", world.getWorld().getName());
|
|
- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
|
|
+ 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("keep-spawn-loaded", world.keepSpawnInMemory);
|
|
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
|
|
worldData.addProperty("visible-chunk-count", visibleChunks.size());
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
index 5482be03a667939ff009b6810d5cc90c8601e983..11cd31691307749e925930c4b6e10e3f3b4fad25 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -76,6 +76,17 @@ public class ChunkHolder {
|
|
public ServerLevel getWorld() { return chunkMap.level; } // Paper
|
|
boolean isUpdateQueued = false; // Paper
|
|
private final ChunkMap chunkMap; // Paper
|
|
+ // Paper start - no-tick view distance
|
|
+ public final LevelChunk getSendingChunk() {
|
|
+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
|
|
+ // in Chunk's neighbour callback
|
|
+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
|
|
+ if (ret != null && ret.areNeighboursLoaded(1)) {
|
|
+ return ret;
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
+ // Paper end - no-tick view distance
|
|
|
|
// Paper start - optimise anyPlayerCloseEnoughForSpawning
|
|
// cached here to avoid a map lookup
|
|
@@ -268,7 +279,7 @@ public class ChunkHolder {
|
|
|
|
public void blockChanged(BlockPos pos) {
|
|
if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks
|
|
- LevelChunk chunk = this.getTickingChunk();
|
|
+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
|
|
|
|
if (chunk != null) {
|
|
int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
|
|
@@ -284,14 +295,15 @@ public class ChunkHolder {
|
|
}
|
|
|
|
public void sectionLightChanged(LightLayer lightType, int y) {
|
|
- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either) this.getFutureIfPresent(ChunkStatus.FEATURES).getNow(null); // CraftBukkit - decompile error
|
|
+ // Paper start - no-tick view distance
|
|
|
|
- if (either != null) {
|
|
- ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error
|
|
+ if (true) {
|
|
+ ChunkAccess ichunkaccess = this.getAvailableChunkNow();
|
|
|
|
if (ichunkaccess != null) {
|
|
ichunkaccess.setUnsaved(true);
|
|
- LevelChunk chunk = this.getTickingChunk();
|
|
+ LevelChunk chunk = this.getSendingChunk();
|
|
+ // Paper end - no-tick view distance
|
|
|
|
if (chunk != null) {
|
|
int j = this.lightEngine.getMinLightSection();
|
|
@@ -394,9 +406,28 @@ public class ChunkHolder {
|
|
}
|
|
|
|
public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
|
|
- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
|
|
- entityplayer.connection.send(packet);
|
|
- });
|
|
+ // Paper start - per player view distance
|
|
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
|
|
+ // view distance map here.
|
|
+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
|
|
+ if (players == null) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ Object[] backingSet = players.getBackingSet();
|
|
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
|
|
+ Object temp = backingSet[i];
|
|
+ if (!(temp instanceof ServerPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ ServerPlayer player = (ServerPlayer)temp;
|
|
+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
|
|
+ continue;
|
|
+ }
|
|
+ player.connection.send(packet);
|
|
+ }
|
|
+ // Paper end - per player view distance
|
|
}
|
|
|
|
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 39915f1c71fae142725ae2977a4eb0c483b4967a..77c89376495d90d0e7cbf6cd02c9a1c8d9a4340b 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -218,6 +218,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick
|
|
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap;
|
|
// Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
|
|
+ public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader
|
|
// Paper start - use distance map to optimise tracker
|
|
public static boolean isLegacyTrackingEntity(Entity entity) {
|
|
return entity.isLegacyTrackingEntity;
|
|
@@ -237,6 +238,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper end - use distance map to optimise tracker
|
|
|
|
void addPlayerToDistanceMaps(ServerPlayer player) {
|
|
+ this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
|
|
int chunkX = MCUtil.getChunkCoordinate(player.getX());
|
|
int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
|
|
// Paper start - use distance map to optimise entity tracker
|
|
@@ -244,7 +246,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
|
|
int trackRange = this.entityTrackerTrackRanges[i];
|
|
|
|
- trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
|
|
+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances
|
|
}
|
|
// Paper end - use distance map to optimise entity tracker
|
|
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
@@ -274,6 +276,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.playerMobDistanceMap.remove(player);
|
|
}
|
|
// Paper end - per player mob spawning
|
|
+ this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
|
|
}
|
|
|
|
void updateMaps(ServerPlayer player) {
|
|
@@ -285,7 +288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
|
|
int trackRange = this.entityTrackerTrackRanges[i];
|
|
|
|
- trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
|
|
+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances
|
|
}
|
|
// Paper end - use distance map to optimise entity tracker
|
|
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
|
|
@@ -295,6 +298,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
|
|
}
|
|
// Paper end - per player mob spawning
|
|
+ this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
|
|
}
|
|
// Paper end
|
|
// Paper start
|
|
@@ -1449,11 +1453,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
completablefuture1.thenAcceptAsync((either) -> {
|
|
either.ifLeft((chunk) -> {
|
|
this.tickingGenerated.getAndIncrement();
|
|
- MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
|
|
-
|
|
- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
|
|
- this.playerLoadedChunk(entityplayer, mutableobject, chunk);
|
|
- });
|
|
+ // Paper - no-tick view distance - moved to Chunk neighbour update
|
|
});
|
|
}, (runnable) -> {
|
|
this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
|
|
@@ -1622,33 +1622,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
int k = this.viewDistance;
|
|
|
|
this.viewDistance = j;
|
|
- this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
|
|
- Iterator objectiterator = this.updatingChunks.getVisibleValuesCopy().iterator(); // Paper
|
|
-
|
|
- while (objectiterator.hasNext()) {
|
|
- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
|
|
- ChunkPos chunkcoordintpair = playerchunk.getPos();
|
|
- MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
|
|
-
|
|
- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
|
|
- SectionPos sectionposition = entityplayer.getLastSectionPos();
|
|
- boolean flag = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), k);
|
|
- boolean flag1 = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), this.viewDistance);
|
|
-
|
|
- this.updateChunkTracking(entityplayer, chunkcoordintpair, mutableobject, flag, flag1);
|
|
- });
|
|
- }
|
|
+ this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system
|
|
}
|
|
|
|
}
|
|
|
|
- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass
|
|
+ // Paper start - replace player loader system
|
|
+ public void setTickViewDistance(int distance) {
|
|
+ this.playerChunkManager.setTickDistance(distance);
|
|
+ }
|
|
+ // Paper end - replace player loader system
|
|
+
|
|
+ public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass // Paper - public
|
|
if (player.level == this.level) {
|
|
if (newWithinViewDistance && !oldWithinViewDistance) {
|
|
ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
|
|
|
|
if (playerchunk != null) {
|
|
- LevelChunk chunk = playerchunk.getTickingChunk();
|
|
+ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system
|
|
|
|
if (chunk != null) {
|
|
this.playerLoadedChunk(player, packet, chunk);
|
|
@@ -1679,7 +1670,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
|
|
void dumpChunks(Writer writer) throws IOException {
|
|
CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
|
|
- TickingTracker tickingtracker = this.distanceManager.tickingTracker();
|
|
+ // Paper - replace loader system
|
|
ObjectBidirectionalIterator objectbidirectionaliterator = this.updatingChunks.getVisibleMap().clone().long2ObjectEntrySet().fastIterator(); // Paper
|
|
|
|
while (objectbidirectionaliterator.hasNext()) {
|
|
@@ -1695,7 +1686,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// CraftBukkit - decompile error
|
|
csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
|
|
return chunk.getBlockEntities().size();
|
|
- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
|
|
+ }).orElse(0), "Use ticket level", -1000, optional1.map((chunk) -> { // Paper - replace loader system
|
|
return chunk.getBlockTicks().count();
|
|
}).orElse(0), optional1.map((chunk) -> {
|
|
return chunk.getFluidTicks().count();
|
|
@@ -1929,15 +1920,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
this.removePlayerFromDistanceMaps(player); // Paper - distance maps
|
|
}
|
|
|
|
- for (int k = i - this.viewDistance - 1; k <= i + this.viewDistance + 1; ++k) {
|
|
- for (int l = j - this.viewDistance - 1; l <= j + this.viewDistance + 1; ++l) {
|
|
- if (ChunkMap.isChunkInRange(k, l, i, j, this.viewDistance)) {
|
|
- ChunkPos chunkcoordintpair = new ChunkPos(k, l);
|
|
-
|
|
- this.updateChunkTracking(player, chunkcoordintpair, new MutableObject(), !added, added);
|
|
- }
|
|
- }
|
|
- }
|
|
+ // Paper - handled by player chunk loader
|
|
|
|
}
|
|
|
|
@@ -1945,7 +1928,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
SectionPos sectionposition = SectionPos.of((EntityAccess) player);
|
|
|
|
player.setLastSectionPos(sectionposition);
|
|
- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z()));
|
|
+ //player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - handled by player chunk loader
|
|
return sectionposition;
|
|
}
|
|
|
|
@@ -1990,65 +1973,40 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
int k1;
|
|
int l1;
|
|
|
|
- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
|
|
- k1 = Math.min(i, i1) - this.viewDistance - 1;
|
|
- l1 = Math.min(j, j1) - this.viewDistance - 1;
|
|
- int i2 = Math.max(i, i1) + this.viewDistance + 1;
|
|
- int j2 = Math.max(j, j1) + this.viewDistance + 1;
|
|
-
|
|
- for (int k2 = k1; k2 <= i2; ++k2) {
|
|
- for (int l2 = l1; l2 <= j2; ++l2) {
|
|
- boolean flag3 = ChunkMap.isChunkInRange(k2, l2, i1, j1, this.viewDistance);
|
|
- boolean flag4 = ChunkMap.isChunkInRange(k2, l2, i, j, this.viewDistance);
|
|
-
|
|
- this.updateChunkTracking(player, new ChunkPos(k2, l2), new MutableObject(), flag3, flag4);
|
|
- }
|
|
- }
|
|
- } else {
|
|
- boolean flag5;
|
|
- boolean flag6;
|
|
-
|
|
- for (k1 = i1 - this.viewDistance - 1; k1 <= i1 + this.viewDistance + 1; ++k1) {
|
|
- for (l1 = j1 - this.viewDistance - 1; l1 <= j1 + this.viewDistance + 1; ++l1) {
|
|
- if (ChunkMap.isChunkInRange(k1, l1, i1, j1, this.viewDistance)) {
|
|
- flag5 = true;
|
|
- flag6 = false;
|
|
- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), true, false);
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- for (k1 = i - this.viewDistance - 1; k1 <= i + this.viewDistance + 1; ++k1) {
|
|
- for (l1 = j - this.viewDistance - 1; l1 <= j + this.viewDistance + 1; ++l1) {
|
|
- if (ChunkMap.isChunkInRange(k1, l1, i, j, this.viewDistance)) {
|
|
- flag5 = false;
|
|
- flag6 = true;
|
|
- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), false, true);
|
|
- }
|
|
- }
|
|
- }
|
|
- }
|
|
+ // 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) {
|
|
- Set<ServerPlayer> set = this.playerMap.getPlayers(chunkPos.toLong());
|
|
- Builder<ServerPlayer> builder = ImmutableList.builder();
|
|
- Iterator iterator = set.iterator();
|
|
+ // 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.
|
|
+ List<ServerPlayer> ret = new java.util.ArrayList<>(4);
|
|
|
|
- while (iterator.hasNext()) {
|
|
- ServerPlayer entityplayer = (ServerPlayer) iterator.next();
|
|
- SectionPos sectionposition = entityplayer.getLastSectionPos();
|
|
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos);
|
|
+ if (players == null) {
|
|
+ return ret;
|
|
+ }
|
|
|
|
- if (onlyOnWatchDistanceEdge && ChunkMap.isChunkOnRangeBorder(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance) || !onlyOnWatchDistanceEdge && ChunkMap.isChunkInRange(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance)) {
|
|
- builder.add(entityplayer);
|
|
+ Object[] backingSet = players.getBackingSet();
|
|
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
|
|
+ Object temp = backingSet[i];
|
|
+ if (!(temp instanceof ServerPlayer)) {
|
|
+ continue;
|
|
+ }
|
|
+ ServerPlayer player = (ServerPlayer)temp;
|
|
+ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) {
|
|
+ continue;
|
|
}
|
|
+ ret.add(player);
|
|
}
|
|
|
|
- return builder.build();
|
|
+ return ret;
|
|
+ // Paper end - per player view distance
|
|
}
|
|
|
|
public void addEntity(Entity entity) {
|
|
@@ -2417,7 +2375,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
double vec3d_dx = player.getX() - this.entity.getX();
|
|
double vec3d_dz = player.getZ() - this.entity.getZ();
|
|
// Paper end - remove allocation of Vec3D here
|
|
- double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16);
|
|
+ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance
|
|
double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
|
|
double d2 = d0 * d0;
|
|
boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player);
|
|
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
index f581a9f79b2357118d912a15344ff94df3b0c50e..d1b5c25b7455174e908cd6ed66789fa700190604 100644
|
|
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
|
@@ -51,8 +51,8 @@ public abstract class DistanceManager {
|
|
public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
|
|
//private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator
|
|
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
|
|
- private final TickingTracker tickingTicketsTracker = new TickingTracker();
|
|
- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
|
|
+ //private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used
|
|
+ //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
|
|
// Paper start use a queue, but still keep unique requirement
|
|
public final java.util.Queue<ChunkHolder> pendingChunkUpdates = new java.util.ArrayDeque<ChunkHolder>() {
|
|
@Override
|
|
@@ -133,7 +133,7 @@ public abstract class DistanceManager {
|
|
java.util.function.Predicate<Ticket<?>> removeIf = (ticket) -> {
|
|
final boolean ret = ticket.timedOut(ticketCounter);
|
|
if (ret) {
|
|
- this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
|
|
+ //this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); // Paper - no longer used
|
|
}
|
|
return ret;
|
|
};
|
|
@@ -153,7 +153,7 @@ public abstract class DistanceManager {
|
|
if (ticket.timedOut(this.ticketTickCounter)) {
|
|
iterator.remove();
|
|
flag = true;
|
|
- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
|
|
+ //this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used
|
|
}
|
|
}
|
|
|
|
@@ -184,9 +184,9 @@ public abstract class DistanceManager {
|
|
protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
|
|
public boolean runAllUpdates(ChunkMap chunkStorage) {
|
|
//this.f.a(); // Paper - no longer used
|
|
- this.tickingTicketsTracker.runAllUpdates();
|
|
+ //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used
|
|
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
|
|
- this.playerTicketManager.runAllUpdates();
|
|
+ // this.playerTicketManager.runAllUpdates(); // Paper - no longer used
|
|
boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator
|
|
|
|
if (flag) {
|
|
@@ -351,7 +351,7 @@ public abstract class DistanceManager {
|
|
long j = chunkcoordintpair.toLong();
|
|
|
|
boolean added = this.addTicket(j, ticket); // CraftBukkit
|
|
- this.tickingTicketsTracker.addTicket(j, ticket);
|
|
+ //this.tickingTicketsTracker.addTicket(j, ticket); // Paper - no longer used
|
|
return added; // CraftBukkit
|
|
}
|
|
|
|
@@ -366,7 +366,7 @@ public abstract class DistanceManager {
|
|
long j = chunkcoordintpair.toLong();
|
|
|
|
boolean removed = this.removeTicket(j, ticket); // CraftBukkit
|
|
- this.tickingTicketsTracker.removeTicket(j, ticket);
|
|
+ //this.tickingTicketsTracker.removeTicket(j, ticket); // Paper - no longer used
|
|
return removed; // CraftBukkit
|
|
}
|
|
|
|
@@ -488,10 +488,10 @@ public abstract class DistanceManager {
|
|
|
|
if (forced) {
|
|
this.addTicket(i, ticket);
|
|
- this.tickingTicketsTracker.addTicket(i, ticket);
|
|
+ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used
|
|
} else {
|
|
this.removeTicket(i, ticket);
|
|
- this.tickingTicketsTracker.removeTicket(i, ticket);
|
|
+ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used
|
|
}
|
|
|
|
}
|
|
@@ -504,8 +504,8 @@ public abstract class DistanceManager {
|
|
return new ObjectOpenHashSet();
|
|
})).add(player);
|
|
//this.f.update(i, 0, true); // Paper - no longer used
|
|
- this.playerTicketManager.update(i, 0, true);
|
|
- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
|
|
+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used
|
|
+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
|
|
}
|
|
|
|
public void removePlayer(SectionPos pos, ServerPlayer player) {
|
|
@@ -518,8 +518,8 @@ public abstract class DistanceManager {
|
|
if (objectset == null || objectset.isEmpty()) { // Paper
|
|
this.playersPerChunk.remove(i);
|
|
//this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
|
|
- this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
|
|
- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
|
|
+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
|
|
+ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
|
|
}
|
|
|
|
}
|
|
@@ -529,11 +529,17 @@ public abstract class DistanceManager {
|
|
}
|
|
|
|
public boolean inEntityTickingRange(long chunkPos) {
|
|
- return this.tickingTicketsTracker.getLevel(chunkPos) < 32;
|
|
+ // Paper start - replace player chunk loader system
|
|
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos);
|
|
+ return holder != null && holder.isEntityTickingReady();
|
|
+ // Paper end - replace player chunk loader system
|
|
}
|
|
|
|
public boolean inBlockTickingRange(long chunkPos) {
|
|
- return this.tickingTicketsTracker.getLevel(chunkPos) < 33;
|
|
+ // Paper start - replace player chunk loader system
|
|
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos);
|
|
+ return holder != null && holder.isTickingReady();
|
|
+ // Paper end - replace player chunk loader system
|
|
}
|
|
|
|
protected String getTicketDebugString(long pos) {
|
|
@@ -543,20 +549,16 @@ public abstract class DistanceManager {
|
|
}
|
|
|
|
protected void updatePlayerTickets(int viewDistance) {
|
|
- this.playerTicketManager.updateViewDistance(viewDistance);
|
|
+ this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager
|
|
}
|
|
|
|
public void updateSimulationDistance(int simulationDistance) {
|
|
- if (simulationDistance != this.simulationDistance) {
|
|
- this.simulationDistance = simulationDistance;
|
|
- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel());
|
|
- }
|
|
-
|
|
+ this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager
|
|
}
|
|
|
|
// Paper start
|
|
public int getSimulationDistance() {
|
|
- return this.simulationDistance;
|
|
+ return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager
|
|
}
|
|
// Paper end
|
|
|
|
@@ -613,10 +615,7 @@ public abstract class DistanceManager {
|
|
|
|
}
|
|
|
|
- @VisibleForTesting
|
|
- TickingTracker tickingTracker() {
|
|
- return this.tickingTicketsTracker;
|
|
- }
|
|
+ // Paper - replace player chunk loader
|
|
|
|
public void removeTicketsOnClosing() {
|
|
ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve
|
|
@@ -633,7 +632,7 @@ public abstract class DistanceManager {
|
|
if (!immutableset.contains(ticket.getType())) {
|
|
iterator.remove();
|
|
flag = true;
|
|
- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
|
|
+ // this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used
|
|
}
|
|
}
|
|
|
|
@@ -672,6 +671,7 @@ public abstract class DistanceManager {
|
|
}
|
|
// CraftBukkit end
|
|
|
|
+ /* Paper - replace old loader system
|
|
private class ChunkTicketTracker extends ChunkTracker {
|
|
|
|
public ChunkTicketTracker() {
|
|
@@ -890,4 +890,5 @@ public abstract class DistanceManager {
|
|
return distance <= this.viewDistance - 2;
|
|
}
|
|
}
|
|
+ */ // Paper - replace old loader system
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index 96a232f22b1c270b91635ce9c7c6cacc63b026cc..59acbf6249f8f5285504c0ddea448a3433d1d68d 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -844,17 +844,10 @@ public class ServerChunkCache extends ChunkSource {
|
|
// Paper end
|
|
|
|
public boolean isPositionTicking(long pos) {
|
|
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
|
|
-
|
|
- if (playerchunk == null) {
|
|
- return false;
|
|
- } else if (!this.level.shouldTickBlocksAt(pos)) {
|
|
- return false;
|
|
- } else {
|
|
- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error
|
|
-
|
|
- return either != null && either.left().isPresent();
|
|
- }
|
|
+ // Paper start - replace player chunk loader system
|
|
+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos);
|
|
+ return holder != null && holder.isTickingReady();
|
|
+ // Paper end - replace player chunk loader system
|
|
}
|
|
|
|
public void save(boolean flush) {
|
|
@@ -911,6 +904,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.tickChunks();
|
|
this.level.timings.chunks.stopTiming(); // Paper - timings
|
|
}
|
|
@@ -1024,13 +1018,13 @@ public class ServerChunkCache extends ChunkSource {
|
|
// Paper end - optimise chunk tick iteration
|
|
ChunkPos chunkcoordintpair = chunk1.getPos();
|
|
|
|
- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
|
|
+ if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - replace player chunk loader system
|
|
chunk1.incrementInhabitedTime(j);
|
|
if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
|
|
NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
|
|
}
|
|
|
|
- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
|
|
+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - replace player chunk loader system
|
|
this.level.tickChunk(chunk1, k);
|
|
if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
|
|
}
|
|
@@ -1259,6 +1253,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
public boolean pollTask() {
|
|
try {
|
|
boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper
|
|
+ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); // Paper
|
|
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
|
|
return true;
|
|
} else {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
index 856ef0c487af8e8c5689d58decc9acf8d52971c9..836841987ff5dd4901f75f47028a60d8d1b6e04a 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
|
@@ -677,7 +677,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
gameprofilerfiller.push("checkDespawn");
|
|
entity.checkDespawn();
|
|
gameprofilerfiller.pop();
|
|
- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) {
|
|
+ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list
|
|
Entity entity1 = entity.getVehicle();
|
|
|
|
if (entity1 != null) {
|
|
@@ -710,7 +710,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
|
|
@Override
|
|
public boolean shouldTickBlocksAt(long chunkPos) {
|
|
- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos);
|
|
+ // Paper start - replace player chunk loader system
|
|
+ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
|
|
+ return holder != null && holder.isTickingReady();
|
|
+ // Paper end - replace player chunk loader system
|
|
}
|
|
|
|
protected void tickTime() {
|
|
@@ -2454,7 +2457,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
|
private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
|
|
// Paper start - optimize is ticking ready type functions
|
|
ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
|
|
- return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos);
|
|
+ return chunkHolder != null && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); // Paper - no longer need to check with chunk source
|
|
// Paper end
|
|
}
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
index 1643883aeda01ec4d0d26115c33de44379db2365..815aa9eb2879cbfcfc2f7a00106f3455b6e8a877 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
|
|
@@ -2475,5 +2475,5 @@ public class ServerPlayer extends Player {
|
|
}
|
|
// CraftBukkit end
|
|
|
|
- public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder
|
|
+ public final int getViewDistance() { throw new UnsupportedOperationException("Use PlayerChunkLoader"); } // Paper - placeholder
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
index 80ea6e59a96b1135823ef8bd228b924caf73faed..a077d5fdfa0d42a6ac22fe1020469a25491d0235 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -274,7 +274,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.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation()));
|
|
+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management
|
|
player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
|
|
playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
|
|
playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
|
|
@@ -943,8 +943,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(), flag, entityplayer1.getLastDeathLocation()));
|
|
- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
|
|
- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot
|
|
+ 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.spawnIn(worldserver1);
|
|
entityplayer1.unsetRemoved();
|
|
entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
|
|
@@ -1513,7 +1513,7 @@ public abstract class PlayerList {
|
|
|
|
public void setViewDistance(int viewDistance) {
|
|
this.viewDistance = viewDistance;
|
|
- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
|
|
+ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance
|
|
Iterator iterator = this.server.getAllLevels().iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
@@ -1528,7 +1528,7 @@ public abstract class PlayerList {
|
|
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
this.simulationDistance = simulationDistance;
|
|
- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance));
|
|
+ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader
|
|
Iterator iterator = this.server.getAllLevels().iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
|
index 3a6e5893181ed681099f2748abca738af45ec9c9..bb51a85b33e1701c2e445305d68d3453772f73df 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
|
@@ -660,7 +660,7 @@ public class EnderDragon extends Mob implements Enemy {
|
|
// this.world.b(1028, this.getChunkCoordinates(), 0);
|
|
//int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
|
|
for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>) ((ServerLevel)level).players()) {
|
|
- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
|
|
+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
|
|
double deltaX = this.getX() - player.getX();
|
|
double deltaZ = this.getZ() - player.getZ();
|
|
double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
|
|
diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
|
index b3e2e834f4f151497bf842796dd8e3a8b5143f1b..4fb40aa91e0961f1974c74c88fa68359e4ad6b16 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
|
@@ -278,7 +278,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
|
|
// this.world.globalLevelEvent(1023, new BlockPosition(this), 0);
|
|
//int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
|
|
for (ServerPlayer player : (List<ServerPlayer>)this.level.players()) { // Paper
|
|
- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
|
|
+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
|
|
double deltaX = this.getX() - player.getX();
|
|
double deltaZ = this.getZ() - player.getZ();
|
|
double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
|
|
diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java
|
|
index 0b3e9e4ed162a6d9e1f3f55b9522b75c94d13254..fa1ff2e79954089552974cefedfcbff2225738ec 100644
|
|
--- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java
|
|
+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java
|
|
@@ -62,9 +62,10 @@ public class EnderEyeItem extends Item {
|
|
|
|
// CraftBukkit start - Use relative location for far away sounds
|
|
// world.b(1038, blockposition1.c(1, 0, 1), 0);
|
|
- int viewDistance = world.getCraftServer().getViewDistance() * 16;
|
|
+ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Paper - apply view distance patch
|
|
BlockPos soundPos = blockposition1.offset(1, 0, 1);
|
|
for (ServerPlayer player : world.getServer().getPlayerList().players) {
|
|
+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - apply view distance patch
|
|
double deltaX = soundPos.getX() - player.getX();
|
|
double deltaZ = soundPos.getZ() - player.getZ();
|
|
double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
|
|
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
|
index 17c0110fd5ccb8399af797cc892ca285b1fb7964..c118efaadd0e3e29f9adcd65c11ecabfc6d76216 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -627,6 +627,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
|
|
|
if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
|
|
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(MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management
|
|
+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
|
|
+ // Paper end - per player view distance
|
|
}
|
|
|
|
if ((i & 1) != 0) {
|
|
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 d870cefbe5b7485f423817f4f639e3e2a304640c..2292cb0e0c1a3e0ed34b941f028136bfb0bff13e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -191,6 +191,43 @@ 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() {
|
|
@@ -815,6 +852,7 @@ public class LevelChunk extends ChunkAccess {
|
|
// Paper end - neighbour cache
|
|
org.bukkit.Server server = this.level.getCraftServer();
|
|
this.level.getChunkSource().addLoadedChunk(this); // Paper
|
|
+ ((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
|
|
@@ -939,7 +977,10 @@ public class LevelChunk extends ChunkAccess {
|
|
});
|
|
}
|
|
|
|
+ public boolean isPostProcessingDone; // Paper - replace chunk loader system
|
|
+
|
|
public void postProcessGeneration() {
|
|
+ try { // Paper - replace chunk loader system
|
|
ChunkPos chunkcoordintpair = this.getPos();
|
|
|
|
for (int i = 0; i < this.postProcessing.length; ++i) {
|
|
@@ -977,6 +1018,11 @@ public class LevelChunk extends ChunkAccess {
|
|
|
|
this.pendingBlockEntities.clear();
|
|
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
|
|
}
|
|
|
|
@Nullable
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 1bbc73dcdf890a9383795ffeb0d368293f7146a5..c4f7aa9ffb72d2bc555ace64bb8cedc5c2545d8b 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -2281,43 +2281,56 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
// Spigot start
|
|
@Override
|
|
public int getViewDistance() {
|
|
- return world.spigotConfig.viewDistance;
|
|
+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management
|
|
}
|
|
|
|
@Override
|
|
public int getSimulationDistance() {
|
|
- return world.spigotConfig.simulationDistance;
|
|
+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management
|
|
}
|
|
// Spigot end
|
|
// Paper start - view distance api
|
|
@Override
|
|
public void setViewDistance(int viewDistance) {
|
|
- throw new UnsupportedOperationException(); //TODO
|
|
+ // Paper start - replace old player chunk management
|
|
+ if (viewDistance < 2 || viewDistance > 32) {
|
|
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
|
|
+ }
|
|
+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
|
|
+ chunkMap.setViewDistance(viewDistance);
|
|
+ // Paper end - replace old player chunk management
|
|
}
|
|
|
|
+ // Paper start - replace old player chunk management
|
|
@Override
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
- throw new UnsupportedOperationException(); //TODO
|
|
+ // Paper start - replace old player chunk management
|
|
+ if (simulationDistance < 2 || simulationDistance > 32) {
|
|
+ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]");
|
|
+ }
|
|
+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
|
|
+ chunkMap.setTickViewDistance(simulationDistance);
|
|
}
|
|
+ // Paper end - replace old player chunk management
|
|
|
|
@Override
|
|
public int getNoTickViewDistance() {
|
|
- throw new UnsupportedOperationException(); //TODO
|
|
+ return this.getViewDistance(); // Paper - replace old player chunk management
|
|
}
|
|
|
|
@Override
|
|
public void setNoTickViewDistance(int viewDistance) {
|
|
- throw new UnsupportedOperationException(); //TODO
|
|
+ this.setViewDistance(viewDistance); // Paper - replace old player chunk management
|
|
}
|
|
|
|
@Override
|
|
public int getSendViewDistance() {
|
|
- throw new UnsupportedOperationException(); //TODO
|
|
+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); // Paper - replace old player chunk management
|
|
}
|
|
|
|
@Override
|
|
public void setSendViewDistance(int viewDistance) {
|
|
- throw new UnsupportedOperationException(); //TODO
|
|
+ getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance); // Paper - replace old player chunk management
|
|
}
|
|
// 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 fca6ccabd527bb05d2f89bf1278b46a85f397318..c88342c53787a48477dcbec4af53d1d5ce2b02e2 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
@@ -541,45 +541,80 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
}
|
|
}
|
|
|
|
+ // Paper start - implement view distances
|
|
@Override
|
|
public int getViewDistance() {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ 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();
|
|
}
|
|
|
|
@Override
|
|
public void setViewDistance(int viewDistance) {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ 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);
|
|
}
|
|
|
|
@Override
|
|
public int getSimulationDistance() {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ 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();
|
|
}
|
|
|
|
@Override
|
|
public void setSimulationDistance(int simulationDistance) {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ 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);
|
|
}
|
|
|
|
@Override
|
|
public int getNoTickViewDistance() {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ return this.getViewDistance();
|
|
}
|
|
|
|
@Override
|
|
public void setNoTickViewDistance(int viewDistance) {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ this.setViewDistance(viewDistance);
|
|
}
|
|
|
|
@Override
|
|
public int getSendViewDistance() {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ 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();
|
|
}
|
|
|
|
@Override
|
|
public void setSendViewDistance(int viewDistance) {
|
|
- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
|
|
+ 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);
|
|
}
|
|
+ // Paper end - implement view distances
|
|
|
|
@Override
|
|
public <T> T getClientOption(com.destroystokyo.paper.ClientOption<T> type) {
|