From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf 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 13b89276feb76fcecaeefc166a1bc161d5931d9d..5af7e5c815752f2fd2b13c02a905796971401813 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -629,4 +629,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 b65ae2d6919a67498d0646c5522735086fec00c1..7f508b9ef616071b1adeef7c00da7f4565ef4ddd 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -246,7 +246,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 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 7e57a53ec614a2f7d2672edff9d7c0e0dca42377..c072f61e8c88eac8335acd660d8ff0e2f9db819e 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 2b7e7a7aaa56b9c51fa0c8065ff25828d92c09ee..5f9ab1b81c1440d9b3003fbbf7d00b135a6b811e 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -115,6 +115,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(), 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 a68e4fc411ae84f12b1ca7443fa66f6325712af8..d24b5fa7e77bb18626459f6c3ab4aa20a7512712 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -620,7 +620,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 7f660d3c528f5fb4150e4ee8b29913436f125b06..40347212ad1bcf857d5b8ddb0ee6a698e2568201 100644 --- a/src/main/java/net/minecraft/server/PlayerChunk.java +++ b/src/main/java/net/minecraft/server/PlayerChunk.java @@ -55,6 +55,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; @@ -210,7 +222,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); @@ -230,7 +242,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); @@ -303,9 +315,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 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> 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 026562c72d7e95345d9369c6d6331cf6cedb8f17..fe343f70ce8024c86363637fda8e5c09cc6e60a9 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -95,7 +95,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { private boolean updatingChunksModified; private final ChunkTaskQueueSorter p; private final Mailbox> mailboxWorldGen; - private final Mailbox> mailboxMain; + final Mailbox> 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; @@ -170,6 +170,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()); @@ -186,6 +202,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) { @@ -198,6 +227,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) { @@ -215,6 +249,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 @@ -322,6 +369,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 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 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 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 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) { @@ -1140,15 +1226,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; } @@ -1243,32 +1325,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) { @@ -1276,7 +1364,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); @@ -1537,6 +1625,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); } @@ -1564,13 +1653,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) } @@ -1578,7 +1661,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; } @@ -1623,6 +1706,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; @@ -1660,7 +1744,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 @@ -1668,11 +1752,46 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @Override public Stream 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 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 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) { @@ -1830,6 +1949,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); @@ -2015,7 +2135,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 3d110c998f6b28e55055637bbfeff6f2ebdb2747..59f00891ee2d1641678d4903a365cb94247bf08f 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.E(), this.s, worldserver1.getTypeKey(), 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.E(), this.s, worldserver1.getTypeKey(), 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())); @@ -814,7 +814,7 @@ public abstract class PlayerList { // CraftBukkit start WorldData worlddata = worldserver1.getWorldData(); entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver1.getTypeKey(), 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)); @@ -1281,7 +1281,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 365539e53efdb7e729b580f52fb23659cbde4f0a..ba50189b10b54f5b35448c898f8c3495ddf8e112 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -508,8 +508,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 3ec021746ffaea89f09126538fcaa0555872ac17..11559f9e4b3f5fda6bfe4f70f963ce4f7967f051 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2530,10 +2530,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 5dcc1ba547db7cc53a08426a7ad119ae88690136..f78e44e05f7a97a7da190aa4b5aa6fb8340728ea 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() ) {