Paper/Spigot-Server-Patches/0475-No-Tick-view-distance-implementation.patch
Aikar 757948dca3
[Auto] Updated Upstream (CraftBukkit)
Upstream has released updates that appears to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

CraftBukkit Changes:
e14c9acbe SPIGOT-6113: Ender Pearls thrown into portals that lead to disabled dimensions crash the server
97da39889 SPIGOT-6114: PlayerRespawnEvent player's location changed before event called
3b5c36769 SPIGOT-6115: Items being deleted on pick up with full inventory
2020-08-27 22:37:10 -04:00

677 lines
39 KiB
Diff

From 0000000000000000000000000000000000000000 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/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
index c9164dfdb27ddf3709129c8aec54903a1df121ff..e33e889c291d37a821a4fbd40d9aac7bb079de0d 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -153,7 +153,8 @@ public class TimingsExport extends Thread {
pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
return pair(rule, world.getWorld().getGameRuleValue(rule));
})),
- pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance())
+ pair("ticking-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance()),
+ pair("notick-viewdistance", world.getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance())
));
}));
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index c6843f37f89f0df8a021f6f4b3ed048e6d36549a..6a62f2000a58312773a8cb57c546d65df980b844 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -618,4 +618,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 eebd014f9c0f4285f16d9ffac79e604854a6926c..6ac8670c76eaf0c1292c6e88c63eaf6b18c9fbb8 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -198,7 +198,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 isAnyNeighborsLoaded() {
diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
index ef61f8e784b7ebd26293d627e8b8e1aef1be6e21..03b1a67aaf3ed75b7669a3157847affd678f8df4 100644
--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
@@ -262,7 +262,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);
}
@@ -381,7 +381,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(() -> {
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index 474bb8ee2548a90c74b458c9952ddf1e465a11ef..4f2e0a6fbc281c3ccb8f1bb655955d44d9afde32 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -117,6 +117,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(worldserver, worldserver.getSpawn(), worldserver.v(), gameprofile);
this.spawnDimension = World.OVERWORLD;
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index df3150072fd36dac28d83309e50342c9cfa326b3..0b5ddff008d151ad03a1f382a8f24494356e8701 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -621,7 +621,8 @@ public final class MCUtil {
});
worldData.addProperty("name", world.getWorld().getName());
- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
+ worldData.addProperty("view-distance", world.getChunkProvider().playerChunkMap.getEffectiveViewDistance());
+ worldData.addProperty("no-view-distance", world.getChunkProvider().playerChunkMap.getRawNoTickViewDistance());
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
worldData.addProperty("visible-chunk-count", visibleChunks.size());
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 4074c1f8f8b71544df9aa6e8d59c5703f2036fd9..2784ea71b9f5ee93f6de8e5a3a135457f1be17a3 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -57,6 +57,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;
@@ -212,7 +224,7 @@ public class PlayerChunk {
}
public void a(BlockPosition blockposition) {
- Chunk chunk = this.getChunk();
+ Chunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
if (chunk != null) {
byte b0 = (byte) SectionPosition.a(blockposition.getY());
@@ -228,7 +240,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);
@@ -310,9 +322,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 b4067657eefe6a418b76b599b4c8ffb8359c3f89..3e73ef8059139b09518075bb6dc89a67f46be51a 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -96,7 +96,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;
private final AtomicInteger u;
@@ -171,6 +171,22 @@ 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 int getLoadViewDistance() {
+ return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance());
+ }
+
+ 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());
@@ -187,6 +203,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) {
@@ -199,6 +228,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) {
@@ -216,6 +250,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
}
// Paper end
@@ -323,6 +370,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) {
@@ -1143,15 +1229,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));
+ this.mailboxMain.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // 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;
}
@@ -1246,32 +1328,38 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
}
- protected void setViewDistance(int i) {
- int j = MathHelper.clamp(i + 1, 3, 33);
+ public 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();
+ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); //Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending
+ }
- while (objectiterator.hasNext()) {
- PlayerChunk playerchunk = (PlayerChunk) objectiterator.next();
- ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
- Packet<?>[] apacket = new Packet[2];
+ }
- this.a(chunkcoordintpair, false).forEach((entityplayer) -> {
- int l = b(chunkcoordintpair, entityplayer, true);
- boolean flag = l <= k;
- boolean flag1 = l <= this.viewDistance;
+ // Paper start - no-tick view distance
+ public final void setNoTickViewDistance(int viewDistance) {
+ viewDistance = viewDistance == -1 ? -1 : MathHelper.clamp(viewDistance, 2, 32);
- this.sendChunk(entityplayer, chunkcoordintpair, apacket, flag, flag1);
- });
+ this.noTickViewDistance = viewDistance;
+ int loadViewDistance = this.getLoadViewDistance();
+ this.chunkDistanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2
+
+ 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(loadViewDistance));
+ }
+ 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) {
@@ -1279,7 +1367,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);
@@ -1540,6 +1628,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);
}
@@ -1567,13 +1656,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)
}
@@ -1581,7 +1664,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;
}
@@ -1626,6 +1709,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;
@@ -1663,7 +1747,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
@@ -1671,11 +1755,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 java.util.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) {
@@ -1833,6 +1952,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);
@@ -2018,7 +2138,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 40f2984e5ba7627ef4ce87133bed5df51a68fd65..4c16da52f4402b21be2df3fdf5999427629d6ae2 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -175,7 +175,7 @@ public abstract class PlayerList {
boolean flag1 = gamerules.getBoolean(GameRules.REDUCED_DEBUG_INFO);
// Spigot - view distance
- playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), entityplayer.playerInteractManager.c(), BiomeManager.a(worldserver1.getSeed()), worlddata.isHardcore(), this.server.F(), this.s, worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, flag1, !flag, worldserver1.isDebugWorld(), worldserver1.isFlatWorld()));
+ playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), entityplayer.playerInteractManager.c(), BiomeManager.a(worldserver1.getSeed()), worlddata.isHardcore(), this.server.F(), this.s, worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), this.getMaxPlayers(), worldserver1.getChunkProvider().playerChunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebugWorld(), worldserver1.isFlatWorld())); // 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()));
@@ -825,7 +825,7 @@ public abstract class PlayerList {
// CraftBukkit start
WorldData worlddata = worldserver1.getWorldData();
entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver1.getDimensionManager(), worldserver1.getDimensionKey(), BiomeManager.a(worldserver1.getSeed()), entityplayer1.playerInteractManager.getGameMode(), entityplayer1.playerInteractManager.c(), worldserver1.isDebugWorld(), worldserver1.isFlatWorld(), flag));
- entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver1.spigotConfig.viewDistance)); // Spigot
+ entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver1.getChunkProvider().playerChunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance
entityplayer1.spawnIn(worldserver1);
entityplayer1.dead = false;
entityplayer1.playerConnection.teleport(new Location(worldserver1.getWorld(), entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch));
@@ -1292,7 +1292,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 34e425854541333b3042f9ec4abb80f186fb67d6..b4bb84088d5932f92715776d9a62aa26a90122ba 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -473,8 +473,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 ((i & 1) != 0) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 5ef12e85d7374c137e2d7ff3e0571995070cc222..f35dda50fd9015a793708d214c648d75f9f87e2b 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2518,10 +2518,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 119a9c1e400ada270d0d97bea4170a8238d9c17d..37e930e0cb05cdb19c1b0dbd870313a14402181b 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() )
{