No-Tick view distance implementation - Closes #3196

Implements world view distance getters/setters

Per-Player is absent due to difficulty of maintaining
the diff required to make it happen.
This commit is contained in:
Spottedleaf 2020-05-06 03:49:52 -04:00 committed by Aikar
parent b87743c1e0
commit 878c66f116
No known key found for this signature in database
GPG key ID: 401ADFC9891FAAFE
2 changed files with 710 additions and 0 deletions

View file

@ -0,0 +1,48 @@
From 919aecffc9c10f8e95f114e326cae396bd48e409 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 5 May 2020 21:28:01 -0700
Subject: [PATCH] World view distance api
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index db18f70e..421ad6a9 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -3166,6 +3166,34 @@ public interface World extends PluginMessageRecipient, Metadatable {
int getViewDistance();
// Spigot end
+ // Paper start - view distance api
+ /**
+ * Sets the view distance for this world.
+ * @param viewDistance view distance in [2, 32]
+ */
+ void setViewDistance(int viewDistance);
+
+ /**
+ * Returns the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @return The no-tick view distance for this world.
+ */
+ int getNoTickViewDistance();
+
+ /**
+ * Sets the no-tick view distance for this world.
+ * <p>
+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not
+ * be set to tick.
+ * </p>
+ * @param viewDistance view distance in [2, 32]
+ */
+ void setNoTickViewDistance(int viewDistance);
+ // Paper end - view distance api
+
// Spigot start
public class Spigot {
--
2.26.0

View file

@ -0,0 +1,662 @@
From 8e744f5b5d46417c511bce46f59fc1c329d30394 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Tue, 5 May 2020 21:23:34 -0700
Subject: [PATCH] No-Tick view distance implementation
Implements world view distance getters/setters
Per-Player is absent due to difficulty of maintaining
the diff required to make it happen.
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index bfb52d75c7..57baf7092d 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -691,4 +691,9 @@ public class PaperWorldConfig {
phantomIgnoreCreative = getBoolean("phantoms-do-not-spawn-on-creative-players", phantomIgnoreCreative);
phantomOnlyAttackInsomniacs = getBoolean("phantoms-only-attack-insomniacs", phantomOnlyAttackInsomniacs);
}
+
+ public int noTickViewDistance;
+ private void viewDistance() {
+ this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1);
+ }
}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 53d3acccd3..af8a3e186e 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -245,7 +245,51 @@ public class Chunk implements IChunkAccess {
}
protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
+ // Paper start - no-tick view distance
+ ChunkProviderServer chunkProviderServer = ((WorldServer)this.world).getChunkProvider();
+ PlayerChunkMap chunkMap = chunkProviderServer.playerChunkMap;
+ // this code handles the addition of ticking tickets - the distance map handles the removal
+ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
+ if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) {
+ // now we're ready for entity ticking
+ chunkProviderServer.serverThreadQueue.execute(() -> {
+ // double check that this condition still holds.
+ if (Chunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(Chunk.this.coordinateKey) != null) {
+ chunkProviderServer.addTicketAtLevel(TicketType.PLAYER, Chunk.this.loc, 31, Chunk.this.loc); // 31 -> entity ticking, TODO check on update
+ }
+ });
+ }
+ }
+ // this code handles the chunk sending
+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
+ if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) {
+ // now we're ready to send
+ chunkMap.mailboxMain.a(ChunkTaskQueueSorter.a(chunkMap.getUpdatingChunk(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap
+ // double check that this condition still holds.
+ if (!Chunk.this.areNeighboursLoaded(1)) {
+ return;
+ }
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(Chunk.this.coordinateKey);
+ if (inRange == null) {
+ return;
+ }
+
+ // broadcast
+ Object[] backingSet = inRange.getBackingSet();
+ Packet[] chunkPackets = new Packet[2];
+ for (int index = 0, len = backingSet.length; index < len; ++index) {
+ Object temp = backingSet[index];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ chunkMap.sendChunk(player, chunkPackets, Chunk.this);
+ }
+ })));
+ }
+ }
+ // Paper end - no-tick view distance
}
public final boolean areNeighboursLoaded(final int radius) {
diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
index 7cd4e29123..942efe62fe 100644
--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
@@ -252,7 +252,7 @@ public abstract class ChunkMapDistance {
return s;
}
- protected void a(int i) {
+ protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change
this.g.a(i);
}
@@ -371,7 +371,7 @@ public abstract class ChunkMapDistance {
private void a(long i, int j, boolean flag, boolean flag1) {
if (flag != flag1) {
- Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, ChunkMapDistance.b, new ChunkCoordIntPair(i));
+ Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkCoordIntPair(i)); // Paper - no-tick view distance
if (flag1) {
ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 6e8179b465..e32c458dfe 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -111,6 +111,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks
+ boolean needsChunkCenterUpdate; // Paper - no-tick view distance
+
public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
super((World) worldserver, gameprofile);
playerinteractmanager.player = this;
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 8742d13499..72ce7bf0c3 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -160,6 +160,18 @@ public class PlayerChunk {
}
// Paper end - optimise isOutsideOfRange
+ // Paper start - no-tick view distance
+ public final Chunk getSendingChunk() {
+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
+ // in Chunk's neighbour callback
+ Chunk ret = this.chunkMap.world.getChunkProvider().getChunkAtIfLoadedImmediately(this.location.x, this.location.z);
+ if (ret != null && ret.areNeighboursLoaded(1)) {
+ return ret;
+ }
+ return null;
+ }
+ // Paper end - no-tick view distance
+
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
@@ -321,7 +333,7 @@ public class PlayerChunk {
}
public void a(int i, int j, int k) {
- Chunk chunk = this.getChunk();
+ Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
this.r |= 1 << (j >> 4);
@@ -341,7 +353,7 @@ public class PlayerChunk {
}
public void a(EnumSkyBlock enumskyblock, int i) {
- Chunk chunk = this.getChunk();
+ Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
chunk.setNeedsSaving(true);
@@ -431,9 +443,48 @@ public class PlayerChunk {
}
private void a(Packet<?> packet, boolean flag) {
- this.players.a(this.location, flag).forEach((entityplayer) -> {
- entityplayer.playerConnection.sendPacket(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.playerViewDistanceBroadcastMap;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> players = viewDistanceMap.getObjectsInRange(this.location);
+ if (players == null) {
+ return;
+ }
+
+ if (flag) { // flag -> border only
+ Object[] backingSet = players.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+
+ int viewDistance = viewDistanceMap.getLastViewDistance(player);
+ long lastPosition = viewDistanceMap.getLastCoordinate(player);
+
+ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - this.location.x);
+ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - this.location.z);
+
+ if (Math.max(distX, distZ) == viewDistance) {
+ player.playerConnection.sendPacket(packet);
+ }
+ }
+ } else {
+ Object[] backingSet = players.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ player.playerConnection.sendPacket(packet);
+ }
+ }
+
+ return;
+ // Paper end - per player view distance
}
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) {
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 099e612171..345f2689c7 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -71,7 +71,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
private boolean updatingChunksModified;
private final ChunkTaskQueueSorter p;
private final Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailboxWorldGen;
- private final Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailboxMain;
+ final Mailbox<ChunkTaskQueueSorter.a<Runnable>> mailboxMain; // Paper - private -> package private
public final WorldLoadListener worldLoadListener;
public final PlayerChunkMap.a chunkDistanceManager; public final PlayerChunkMap.a getChunkMapDistanceManager() { return this.chunkDistanceManager; } // Paper - OBFHELPER
private final AtomicInteger u;
@@ -141,6 +141,19 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
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 PlayerChunkMap#isOutsideRange
+ // Paper start - no-tick view distance
+ int noTickViewDistance;
+ public final int getRawNoTickViewDistance() {
+ return this.noTickViewDistance;
+ }
+ public final int getEffectiveNoTickViewDistance() {
+ return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance;
+ }
+
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap;
+ // Paper end - no-tick view distance
void addPlayerToDistanceMaps(EntityPlayer player) {
int chunkX = MCUtil.getChunkCoordinate(player.locX());
@@ -157,6 +170,19 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
// Paper start - optimise PlayerChunkMap#isOutsideRange
this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE);
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // Paper start - no-tick view distance
+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
+
+ if (!this.cannotLoadChunks(player)) {
+ this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance);
+ this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
+ }
+
+ player.needsChunkCenterUpdate = true;
+ this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
+ player.needsChunkCenterUpdate = false;
+ // Paper end - no-tick view distance
}
void removePlayerFromDistanceMaps(EntityPlayer player) {
@@ -169,6 +195,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.playerMobSpawnMap.remove(player);
this.playerChunkTickRangeMap.remove(player);
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // Paper start - no-tick view distance
+ this.playerViewDistanceBroadcastMap.remove(player);
+ this.playerViewDistanceTickMap.remove(player);
+ this.playerViewDistanceNoTickMap.remove(player);
+ // Paper end - no-tick view distance
}
void updateMaps(EntityPlayer player) {
@@ -186,6 +217,19 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
// Paper start - optimise PlayerChunkMap#isOutsideRange
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, ChunkMapDistance.MOB_SPAWN_RANGE);
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // Paper start - no-tick view distance
+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
+
+ if (!this.cannotLoadChunks(player)) {
+ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance);
+ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
+ }
+
+ player.needsChunkCenterUpdate = true;
+ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
+ player.needsChunkCenterUpdate = false;
+ // Paper end - no-tick view distance
}
@@ -293,6 +337,45 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
});
// Paper end - optimise PlayerChunkMap#isOutsideRange
+ // Paper start - no-tick view distance
+ this.setNoTickViewDistance(this.world.paperConfig.noTickViewDistance);
+ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ if (newState.size() != 1) {
+ return;
+ }
+ Chunk chunk = PlayerChunkMap.this.world.getChunkProvider().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
+ return;
+ }
+
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
+ PlayerChunkMap.this.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
+ },
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ if (newState != null) {
+ return;
+ }
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(rangeX, rangeZ);
+ PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
+ });
+ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ if (player.needsChunkCenterUpdate) {
+ player.needsChunkCenterUpdate = false;
+ player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ));
+ }
+ PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded
+ },
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded
+ });
+ // Paper end - no-tick view distance
}
public void updatePlayerMobTypeMap(Entity entity) {
@@ -1113,15 +1196,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
completablefuture1.thenAcceptAsync((either) -> {
either.mapLeft((chunk) -> {
this.u.getAndIncrement();
- Packet<?>[] apacket = new Packet[2];
-
- this.a(chunkcoordintpair, false).forEach((entityplayer) -> {
- this.a(entityplayer, apacket, chunk);
- });
+ // Paper - no-tick view distance - moved to Chunk neighbour update
return Either.left(chunk);
});
}, (runnable) -> {
- this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error
+ this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error // Paper - diff on change, this is the scheduling method copied in Chunk used to schedule chunk broadcasts (on change it needs to be copied again)
});
return completablefuture1;
}
@@ -1221,32 +1300,52 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
} // Paper
}
- protected void setViewDistance(int i) {
- int j = MathHelper.clamp(i + 1, 3, 33);
+ public final void setViewDistance(int i) { // Paper - public
+ int j = MathHelper.clamp(i + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32
if (j != this.viewDistance) {
int k = this.viewDistance;
this.viewDistance = j;
- this.chunkDistanceManager.a(this.viewDistance);
- ObjectIterator objectiterator = this.updatingChunks.values().iterator();
+ if (this.world != null && this.world.players != null) { // this can be called from constructor, where these aren't set
+ // Paper start - no-tick view distance
+ for (EntityPlayer player : this.world.players) {
+ PlayerConnection connection = player.playerConnection;
+ if (connection != null) {
+ // moved in from PlayerList
+ connection.sendPacket(new PacketPlayOutViewDistance(this.getEffectiveNoTickViewDistance()));
+ }
+ this.updateMaps(player); // distance map handles the chunk sending (and ticket level changes)
+ }
+ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); // propagate changes to no-tick, which does the actual chunk loading/sending
+ // Paper end - no-tick view distance
+ }
+ }
- while (objectiterator.hasNext()) {
- PlayerChunk playerchunk = (PlayerChunk) objectiterator.next();
- ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
- Packet<?>[] apacket = new Packet[2];
+ }
+
+ // Paper start - no-tick view distance
+ public final void setNoTickViewDistance(int viewDistance) {
+ viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32);
+ if (viewDistance == this.noTickViewDistance && viewDistance != -1) {
+ return;
+ }
- this.a(chunkcoordintpair, false).forEach((entityplayer) -> {
- int l = b(chunkcoordintpair, entityplayer, true);
- boolean flag = l <= k;
- boolean flag1 = l <= this.viewDistance;
+ this.noTickViewDistance = viewDistance;
+ this.chunkDistanceManager.setNoTickViewDistance(this.getEffectiveNoTickViewDistance() + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2
- this.sendChunk(entityplayer, chunkcoordintpair, apacket, flag, flag1);
- });
+ if (this.world != null && this.world.players != null) { // this can be called from constructor, where these aren't set
+ for (EntityPlayer player : this.world.players) {
+ PlayerConnection connection = player.playerConnection;
+ if (connection != null) {
+ // moved in from PlayerList
+ connection.sendPacket(new PacketPlayOutViewDistance(this.getEffectiveNoTickViewDistance()));
+ }
+ this.updateMaps(player);
}
}
-
}
+ // Paper end - no-tick view distance
protected void sendChunk(EntityPlayer entityplayer, ChunkCoordIntPair chunkcoordintpair, Packet<?>[] apacket, boolean flag, boolean flag1) {
if (entityplayer.world == this.world) {
@@ -1254,7 +1353,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
PlayerChunk playerchunk = this.getVisibleChunk(chunkcoordintpair.pair());
if (playerchunk != null) {
- Chunk chunk = playerchunk.getChunk();
+ Chunk chunk = playerchunk.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
this.a(entityplayer, apacket, chunk);
@@ -1523,6 +1622,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
// Paper end - optimise isOutsideOfRange
+ private boolean cannotLoadChunks(EntityPlayer entityplayer) { return this.b(entityplayer); } // Paper - OBFHELPER
private boolean b(EntityPlayer entityplayer) {
return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS);
}
@@ -1550,13 +1650,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.removePlayerFromDistanceMaps(entityplayer); // Paper - distance maps
}
- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) {
- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) {
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k, l);
-
- this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], !flag, flag);
- }
- }
+ // Paper - broadcast view distance map handles this (see remove/add calls above)
}
@@ -1564,7 +1658,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
SectionPosition sectionposition = SectionPosition.a((Entity) entityplayer);
entityplayer.a(sectionposition);
- entityplayer.playerConnection.sendPacket(new PacketPlayOutViewCentre(sectionposition.a(), sectionposition.c()));
+ // Paper - distance map handles this now
return sectionposition;
}
@@ -1609,6 +1703,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
int k1;
int l1;
+ /* // Paper start - replaced by distance map
if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
k1 = Math.min(i, i1) - this.viewDistance;
l1 = Math.min(j, j1) - this.viewDistance;
@@ -1646,7 +1741,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], false, true);
}
}
- }
+ }*/ // Paper end - replaced by distance map
this.updateMaps(entityplayer); // Paper - distance maps
@@ -1654,11 +1749,46 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
@Override
public Stream<EntityPlayer> a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
- return this.playerMap.a(chunkcoordintpair.pair()).filter((entityplayer) -> {
- int i = b(chunkcoordintpair, entityplayer, true);
+ // Paper start - per player view distance
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
+ // view distance map here.
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkcoordintpair);
- return i > this.viewDistance ? false : !flag || i == this.viewDistance;
- });
+ if (inRange == null) {
+ return Stream.empty();
+ }
+ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it
+ List<EntityPlayer> players = new ArrayList<>();
+ Object[] backingSet = inRange.getBackingSet();
+
+ if (flag) { // flag -> border only
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
+ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
+
+ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkcoordintpair.x);
+ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkcoordintpair.z);
+ if (Math.max(distX, distZ) == viewDistance) {
+ players.add(player);
+ }
+ }
+ } else {
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ players.add(player);
+ }
+ }
+ return players.stream();
+ // Paper end - per player view distance
}
protected void addEntity(Entity entity) {
@@ -1829,6 +1959,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
+ final void sendChunk(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Paper - OBFHELPER
private void a(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) {
if (apacket[0] == null) {
apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, true); // Paper - Anti-Xray
@@ -2014,7 +2145,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(this.tracker.chunkX, this.tracker.chunkZ);
PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair.pair());
- if (playerchunk != null && playerchunk.getChunk() != null) {
+ if (playerchunk != null && playerchunk.getSendingChunk() != null) { // Paper - no-tick view distance
flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.viewDistance;
}
}
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index edf9df8c8a..ec95b63e51 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -150,7 +150,7 @@ public abstract class PlayerList {
// CraftBukkit - getType()
// Spigot - view distance
- playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), WorldData.c(worlddata.getSeed()), worlddata.isHardcore(), worldserver.worldProvider.getDimensionManager().getType(), this.getMaxPlayers(), worlddata.getType(), worldserver.spigotConfig.viewDistance, flag1, !flag));
+ playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), WorldData.c(worlddata.getSeed()), worlddata.isHardcore(), worldserver.worldProvider.getDimensionManager().getType(), this.getMaxPlayers(), worlddata.getType(), worldserver.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance(), flag1, !flag)); // Paper - no-tick view distance
entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
playerconnection.sendPacket(new PacketPlayOutCustomPayload(PacketPlayOutCustomPayload.a, (new PacketDataSerializer(Unpooled.buffer())).a(this.getServer().getServerModName())));
playerconnection.sendPacket(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
@@ -770,7 +770,7 @@ public abstract class PlayerList {
WorldData worlddata = worldserver.getWorldData();
entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver.worldProvider.getDimensionManager().getType(), WorldData.c(worldserver.getWorldData().getSeed()), worldserver.getWorldData().getType(), entityplayer1.playerInteractManager.getGameMode()));
- entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver.spigotConfig.viewDistance)); // Spigot
+ entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance())); // Paper - no-tick view distance
entityplayer1.spawnIn(worldserver);
entityplayer1.dead = false;
entityplayer1.playerConnection.teleport(new Location(worldserver.getWorld(), entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch));
@@ -1254,7 +1254,7 @@ public abstract class PlayerList {
public void a(int i) {
this.viewDistance = i;
- this.sendAll(new PacketPlayOutViewDistance(i));
+ //this.sendAll(new PacketPlayOutViewDistance(i)); // Paper - move into setViewDistance
Iterator iterator = this.server.getWorlds().iterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 899c535c40..0e6368d0fb 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -443,8 +443,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
this.b(blockposition, iblockdata1, iblockdata2);
}
- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - diff on change, see below
this.notify(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 || ((WorldServer)this).getChunkProvider().playerChunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) {
+ ((WorldServer)this).getChunkProvider().flagDirty(blockposition);
+ // Paper end - per player view distance
}
if (!this.isClientSide && (i & 1) != 0) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 995f706678..ee7ae46389 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2483,10 +2483,39 @@ public class CraftWorld implements World {
// Spigot start
@Override
public int getViewDistance() {
- return world.spigotConfig.viewDistance;
+ return getHandle().getChunkProvider().playerChunkMap.getEffectiveViewDistance(); // Paper - no-tick view distance
}
// Spigot end
+ // Paper start - per player view distance
+ @Override
+ public void setViewDistance(int viewDistance) {
+ if (viewDistance < 2 || viewDistance > 32) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ net.minecraft.server.PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap;
+ if (viewDistance != chunkMap.getEffectiveViewDistance()) {
+ chunkMap.setViewDistance(viewDistance);
+ }
+ }
+
+ @Override
+ public int getNoTickViewDistance() {
+ return getHandle().getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance();
+ }
+
+ @Override
+ public void setNoTickViewDistance(int viewDistance) {
+ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ net.minecraft.server.PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap;
+ if (viewDistance != chunkMap.getRawNoTickViewDistance()) {
+ chunkMap.setNoTickViewDistance(viewDistance);
+ }
+ }
+ // Paper end - per player view distance
+
// Spigot start
private final Spigot spigot = new Spigot()
{
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index d873b8cf3a..f735217e7a 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -201,7 +201,7 @@ public class ActivationRange
maxRange = Math.max( maxRange, waterActivationRange );
maxRange = Math.max( maxRange, villagerActivationRange );
// Paper end
- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
+ maxRange = Math.min( ( ((net.minecraft.server.WorldServer)world).getChunkProvider().playerChunkMap.getEffectiveViewDistance() << 4 ) - 8, maxRange ); // Paper - no-tick view distance
for ( EntityHuman player : world.getPlayers() )
{
--
2.26.0