From 2596b8afdcce897b8147b87660b0b570d1c39a69 Mon Sep 17 00:00:00 2001 From: Woder <17339354+wode490390@users.noreply.github.com> Date: Sat, 12 Aug 2023 23:44:59 +0800 Subject: [PATCH] Prepare chunk packets only for required versions --- src/main/java/cn/nukkit/Player.java | 9 + src/main/java/cn/nukkit/level/Level.java | 246 +++++++++++++++--- .../format/generic/ChunkPacketCache.java | 14 +- .../format/generic/ChunkRequestTask.java | 17 +- .../level/format/mcregion/McRegion.java | 2 +- 5 files changed, 240 insertions(+), 48 deletions(-) diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 89b03ad73aa..f9c8e2dce1c 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -278,6 +278,8 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde public int pickedXPOrb = 0; protected int protocol; + @Nullable + protected StaticVersion blockVersion; protected int formWindowCount = 0; protected Int2ObjectMap formWindows = new Int2ObjectOpenHashMap<>(); @@ -817,6 +819,8 @@ protected boolean switchLevel(Level targetLevel) { this.unloadChunk(chunkX, chunkZ, oldLevel); } + targetLevel.onPlayerAdd(this); + this.usedChunks = new Long2BooleanOpenHashMap(); if (this.onLevelSwitch()) { @@ -6170,4 +6174,9 @@ protected void sendDeathInfo(TextContainer message) { */ public void sendMotionPredictionHints(long entityRuntimeId, Vector3f motion, boolean onGround) { } + + @Nullable + public StaticVersion getBlockVersion() { + return blockVersion; + } } diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 7a0c180c5f1..6e82c209408 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -247,6 +247,8 @@ public int size() { // private final List nextTickUpdates = Lists.newArrayList(); //private final Map updateQueueIndex = new HashMap<>(); + private final Map requestChunkVersions = new EnumMap<>(StaticVersion.class); + private final Long2ObjectMap> subChunkSendQueue = new Long2ObjectOpenHashMap<>(); private final ConcurrentMap> chunkSendQueue = new ConcurrentHashMap<>(); @@ -3353,28 +3355,24 @@ public boolean requestSubChunks(int x, int z, Player player) { return false; } long index = Level.chunkHash(x, z); - - Int2ObjectMap loaders = this.subChunkSendQueue.get(index); - if (loaders == null) { - loaders = new Int2ObjectOpenHashMap<>(); - this.subChunkSendQueue.put(index, loaders); - } - loaders.put(loaderId, player); - - this.chunkSendQueue.putIfAbsent(index, new Int2ObjectOpenHashMap<>()); + this.subChunkSendQueue.computeIfAbsent(index, key -> new Int2ObjectOpenHashMap<>()).put(loaderId, player); + this.chunkSendQueue.computeIfAbsent(index, key -> new Int2ObjectOpenHashMap<>()); return true; } public void requestChunk(int x, int z, Player player) { Preconditions.checkState(player.getLoaderId() > 0, player.getName() + " has no chunk loader"); long index = Level.chunkHash(x, z); - this.chunkSendQueue.putIfAbsent(index, new Int2ObjectOpenHashMap<>()); - this.chunkSendQueue.get(index).put(player.getLoaderId(), player); + this.chunkSendQueue.computeIfAbsent(index, key -> new Int2ObjectOpenHashMap<>()).put(player.getLoaderId(), player); } private void sendChunk(int x, int z, long index, int subChunkCount, ChunkBlobCache chunkBlobCache, ChunkPacketCache chunkPacketCache) { - if (this.chunkSendTasks.contains(index)) { - for (Player player : this.chunkSendQueue.get(index).values()) { + if (this.chunkSendTasks.remove(index)) { + Int2ObjectMap loaders = this.chunkSendQueue.remove(index); + if (loaders == null) { + return; + } + for (Player player : loaders.values()) { if (player.isConnected() && player.usedChunks.containsKey(index)) { int protocol = player.getProtocol(); if (protocol < 361) { @@ -3384,8 +3382,18 @@ private void sendChunk(int x, int z, long index, int subChunkCount, ChunkBlobCac } else if (protocol < StaticVersion.getValues()[0].getProtocol()) { player.sendChunk(x, z, subChunkCount, chunkBlobCache, chunkPacketCache.getPacket116()); } else if (protocol < 475) { - player.sendChunk(x, z, subChunkCount, chunkBlobCache, chunkPacketCache.getPacket( - StaticVersion.fromProtocol(protocol, player.isNetEaseClient()))); + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion == null) { + continue; + } + + BatchPacket packet = chunkPacketCache.getPacket(blockVersion); + if (packet == null) { + requestChunk(x, z, player); + continue; + } + + player.sendChunk(x, z, subChunkCount, chunkBlobCache, packet); } else if (player.isSubChunkRequestAvailable()) { if (protocol < 486 || !ENABLE_SUB_CHUNK_NETWORK_OPTIMIZATION) { if (protocol < 503) { @@ -3403,46 +3411,136 @@ private void sendChunk(int x, int z, long index, int subChunkCount, ChunkBlobCac chunkPacketCache.getSubModePacketTruncatedNew()); } } else { - player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, chunkBlobCache, - chunkPacketCache.getPacket(StaticVersion.fromProtocol(protocol, player.isNetEaseClient()))); + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion == null) { + continue; + } + + BatchPacket packet = chunkPacketCache.getPacket(blockVersion); + if (packet == null) { + requestChunk(x, z, player); + continue; + } + + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, chunkBlobCache, packet); } } } - - this.chunkSendQueue.remove(index); - this.chunkSendTasks.remove(index); } } private void processChunkRequest() { this.timings.syncChunkSendTimer.startTiming(); - Iterator it = this.chunkSendQueue.keySet().iterator(); + Iterator>> it = this.chunkSendQueue.entrySet().iterator(); while (it.hasNext()) { - long index = it.next(); - if (this.chunkSendTasks.contains(index)) { + Map.Entry> entry = it.next(); + long index = entry.getKey(); + if (!this.chunkSendTasks.add(index)) { continue; } int x = getHashX(index); int z = getHashZ(index); - this.chunkSendTasks.add(index); BaseFullChunk chunk = getChunk(x, z); if (chunk != null) { ChunkBlobCache blobCache = chunk.getBlobCache(); ChunkPacketCache packetCache = chunk.getPacketCache(); if (blobCache != null && packetCache != null) { - this.sendChunk(x, z, index, blobCache.getSubChunkCount(), blobCache, packetCache); + int subChunkCount = blobCache.getSubChunkCount(); + + boolean requestFullChunk = false; + Iterator iter = entry.getValue().values().iterator(); + while (iter.hasNext()) { + Player player = iter.next(); + + if (!player.isConnected() || !player.usedChunks.containsKey(index)) { + iter.remove(); + continue; + } + + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion != null && !packetCache.hasRequested(blockVersion)) { + requestFullChunk = true; + continue; + } + + int protocol = player.getProtocol(); + if (protocol < 361) { + player.sendChunk(x, z, subChunkCount, blobCache, packetCache.getPacketOld()); + } else if (protocol < 407) { + player.sendChunk(x, z, subChunkCount, blobCache, packetCache.getPacket()); + } else if (protocol < StaticVersion.getValues()[0].getProtocol()) { + player.sendChunk(x, z, subChunkCount, blobCache, packetCache.getPacket116()); + } else if (protocol < 475) { + if (blockVersion == null) { + iter.remove(); + continue; + } + + player.sendChunk(x, z, subChunkCount, blobCache, packetCache.getPacket(blockVersion)); + } else if (player.isSubChunkRequestAvailable()) { + if (protocol < 486 || !ENABLE_SUB_CHUNK_NETWORK_OPTIMIZATION) { + if (protocol < 503) { + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, blobCache, packetCache.getSubModePacket()); + } else { + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, blobCache, packetCache.getSubModePacketNew()); + } + } else if (protocol < 503) { + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, blobCache, packetCache.getSubModePacketTruncated()); + } else { + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, blobCache, packetCache.getSubModePacketTruncatedNew()); + } + } else { + if (blockVersion == null) { + iter.remove(); + continue; + } + + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, blobCache, packetCache.getPacket(blockVersion)); + } - Int2ObjectMap loaders = this.subChunkSendQueue.remove(index); + iter.remove(); + } + + boolean requestSubChunks = false; + Int2ObjectMap loaders = this.subChunkSendQueue.get(index); if (loaders != null) { - for (Player player : loaders.values()) { + Iterator iterator = loaders.values().iterator(); + while (iterator.hasNext()) { + Player player = iterator.next(); + if (!player.isConnected()) { + iterator.remove(); + continue; + } + + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion == null) { + iterator.remove(); continue; } + + if (!packetCache.hasRequested(blockVersion)) { + requestSubChunks = true; + continue; + } + player.sendSubChunks(this.getDimension().ordinal(), x, z, blobCache.getSubChunkCount(), blobCache, packetCache, blobCache.getHeightMapType(), blobCache.getHeightMapData()); + iterator.remove(); + } + + if (!requestSubChunks) { + this.subChunkSendQueue.remove(index); } } - continue; + if (!requestFullChunk) { + this.chunkSendQueue.remove(index); + this.chunkSendTasks.remove(index); + + if (!requestSubChunks) { + continue; + } + } } } this.timings.syncChunkSendPrepareTimer.startTiming(); @@ -3463,7 +3561,7 @@ public boolean isCacheChunks() { * Chunk request callback on main thread * If this.cacheChunks == false, the ChunkPacketCache can be null; */ - public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount, ChunkBlobCache chunkBlobCache, ChunkPacketCache chunkPacketCache, byte[] payload, byte[] payloadOld, byte[] subModePayload, byte[] subModePayloadNew, Map payloads, Map subChunkPayloads, byte[] heightMapType, byte[][] heightMapData, boolean[] emptySection) { + public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount, ChunkBlobCache chunkBlobCache, ChunkPacketCache chunkPacketCache, byte[] payload, byte[] payloadOld, byte[] subModePayload, byte[] subModePayloadNew, Map payloads, Map subChunkPayloads, byte[] heightMapType, byte[][] heightMapData, boolean[] emptySection, Set requestedVersions) { this.timings.syncChunkSendTimer.startTiming(); long index = Level.chunkHash(x, z); @@ -3513,8 +3611,8 @@ public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount getChunkCacheFromData(x, z, LevelChunkPacket.CLIENT_REQUEST_TRUNCATED_COLUMN_FAKE_COUNT, extendedCount, subModePayload, false, true), getChunkCacheFromData(x, z, subChunkCount, payload, false, true), getChunkCacheFromData(x, z, subChunkCount, payload, false, false), - getChunkCacheFromData(x, z, subChunkCount, payloadOld, true, false) - ); + getChunkCacheFromData(x, z, subChunkCount, payloadOld, true, false), + requestedVersions); } BaseFullChunk chunk = getChunk(x, z, false); if (chunk != null && chunk.getChanges() <= timestamp) { @@ -3529,6 +3627,17 @@ public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount if (!player.isConnected()) { continue; } + + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion == null) { + continue; + } + + if (!payloads.containsKey(blockVersion)) { + requestSubChunks(x, z, player); + continue; + } + player.sendSubChunks(this.getDimension().ordinal(), x, z, subChunkCount, chunkBlobCache, chunkPacketCache, heightMapType, heightMapData); } } @@ -3537,29 +3646,43 @@ public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount return; } - if (this.chunkSendTasks.contains(index)) { - for (Player player : this.chunkSendQueue.get(index).values()) { + Int2ObjectMap chunkLoaders; + if (this.chunkSendTasks.remove(index) && (chunkLoaders = this.chunkSendQueue.remove(index)) != null) { + for (Player player : chunkLoaders.values()) { if (player.isConnected() && player.usedChunks.containsKey(index)) { + StaticVersion blockVersion = player.getBlockVersion(); + byte[] data = null; + if (blockVersion != null) { + data = payloads.get(blockVersion); + if (data == null) { + requestChunk(x, z, player); + continue; + } + } + int protocol = player.getProtocol(); if (protocol < 361) { player.sendChunk(x, z, subChunkCount, chunkBlobCache, payloadOld, subModePayload); } else if (protocol < StaticVersion.getValues()[0].getProtocol()) { player.sendChunk(x, z, subChunkCount, chunkBlobCache, payload, subModePayload); } else if (protocol < 475) { - player.sendChunk(x, z, subChunkCount, chunkBlobCache, - payloads.get(StaticVersion.fromProtocol(protocol, player.isNetEaseClient())), subModePayload); + if (data == null) { + continue; + } + player.sendChunk(x, z, subChunkCount, chunkBlobCache, data, subModePayload); } else if (protocol < 503) { - player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, chunkBlobCache, - payloads.get(StaticVersion.fromProtocol(protocol, player.isNetEaseClient())), subModePayload); + if (data == null) { + continue; + } + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, chunkBlobCache, data, subModePayload); } else { - player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, chunkBlobCache, - payloads.get(StaticVersion.fromProtocol(protocol, player.isNetEaseClient())), subModePayloadNew); + if (data == null) { + continue; + } + player.sendChunk(x, z, subChunkCount + PADDING_SUB_CHUNK_COUNT, chunkBlobCache, data, subModePayloadNew); } } } - - this.chunkSendQueue.remove(index); - this.chunkSendTasks.remove(index); } Int2ObjectMap loaders = this.subChunkSendQueue.remove(index); @@ -3568,6 +3691,17 @@ public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount if (!player.isConnected()) { continue; } + + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion == null) { + continue; + } + + if (!payloads.containsKey(blockVersion)) { + requestSubChunks(x, z, player); + continue; + } + player.sendSubChunks(this.getDimension().ordinal(), x, z, subChunkCount, chunkBlobCache, subChunkPayloads, heightMapType, heightMapData); } } @@ -3583,6 +3717,8 @@ public void removeEntity(Entity entity) { if (entity instanceof Player) { this.players.remove(entity.getId()); this.checkSleep(); + + this.onPlayerRemove((Player) entity); } else { entity.close(); } @@ -3599,6 +3735,8 @@ public void removeEntityDirect(Entity entity) { if (entity instanceof Player) { this.players.remove(entity.getId()); this.checkSleep(); + + this.onPlayerRemove((Player) entity); } this.entities.remove(entity.getId()); @@ -4823,4 +4961,28 @@ public Long2IntMap getChunkTickList() { public Long2LongMap getChunkUnloadQueue() { return unloadQueue; } + + public void onPlayerAdd(Player player) { + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion != null) { + requestChunkVersions.computeIfAbsent(blockVersion, key -> new LongOpenHashSet()).add(player.getId()); + } + } + + public void onPlayerRemove(Player player) { + StaticVersion blockVersion = player.getBlockVersion(); + if (blockVersion != null) { + LongSet players = requestChunkVersions.get(blockVersion); + if (players != null) { + players.remove(player.getId()); + if (players.isEmpty()) { + requestChunkVersions.remove(blockVersion); + } + } + } + } + + public Map getRequestChunkVersions() { + return requestChunkVersions; + } } diff --git a/src/main/java/cn/nukkit/level/format/generic/ChunkPacketCache.java b/src/main/java/cn/nukkit/level/format/generic/ChunkPacketCache.java index 79f39e51933..6a8b1552352 100644 --- a/src/main/java/cn/nukkit/level/format/generic/ChunkPacketCache.java +++ b/src/main/java/cn/nukkit/level/format/generic/ChunkPacketCache.java @@ -4,7 +4,9 @@ import cn.nukkit.network.protocol.BatchPacket; import cn.nukkit.network.protocol.SubChunkPacket; +import javax.annotation.Nullable; import java.util.Map; +import java.util.Set; public class ChunkPacketCache { @@ -23,7 +25,9 @@ public class ChunkPacketCache { private final BatchPacket packet; private final BatchPacket packetOld; - public ChunkPacketCache(Map packets, Map subChunkPackets, Map subChunkPacketsUncompressed, BatchPacket subModePacketNew, BatchPacket subModePacket, BatchPacket subModePacketTruncatedNew, BatchPacket subModePacketTruncated, BatchPacket packet116, BatchPacket packet, BatchPacket packetOld) { + private final Set requestedVersions; + + public ChunkPacketCache(Map packets, Map subChunkPackets, Map subChunkPacketsUncompressed, BatchPacket subModePacketNew, BatchPacket subModePacket, BatchPacket subModePacketTruncatedNew, BatchPacket subModePacketTruncated, BatchPacket packet116, BatchPacket packet, BatchPacket packetOld, Set requestedVersions) { this.packets = packets; this.subChunkPackets = subChunkPackets; this.subChunkPacketsUncompressed = subChunkPacketsUncompressed; @@ -34,16 +38,20 @@ public ChunkPacketCache(Map packets, Map { private static final byte[] EMPTY = new byte[0]; @@ -92,6 +94,7 @@ public class ChunkRequestTask extends AsyncTask { int x; int z; Level level; + Set requestedVersions; boolean success; @@ -116,6 +119,8 @@ public ChunkRequestTask(Chunk chunk) { x = chunk.getX(); z = chunk.getZ(); level = chunk.getProvider().getLevel(); + Set requestedVersions = level.getRequestChunkVersions().keySet(); + this.requestedVersions = requestedVersions.isEmpty() ? Collections.emptySet() : EnumSet.copyOf(requestedVersions); timestamp = chunk.getChanges(); } @@ -320,6 +325,10 @@ public void onRun() { continue; } + if (!requestedVersions.contains(version)) { + continue; + } + byte[][] blockStorages = new byte[extendedCount][]; payloads.put(version, encodeChunk(chunk, sections, count, blockStorages, biomePalettesNew, biomePalettes, fullChunkBlockEntities, version)); @@ -401,8 +410,8 @@ public void onRun() { Level.getChunkCacheFromData(x, z, LevelChunkPacket.CLIENT_REQUEST_TRUNCATED_COLUMN_FAKE_COUNT, extendedCount, subModePayload, false, true), Level.getChunkCacheFromData(x, z, count, payload, false, true), Level.getChunkCacheFromData(x, z, count, payload, false, false), - Level.getChunkCacheFromData(x, z, count, payloadOld, true, false) - ); + Level.getChunkCacheFromData(x, z, count, payloadOld, true, false), + requestedVersions); } success = true; } catch (Exception e) { @@ -415,7 +424,7 @@ public void onCompletion(Server server) { if (!success) { return; } - level.chunkRequestCallback(timestamp, x, z, count, chunkBlobCache, chunkPacketCache, payload, payloadOld, subModePayload, subModePayloadNew, payloads, subChunkPayloads, heightmapType, heightmapData, emptySection); + level.chunkRequestCallback(timestamp, x, z, count, chunkBlobCache, chunkPacketCache, payload, payloadOld, subModePayload, subModePayloadNew, payloads, subChunkPayloads, heightmapType, heightmapData, emptySection, requestedVersions); } private static byte[] encodeChunk(boolean isOld, Chunk chunk, ChunkSection[] sections, int count, byte[] blockEntities) { diff --git a/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java b/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java index d30292b9561..0e0569eeabd 100644 --- a/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java +++ b/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java @@ -173,7 +173,7 @@ public AsyncTask requestChunkTask(int x, int z) throws ChunkException { stream.put(tiles); byte[] payload = stream.getBuffer(); - this.getLevel().chunkRequestCallback(timestamp, x, z, 16, null, null, payload, payload, payload, payload, Collections.emptyMap(), Collections.emptyMap(), null, null, null); + this.getLevel().chunkRequestCallback(timestamp, x, z, 16, null, null, payload, payload, payload, payload, Collections.emptyMap(), Collections.emptyMap(), null, null, null, Collections.emptySet()); return null; }