From 7395588b8f48296bd8cde928624420138e71b80c Mon Sep 17 00:00:00 2001 From: Woder <17339354+wode490390@users.noreply.github.com> Date: Mon, 25 Nov 2024 20:15:28 +0800 Subject: [PATCH] Batch block updates --- src/main/java/cn/nukkit/Player.java | 6 +- src/main/java/cn/nukkit/level/Level.java | 59 +++++++++++++++---- .../protocol/UpdateSubChunkBlocksPacket.java | 48 +++++++++++++++ .../protocol/types/BlockChangeEntry.java | 26 ++++++++ 4 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 src/main/java/cn/nukkit/network/protocol/UpdateSubChunkBlocksPacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/types/BlockChangeEntry.java diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index e7b22129d65..8df1159cc1f 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -6237,11 +6237,9 @@ public boolean canBreathe() { } /** - * 1.18.0 后使用 UpdateSubChunkBlocksPacket 代替 UpdateBlockPacket 和 UpdateBlockSyncedPacket. - * WIP + * @since 1.18.0 */ - public void updateBlock(Block entry) { - //TODO: queue + public void updateSubChunkBlocks(int subChunkBlockX, int subChunkBlockY, int subChunkBlockZ, BlockChangeEntry[] layer0, BlockChangeEntry[] layer1) { } public void sendAdventureSettingsAndAbilities(Player player, AdventureSettings settings) { diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 15385014b79..4d30ee105ec 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -57,6 +57,7 @@ import cn.nukkit.network.Network; import cn.nukkit.network.protocol.*; import cn.nukkit.network.protocol.BatchPacket.Track; +import cn.nukkit.network.protocol.types.BlockChangeEntry; import cn.nukkit.plugin.Plugin; import cn.nukkit.scheduler.AsyncTask; import cn.nukkit.scheduler.BlockUpdateScheduler; @@ -270,7 +271,7 @@ public class Level implements ChunkManager, Metadatable { private final long[] lastChunkPos = new long[CHUNK_CACHE_SIZE]; private final BaseFullChunk[] lastChunk = new BaseFullChunk[CHUNK_CACHE_SIZE]; - private final Long2ObjectMap changedBlocks = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap> changedBlocks = new Long2ObjectOpenHashMap<>(); private final Lock changeBlockLock = new ReentrantLock(); private final BlockUpdateScheduler updateQueue; @@ -1303,19 +1304,49 @@ private void sendChangedBlocks() { return; } - Iterator> iter = changedBlocks.long2ObjectEntrySet().iterator(); + Iterator>> iter = changedBlocks.long2ObjectEntrySet().iterator(); while (iter.hasNext()) { - Long2ObjectMap.Entry entry = iter.next(); + Long2ObjectMap.Entry> entry = iter.next(); long chunkIndex = entry.getLongKey(); - IntSet localPositions = entry.getValue(); + int chunkX = Level.getHashX(chunkIndex); + int chunkZ = Level.getHashZ(chunkIndex); + Collection players = getChunkPlayers(chunkX, chunkZ).values(); - Vector3[] blocks = new Vector3[localPositions.size()]; - int i = 0; - IntIterator iterator = localPositions.iterator(); - while (iterator.hasNext()) { - blocks[i++] = getBlockXYZ(chunkIndex, iterator.nextInt()); + if (players.isEmpty()) { + iter.remove(); + continue; + } + + int chunkBlockX = chunkX << 4; + int chunkBlockZ = chunkZ << 4; + Int2ObjectMap subChunks = entry.getValue(); + for (Int2ObjectMap.Entry ent : subChunks.int2ObjectEntrySet()) { + int chunkBlockY = ent.getIntKey() << 4; + IntSet localPositions = ent.getValue(); + int count = localPositions.size(); + BlockChangeEntry[] layer0 = new BlockChangeEntry[count]; + BlockChangeEntry[] layer1 = new BlockChangeEntry[count]; + int index = 0; + for (int localPos : localPositions) { + int x = chunkBlockX | (localPos >> 8); + int y = chunkBlockY | (localPos & 0xf); + int z = chunkBlockZ | ((localPos >> 4) & 0xf); + layer0[index] = BlockChangeEntry.create(x, y, z, getFullBlock(0, x, y, z), UpdateBlockPacket.FLAG_ALL_PRIORITY); + layer1[index] = BlockChangeEntry.create(x, y, z, getFullBlock(1, x, y, z), UpdateBlockPacket.FLAG_ALL_PRIORITY); + index++; + } + + UpdateSubChunkBlocksPacket packet = new UpdateSubChunkBlocksPacket(); + packet.subChunkBlockX = chunkBlockX; + packet.subChunkBlockY = chunkBlockY; + packet.subChunkBlockZ = chunkBlockZ; + packet.layer0 = layer0; + packet.layer1 = layer1; + for (Player player : players) { +// player.updateSubChunkBlocks(chunkBlockX, chunkBlockY, chunkBlockZ, layer0, layer1); + player.dataPacket(packet); + } } - sendBlocks(getChunkPlayers(Level.getHashX(chunkIndex), Level.getHashZ(chunkIndex)).values().toArray(new Player[0]), blocks, UpdateBlockPacket.FLAG_ALL_PRIORITY); iter.remove(); } @@ -2601,10 +2632,14 @@ private void addBlockChange(int x, int y, int z) { } private void addBlockChange(long index, int x, int y, int z) { + int chunkY = y >> 4; + int localIndex = ((x & 0xf) << 8) | ((z & 0xf) << 4) | (y & 0xf); + changeBlockLock.lock(); try { - changedBlocks.computeIfAbsent(index, key -> new IntOpenHashSet()) - .add(Level.localBlockHash(x, y, z)); + changedBlocks.computeIfAbsent(index, key -> new Int2ObjectOpenHashMap<>()) + .computeIfAbsent(chunkY, key -> new IntOpenHashSet()) + .add(localIndex); } finally { changeBlockLock.unlock(); } diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateSubChunkBlocksPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateSubChunkBlocksPacket.java new file mode 100644 index 00000000000..7344f1a8167 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/UpdateSubChunkBlocksPacket.java @@ -0,0 +1,48 @@ +package cn.nukkit.network.protocol; + +import cn.nukkit.network.protocol.types.BlockChangeEntry; +import lombok.ToString; + +@ToString +public class UpdateSubChunkBlocksPacket extends DataPacket { + public static final int NETWORK_ID = ProtocolInfo.UPDATE_SUB_CHUNK_BLOCKS_PACKET; + + public int subChunkBlockX; + public int subChunkBlockY; + public int subChunkBlockZ; + public BlockChangeEntry[] layer0 = new BlockChangeEntry[0]; + public BlockChangeEntry[] layer1 = new BlockChangeEntry[0]; + + @Override + public int pid() { + return NETWORK_ID; + } + + @Override + public void decode() { + } + + @Override + public void encode() { + this.reset(); + this.putBlockVector3(this.subChunkBlockX, this.subChunkBlockY, this.subChunkBlockZ); + + this.putUnsignedVarInt(this.layer0.length); + for (BlockChangeEntry entry : this.layer0) { + writeEntry(entry); + } + + this.putUnsignedVarInt(this.layer1.length); + for (BlockChangeEntry entry : this.layer1) { + writeEntry(entry); + } + } + + private void writeEntry(BlockChangeEntry entry) { + this.putBlockVector3(entry.x(), entry.y(), entry.z()); + this.putUnsignedVarInt(entry.block()); + this.putUnsignedVarInt(entry.flags()); + this.putUnsignedVarLong(entry.syncedUpdateEntityUniqueId()); + this.putUnsignedVarInt(entry.syncedUpdateType()); + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/types/BlockChangeEntry.java b/src/main/java/cn/nukkit/network/protocol/types/BlockChangeEntry.java new file mode 100644 index 00000000000..086c3ef325c --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/types/BlockChangeEntry.java @@ -0,0 +1,26 @@ +package cn.nukkit.network.protocol.types; + +import cn.nukkit.network.protocol.UpdateBlockPacket; + +public record BlockChangeEntry( + int x, + int y, + int z, + int block, + int flags, + // These two fields are useless 99.99% of the time; they are here to allow this packet to provide UpdateBlockSyncedPacket functionality. + long syncedUpdateEntityUniqueId, + int syncedUpdateType +) { + public static BlockChangeEntry create(int x, int y, int z, int block) { + return create(x, y, z, block, UpdateBlockPacket.FLAG_ALL_PRIORITY); + } + + public static BlockChangeEntry create(int x, int y, int z, int block, int flags) { + return create(x, y, z, block, flags,-1, 0); + } + + public static BlockChangeEntry create(int x, int y, int z, int block, int flags, long syncedUpdateEntityUniqueId, int syncedUpdateType) { + return new BlockChangeEntry(x, y, z, block, flags, syncedUpdateEntityUniqueId, syncedUpdateType); + } +}