From caa89dd2f8a4bf0db702aafde79914c301e7d38e Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Sun, 16 Aug 2020 00:53:42 +0200 Subject: [PATCH] Chunk update --- .../net/minestom/server/entity/Player.java | 11 +- .../net/minestom/server/instance/Chunk.java | 180 +++++++++-- .../server/instance/DynamicChunk.java | 301 +++++++++--------- .../minestom/server/instance/Instance.java | 63 ---- .../server/instance/InstanceContainer.java | 82 +---- .../server/instance/SharedInstance.java | 15 - .../minestom/server/instance/StaticChunk.java | 114 +++---- .../server/instance/batch/BlockBatch.java | 5 +- .../server/instance/batch/ChunkBatch.java | 5 +- .../server/instance/block/BlockProvider.java | 6 + .../listener/BlockPlacementListener.java | 2 +- 11 files changed, 388 insertions(+), 396 deletions(-) create mode 100644 src/main/java/net/minestom/server/instance/block/BlockProvider.java diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 67279f809..5120a72f7 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -39,7 +39,10 @@ import net.minestom.server.scoreboard.Team; import net.minestom.server.sound.Sound; import net.minestom.server.sound.SoundCategory; import net.minestom.server.stat.PlayerStatistic; -import net.minestom.server.utils.*; +import net.minestom.server.utils.ArrayUtils; +import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.Position; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.validate.Check; import net.minestom.server.world.DimensionType; @@ -540,7 +543,7 @@ public class Player extends LivingEntity implements CommandSender { viewableChunks.clear(); if (this.instance != null) { - DimensionType instanceDimensionType = instance.getDimensionType(); + final DimensionType instanceDimensionType = instance.getDimensionType(); if (dimensionType != instanceDimensionType) sendDimension(instanceDimensionType); } @@ -557,7 +560,6 @@ public class Player extends LivingEntity implements CommandSender { if (chunk != null) { viewableChunks.add(chunk); chunk.addViewer(this); - instance.sendChunk(this, chunk); if (chunk.getChunkX() == Math.floorDiv((int) getPosition().getX(), 16) && chunk.getChunkZ() == Math.floorDiv((int) getPosition().getZ(), 16)) updateViewPosition(chunk); } @@ -1196,7 +1198,7 @@ public class Player extends LivingEntity implements CommandSender { * It does remove and add the player from the chunks viewers list when removed or added * It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent} * - * @param newChunk the current/new player chunk + * @param newChunk the current/new player chunk */ protected void onChunkChange(Chunk newChunk) { final long[] lastVisibleChunks = new long[viewableChunks.size()]; @@ -1236,7 +1238,6 @@ public class Player extends LivingEntity implements CommandSender { } this.viewableChunks.add(chunk); chunk.addViewer(this); - instance.sendChunk(this, chunk); }); } } diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 5781ec218..afbf3e5de 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -15,18 +15,21 @@ import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.instance.block.UpdateConsumer; import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.ChunkDataPacket; +import net.minestom.server.network.packet.server.play.UpdateLightPacket; +import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.chunk.ChunkUtils; +import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.time.CooldownUtils; import net.minestom.server.utils.time.UpdateOption; import net.minestom.server.utils.validate.Check; import net.minestom.server.world.biomes.Biome; import java.io.IOException; -import java.util.Collections; -import java.util.Set; +import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; // TODO light data & API public abstract class Chunk implements Viewable { @@ -56,7 +59,7 @@ public abstract class Chunk implements Viewable { // (block index)/(last update in ms) protected Int2LongMap updatableBlocksLastUpdate = new Int2LongOpenHashMap(); - protected volatile boolean packetUpdated; + protected AtomicBoolean packetUpdated = new AtomicBoolean(false); // Block entities protected Set blockEntities = new CopyOnWriteArraySet<>(); @@ -176,10 +179,22 @@ public abstract class Chunk implements Viewable { return chunkZ; } + /** + * Get the cached data packet + *

+ * Use {@link #retrieveDataBuffer(Consumer)} to be sure to get the updated version + * + * @return the current cached data packet, can be null or outdated + */ public ByteBuf getFullDataPacket() { return fullDataPacket; } + public void setFullDataPacket(ByteBuf fullDataPacket) { + this.fullDataPacket = fullDataPacket; + this.packetUpdated.set(true); + } + protected boolean isBlockEntity(short blockStateId) { final Block block = Block.fromStateId(blockStateId); return block.hasBlockEntity(); @@ -189,17 +204,6 @@ public abstract class Chunk implements Viewable { return blockEntities; } - /** - * Get the columnar space linked to this chunk - *

- * Used internally by the pathfinder - * - * @return this chunk columnar space - */ - public PFColumnarSpace getColumnarSpace() { - return columnarSpace; - } - /** * Change this chunk columnar space * @@ -209,19 +213,47 @@ public abstract class Chunk implements Viewable { this.columnarSpace = columnarSpace; } - public void setFullDataPacket(ByteBuf fullDataPacket) { - this.fullDataPacket = fullDataPacket; - this.packetUpdated = true; + /** + * Retrieve the updated data packet + * + * @param consumer the consumer called once the packet is sure to be up-to-date + */ + public void retrieveDataBuffer(Consumer consumer) { + final ByteBuf data = getFullDataPacket(); + if (data == null || !packetUpdated.get()) { + PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), packet -> { + setFullDataPacket(packet); + consumer.accept(packet); + }); + } else { + consumer.accept(data); + } } + /** + * Serialize the chunk + * + * @return the serialized chunk + * @throws IOException + */ protected abstract byte[] getSerializedData() throws IOException; + /** + * Get a {@link ChunkDataPacket} which should contain the full chunk + * + * @return a fresh full chunk data packet + */ public ChunkDataPacket getFreshFullDataPacket() { ChunkDataPacket fullDataPacket = getFreshPacket(); fullDataPacket.fullChunk = true; return fullDataPacket; } + /** + * Get a {@link ChunkDataPacket} which should contain the non-full chunk + * + * @return a fresh non-full chunk data packet + */ public ChunkDataPacket getFreshPartialDataPacket() { ChunkDataPacket fullDataPacket = getFreshPacket(); fullDataPacket.fullChunk = false; @@ -233,20 +265,6 @@ public abstract class Chunk implements Viewable { */ protected abstract ChunkDataPacket getFreshPacket(); - // Write the packet in the current thread - public void refreshDataPacket() { - final ByteBuf buffer = PacketUtils.writePacket(getFreshFullDataPacket()); - setFullDataPacket(buffer); - } - - // Write the packet in the writer thread pools - public void refreshDataPacket(Runnable runnable) { - PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), buf -> { - setFullDataPacket(buf); - runnable.run(); - }); - } - /** * Used to verify if the chunk should still be kept in memory * @@ -266,6 +284,9 @@ public abstract class Chunk implements Viewable { public boolean addViewer(Player player) { final boolean result = this.viewers.add(player); + // Send the chunk data & light packets to the player + sendChunk(player); + PlayerChunkLoadEvent playerChunkLoadEvent = new PlayerChunkLoadEvent(player, chunkX, chunkZ); player.callEvent(PlayerChunkLoadEvent.class, playerChunkLoadEvent); return result; @@ -286,6 +307,101 @@ public abstract class Chunk implements Viewable { return Collections.unmodifiableSet(viewers); } + /** + * Send the chunk data to {@code player} + * + * @param player the player + */ + protected void sendChunk(Player player) { + if (!isLoaded()) + return; + if (!PlayerUtils.isNettyClient(player)) + return; + + final PlayerConnection playerConnection = player.getPlayerConnection(); + + retrieveDataBuffer(buf -> { + playerConnection.sendPacket(buf, true); + }); + + // TODO do not hardcode + if (MinecraftServer.isFixLighting()) { + UpdateLightPacket updateLightPacket = new UpdateLightPacket(); + updateLightPacket.chunkX = getChunkX(); + updateLightPacket.chunkZ = getChunkZ(); + updateLightPacket.skyLightMask = 0x3FFF0; + updateLightPacket.blockLightMask = 0x3F; + updateLightPacket.emptySkyLightMask = 0x0F; + updateLightPacket.emptyBlockLightMask = 0x3FFC0; + byte[] bytes = new byte[2048]; + Arrays.fill(bytes, (byte) 0xFF); + List temp = new ArrayList<>(); + List temp2 = new ArrayList<>(); + for (int i = 0; i < 14; ++i) { + temp.add(bytes); + } + for (int i = 0; i < 6; ++i) { + temp2.add(bytes); + } + updateLightPacket.skyLight = temp; + updateLightPacket.blockLight = temp2; + PacketWriterUtils.writeAndSend(player, updateLightPacket); + } + } + + /** + * Send a full {@link ChunkDataPacket} to {@code player} + * + * @param player the player to update the chunk to + */ + public void sendChunkUpdate(Player player) { + retrieveDataBuffer(buf -> { + final PlayerConnection playerConnection = player.getPlayerConnection(); + playerConnection.sendPacket(buf, true); + }); + } + + /** + * Send a full {@link ChunkDataPacket} to all chunk viewers + */ + public void sendChunkUpdate() { + final Set chunkViewers = getViewers(); + if (!chunkViewers.isEmpty()) { + retrieveDataBuffer(buf -> { + chunkViewers.forEach(player -> { + final PlayerConnection playerConnection = player.getPlayerConnection(); + if (!PlayerUtils.isNettyClient(playerConnection)) + return; + + playerConnection.sendPacket(buf, true); + }); + }); + + } + } + + /** + * Send a chunk section update packet to {@code player} + * + * @param section the section to update + * @param player the player to send the packet to + */ + public void sendChunkSectionUpdate(int section, Player player) { + if (!PlayerUtils.isNettyClient(player)) + return; + + PacketWriterUtils.writeAndSend(player, getChunkSectionUpdatePacket(section)); + } + + protected ChunkDataPacket getChunkSectionUpdatePacket(int section) { + ChunkDataPacket chunkDataPacket = getFreshPartialDataPacket(); + chunkDataPacket.fullChunk = false; + int[] sections = new int[16]; + sections[section] = 1; + chunkDataPacket.sections = sections; + return chunkDataPacket; + } + /** * Set the chunk as "unloaded" */ diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index cc7373075..112741413 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -19,188 +19,189 @@ import java.util.concurrent.CopyOnWriteArraySet; public class DynamicChunk extends Chunk { - public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; - private short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; + public short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; + private short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; - public DynamicChunk(Biome[] biomes, int chunkX, int chunkZ) { - super(biomes, chunkX, chunkZ); - } + public DynamicChunk(Biome[] biomes, int chunkX, int chunkZ) { + super(biomes, chunkX, chunkZ); + } - @Override - public void UNSAFE_removeCustomBlock(int x, int y, int z) { - final int index = getBlockIndex(x, y, z); - this.customBlocksId[index] = 0; // Set to none - this.blocksData.remove(index); + @Override + public void UNSAFE_removeCustomBlock(int x, int y, int z) { + final int index = getBlockIndex(x, y, z); + this.customBlocksId[index] = 0; // Set to none + this.blocksData.remove(index); - this.updatableBlocks.remove(index); - this.updatableBlocksLastUpdate.remove(index); + this.updatableBlocks.remove(index); + this.updatableBlocksLastUpdate.remove(index); - this.blockEntities.remove(index); - } + this.blockEntities.remove(index); + } - @Override - protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) { - final int index = getBlockIndex(x, y, z); - if (blockStateId != 0 - || (blockStateId == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found - this.blocksStateId[index] = blockStateId; - this.customBlocksId[index] = customId; - } else { - // Block has been deleted, clear cache and return + @Override + protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) { + final int index = getBlockIndex(x, y, z); + if (blockStateId != 0 + || (blockStateId == 0 && customId != 0 && updateConsumer != null)) { // Allow custom air block for update purpose, refused if no update consumer has been found + this.blocksStateId[index] = blockStateId; + this.customBlocksId[index] = customId; + } else { + // Block has been deleted, clear cache and return - this.blocksStateId[index] = 0; // Set to air + this.blocksStateId[index] = 0; // Set to air - this.blocksData.remove(index); + this.blocksData.remove(index); - this.updatableBlocks.remove(index); - this.updatableBlocksLastUpdate.remove(index); + this.updatableBlocks.remove(index); + this.updatableBlocksLastUpdate.remove(index); - this.blockEntities.remove(index); + this.blockEntities.remove(index); - this.packetUpdated = false; - return; - } + this.packetUpdated.set(false); + return; + } - // Set the new data (or remove from the map if is null) - if (data != null) { - this.blocksData.put(index, data); - } else { - this.blocksData.remove(index); - } + // Set the new data (or remove from the map if is null) + if (data != null) { + this.blocksData.put(index, data); + } else { + this.blocksData.remove(index); + } - // Set update consumer - if (updateConsumer != null) { - this.updatableBlocks.add(index); - this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis()); - } else { - this.updatableBlocks.remove(index); - this.updatableBlocksLastUpdate.remove(index); - } + // Set update consumer + if (updateConsumer != null) { + this.updatableBlocks.add(index); + this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis()); + } else { + this.updatableBlocks.remove(index); + this.updatableBlocksLastUpdate.remove(index); + } - if (isBlockEntity(blockStateId)) { - this.blockEntities.add(index); - } else { - this.blockEntities.remove(index); - } + // Set block entity + if (isBlockEntity(blockStateId)) { + this.blockEntities.add(index); + } else { + this.blockEntities.remove(index); + } - this.packetUpdated = false; + this.packetUpdated.set(false); - if (columnarSpace != null) { - final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields(); - final PFBlockDescription blockDescription = new PFBlockDescription(Block.fromStateId(blockStateId)); - columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0); - } - } + if (columnarSpace != null) { + final ColumnarOcclusionFieldList columnarOcclusionFieldList = columnarSpace.occlusionFields(); + final PFBlockDescription blockDescription = new PFBlockDescription(Block.fromStateId(blockStateId)); + columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0); + } + } - @Override - public short getBlockStateId(int x, int y, int z) { - final int index = getBlockIndex(x, y, z); - if (!MathUtils.isBetween(index, 0, blocksStateId.length)) { - return 0; // TODO: custom invalid block - } - final short id = blocksStateId[index]; - return id; - } + @Override + public short getBlockStateId(int x, int y, int z) { + final int index = getBlockIndex(x, y, z); + if (!MathUtils.isBetween(index, 0, blocksStateId.length)) { + return 0; // TODO: custom invalid block + } + final short id = blocksStateId[index]; + return id; + } - @Override - public short getCustomBlockId(int x, int y, int z) { - final int index = getBlockIndex(x, y, z); - if (!MathUtils.isBetween(index, 0, blocksStateId.length)) { - return 0; // TODO: custom invalid block - } - final short id = customBlocksId[index]; - return id; - } + @Override + public short getCustomBlockId(int x, int y, int z) { + final int index = getBlockIndex(x, y, z); + if (!MathUtils.isBetween(index, 0, blocksStateId.length)) { + return 0; // TODO: custom invalid block + } + final short id = customBlocksId[index]; + return id; + } - @Override - public CustomBlock getCustomBlock(int x, int y, int z) { - final int index = getBlockIndex(x, y, z); - if (!MathUtils.isBetween(index, 0, blocksStateId.length)) { - return null; // TODO: custom invalid block - } - final short id = customBlocksId[index]; - return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null; - } + @Override + public CustomBlock getCustomBlock(int x, int y, int z) { + final int index = getBlockIndex(x, y, z); + if (!MathUtils.isBetween(index, 0, blocksStateId.length)) { + return null; // TODO: custom invalid block + } + final short id = customBlocksId[index]; + return id != 0 ? BLOCK_MANAGER.getCustomBlock(id) : null; + } - @Override - protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) { - final int blockIndex = getBlockIndex(x, y, z); - if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) { - return; - } + @Override + protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) { + final int blockIndex = getBlockIndex(x, y, z); + if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) { + return; + } - this.blocksStateId[blockIndex] = blockStateId; - this.customBlocksId[blockIndex] = customId; - } + this.blocksStateId[blockIndex] = blockStateId; + this.customBlocksId[blockIndex] = customId; + } - @Override - protected void refreshBlockStateId(int x, int y, int z, short blockStateId) { - final int blockIndex = getBlockIndex(x, y, z); - if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) { - return; - } + @Override + protected void refreshBlockStateId(int x, int y, int z, short blockStateId) { + final int blockIndex = getBlockIndex(x, y, z); + if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) { + return; + } - this.blocksStateId[blockIndex] = blockStateId; - } + this.blocksStateId[blockIndex] = blockStateId; + } - @Override - protected byte[] getSerializedData() throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(output); + @Override + protected byte[] getSerializedData() throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(output); - for (int i = 0; i < BIOME_COUNT; i++) { - dos.writeByte(biomes[i].getId()); - } + for (int i = 0; i < BIOME_COUNT; i++) { + dos.writeByte(biomes[i].getId()); + } - for (byte x = 0; x < CHUNK_SIZE_X; x++) { - for (short y = 0; y < CHUNK_SIZE_Y; y++) { - for (byte z = 0; z < CHUNK_SIZE_Z; z++) { - final int index = getBlockIndex(x, y, z); + for (byte x = 0; x < CHUNK_SIZE_X; x++) { + for (short y = 0; y < CHUNK_SIZE_Y; y++) { + for (byte z = 0; z < CHUNK_SIZE_Z; z++) { + final int index = getBlockIndex(x, y, z); - final short blockStateId = blocksStateId[index]; - final short customBlockId = customBlocksId[index]; + final short blockStateId = blocksStateId[index]; + final short customBlockId = customBlocksId[index]; - if (blockStateId == 0 && customBlockId == 0) - continue; + if (blockStateId == 0 && customBlockId == 0) + continue; - final Data data = blocksData.get(index); + final Data data = blocksData.get(index); - // Chunk coordinates - dos.writeInt(x); - dos.writeInt(y); - dos.writeInt(z); + // Chunk coordinates + dos.writeInt(x); + dos.writeInt(y); + dos.writeInt(z); - // Id - dos.writeShort(blockStateId); - dos.writeShort(customBlockId); + // Id + dos.writeShort(blockStateId); + dos.writeShort(customBlockId); - // Data - final boolean hasData = (data != null && (data instanceof SerializableData)); - dos.writeBoolean(hasData); - if (hasData) { - final byte[] d = ((SerializableData) data).getSerializedData(); - dos.writeInt(d.length); - dos.write(d); - } - } - } - } + // Data + final boolean hasData = (data != null && (data instanceof SerializableData)); + dos.writeBoolean(hasData); + if (hasData) { + final byte[] d = ((SerializableData) data).getSerializedData(); + dos.writeInt(d.length); + dos.write(d); + } + } + } + } - final byte[] result = output.toByteArray(); - return result; - } + final byte[] result = output.toByteArray(); + return result; + } - @Override - protected ChunkDataPacket getFreshPacket() { - ChunkDataPacket fullDataPacket = new ChunkDataPacket(); - fullDataPacket.biomes = biomes.clone(); - fullDataPacket.chunkX = chunkX; - fullDataPacket.chunkZ = chunkZ; - fullDataPacket.blocksStateId = blocksStateId.clone(); - fullDataPacket.customBlocksId = customBlocksId.clone(); - fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities); - fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData); - return fullDataPacket; - } + @Override + protected ChunkDataPacket getFreshPacket() { + ChunkDataPacket fullDataPacket = new ChunkDataPacket(); + fullDataPacket.biomes = biomes.clone(); + fullDataPacket.chunkX = chunkX; + fullDataPacket.chunkZ = chunkZ; + fullDataPacket.blocksStateId = blocksStateId.clone(); + fullDataPacket.customBlocksId = customBlocksId.clone(); + fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities); + fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData); + return fullDataPacket; + } } diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index a86c8274d..e62e49f61 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -1,6 +1,5 @@ package net.minestom.server.instance; -import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; import net.minestom.server.UpdateManager; import net.minestom.server.data.Data; @@ -19,13 +18,11 @@ import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.BlockActionPacket; -import net.minestom.server.network.packet.server.play.ChunkDataPacket; import net.minestom.server.network.packet.server.play.TimeUpdatePacket; import net.minestom.server.storage.StorageFolder; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; import net.minestom.server.utils.chunk.ChunkUtils; -import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.time.CooldownUtils; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.UpdateOption; @@ -227,21 +224,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta protected abstract void createChunk(int chunkX, int chunkZ, Consumer callback); - /** - * Send all chunks data to {@code player} - * - * @param player the player - */ - public abstract void sendChunks(Player player); - - /** - * Send a specific chunk data to {@code player} - * - * @param player the player - * @param chunk the chunk - */ - public abstract void sendChunk(Player player, Chunk chunk); - /** * When set to true, chunks will load with players moving closer * Otherwise using {@link #loadChunk(int, int)} will be required to even spawn a player @@ -264,50 +246,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta */ public abstract boolean isInVoid(Position position); - // - - /** - * Send a full {@link ChunkDataPacket} to {@code player} - * - * @param player the player to update the chunk to - * @param chunk the chunk to send - */ - public void sendChunkUpdate(Player player, Chunk chunk) { - player.getPlayerConnection().sendPacket(chunk.getFullDataPacket(), true); - } - - protected void sendChunkUpdate(Collection players, Chunk chunk) { - final ByteBuf chunkData = chunk.getFullDataPacket(); - players.forEach(player -> { - if (!PlayerUtils.isNettyClient(player)) - return; - - player.getPlayerConnection().sendPacket(chunkData, true); - }); - } - - protected void sendChunkSectionUpdate(Chunk chunk, int section, Collection players) { - PacketWriterUtils.writeAndSend(players, getChunkSectionUpdatePacket(chunk, section)); - } - - public void sendChunkSectionUpdate(Chunk chunk, int section, Player player) { - if (!PlayerUtils.isNettyClient(player)) - return; - - PacketWriterUtils.writeAndSend(player, getChunkSectionUpdatePacket(chunk, section)); - } - - protected ChunkDataPacket getChunkSectionUpdatePacket(Chunk chunk, int section) { - ChunkDataPacket chunkDataPacket = chunk.getFreshPartialDataPacket(); - chunkDataPacket.fullChunk = false; - int[] sections = new int[16]; - sections[section] = 1; - chunkDataPacket.sections = sections; - return chunkDataPacket; - } - // - - /** * Get if the instance has been registered in {@link InstanceManager} * @@ -783,7 +721,6 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta if (isPlayer) { final Player player = (Player) entity; - sendChunks(player); getWorldBorder().init(player); } diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index a13c6a0e3..c36208308 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -1,6 +1,5 @@ package net.minestom.server.instance; -import io.netty.buffer.ByteBuf; import lombok.Setter; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; @@ -12,20 +11,18 @@ import net.minestom.server.event.player.PlayerBlockBreakEvent; import net.minestom.server.instance.batch.BlockBatch; import net.minestom.server.instance.batch.ChunkBatch; import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.block.BlockProvider; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.instance.block.rule.BlockPlacementRule; -import net.minestom.server.network.PacketWriterUtils; import net.minestom.server.network.packet.server.play.BlockChangePacket; import net.minestom.server.network.packet.server.play.ParticlePacket; import net.minestom.server.network.packet.server.play.UnloadChunkPacket; -import net.minestom.server.network.packet.server.play.UpdateLightPacket; import net.minestom.server.particle.Particle; import net.minestom.server.particle.ParticleCreator; import net.minestom.server.storage.StorageFolder; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; import net.minestom.server.utils.chunk.ChunkUtils; -import net.minestom.server.utils.player.PlayerUtils; import net.minestom.server.utils.thread.MinestomThread; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.validate.Check; @@ -41,7 +38,6 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Function; /** * InstanceContainer is an instance that contains chunks in contrary to SharedInstance. @@ -66,7 +62,7 @@ public class InstanceContainer extends Instance { private boolean autoChunkLoad; @Setter - private BiFunction> chunkDecider; + private BiFunction chunkDecider; public InstanceContainer(UUID uniqueId, DimensionType dimensionType, StorageFolder storageFolder) { super(uniqueId, dimensionType); @@ -256,7 +252,7 @@ public class InstanceContainer extends Instance { // The player probably have a wrong version of this chunk section, send it if (blockStateId == 0) { - sendChunkSectionUpdate(chunk, ChunkUtils.getSectionAt(y), player); + chunk.sendChunkSectionUpdate(ChunkUtils.getSectionAt(y), player); return false; } @@ -286,7 +282,7 @@ public class InstanceContainer extends Instance { } else { // Cancelled so we need to refresh player chunk section final int section = ChunkUtils.getSectionAt(blockPosition.getY()); - sendChunkSectionUpdate(chunk, section, player); + chunk.sendChunkSectionUpdate(section, player); } return result; } @@ -439,11 +435,19 @@ public class InstanceContainer extends Instance { chunkGenerator.fillBiomes(biomes, chunkX, chunkZ); } - Function chunkSuppler = chunkDecider != null ? chunkDecider.apply(chunkX, chunkZ) : null; - final Chunk chunk = chunkSuppler == null ? new DynamicChunk(biomes, chunkX, chunkZ) : new StaticChunk(biomes, chunkX, chunkZ, chunkSuppler) ; + final Chunk chunk; + final BlockProvider blockProvider = chunkDecider != null ? chunkDecider.apply(chunkX, chunkZ) : null; + if (blockProvider != null) { + // Use static chunk + chunk = new StaticChunk(biomes, chunkX, chunkZ, blockProvider); + } else { + // Use dynamic chunk + chunk = new DynamicChunk(biomes, chunkX, chunkZ); + } + cacheChunk(chunk); - if (chunkGenerator != null && chunkSuppler == null) { + if (chunkGenerator != null && blockProvider == null) { final ChunkBatch chunkBatch = createChunkBatch(chunk); chunkBatch.flushChunkGenerator(chunkGenerator, callback); @@ -457,62 +461,6 @@ public class InstanceContainer extends Instance { callChunkLoadEvent(chunkX, chunkZ); } - public void sendChunkUpdate(Chunk chunk) { - final Set chunkViewers = chunk.getViewers(); - if (!chunkViewers.isEmpty()) { - sendChunkUpdate(chunkViewers, chunk); - } - } - - @Override - public void sendChunks(Player player) { - for (Chunk chunk : getChunks()) { - sendChunk(player, chunk); - } - } - - @Override - public void sendChunk(Player player, Chunk chunk) { - if (!chunk.isLoaded()) - return; - if (!PlayerUtils.isNettyClient(player)) - return; - - final ByteBuf data = chunk.getFullDataPacket(); - if (data == null || !chunk.packetUpdated) { - PacketWriterUtils.writeCallbackPacket(chunk.getFreshFullDataPacket(), packet -> { - chunk.setFullDataPacket(packet); - sendChunkUpdate(player, chunk); - }); - } else { - sendChunkUpdate(player, chunk); - } - - // TODO do not hardcode - if (MinecraftServer.isFixLighting()) { - UpdateLightPacket updateLightPacket = new UpdateLightPacket(); - updateLightPacket.chunkX = chunk.getChunkX(); - updateLightPacket.chunkZ = chunk.getChunkZ(); - updateLightPacket.skyLightMask = 0x3FFF0; - updateLightPacket.blockLightMask = 0x3F; - updateLightPacket.emptySkyLightMask = 0x0F; - updateLightPacket.emptyBlockLightMask = 0x3FFC0; - byte[] bytes = new byte[2048]; - Arrays.fill(bytes, (byte) 0xFF); - List temp = new ArrayList<>(); - List temp2 = new ArrayList<>(); - for (int i = 0; i < 14; ++i) { - temp.add(bytes); - } - for (int i = 0; i < 6; ++i) { - temp2.add(bytes); - } - updateLightPacket.skyLight = temp; - updateLightPacket.blockLight = temp2; - PacketWriterUtils.writeAndSend(player, updateLightPacket); - } - } - @Override public void enableAutoChunkLoad(boolean enable) { this.autoChunkLoad = enable; diff --git a/src/main/java/net/minestom/server/instance/SharedInstance.java b/src/main/java/net/minestom/server/instance/SharedInstance.java index 04c8a05cd..da6490685 100644 --- a/src/main/java/net/minestom/server/instance/SharedInstance.java +++ b/src/main/java/net/minestom/server/instance/SharedInstance.java @@ -101,11 +101,6 @@ public class SharedInstance extends Instance { instanceContainer.setStorageFolder(storageFolder); } - @Override - public void sendChunkSectionUpdate(Chunk chunk, int section, Player player) { - instanceContainer.sendChunkSectionUpdate(chunk, section, player); - } - @Override public void retrieveChunk(int chunkX, int chunkZ, Consumer callback) { instanceContainer.retrieveChunk(chunkX, chunkZ, callback); @@ -116,16 +111,6 @@ public class SharedInstance extends Instance { instanceContainer.createChunk(chunkX, chunkZ, callback); } - @Override - public void sendChunks(Player player) { - instanceContainer.sendChunks(player); - } - - @Override - public void sendChunk(Player player, Chunk chunk) { - instanceContainer.sendChunk(player, chunk); - } - @Override public void enableAutoChunkLoad(boolean enable) { instanceContainer.enableAutoChunkLoad(enable); diff --git a/src/main/java/net/minestom/server/instance/StaticChunk.java b/src/main/java/net/minestom/server/instance/StaticChunk.java index 694572b93..b61908ff2 100644 --- a/src/main/java/net/minestom/server/instance/StaticChunk.java +++ b/src/main/java/net/minestom/server/instance/StaticChunk.java @@ -2,82 +2,82 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minestom.server.data.Data; +import net.minestom.server.instance.block.BlockProvider; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.instance.block.UpdateConsumer; import net.minestom.server.network.packet.server.play.ChunkDataPacket; -import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.world.biomes.Biome; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.function.Function; public class StaticChunk extends Chunk { - final Function blockProvider; + protected final BlockProvider blockProvider; - public StaticChunk(Biome[] biomes, int chunkX, int chunkZ, Function blockProvider) { - super(biomes, chunkX, chunkZ); - this.blockProvider = blockProvider; - } + public StaticChunk(Biome[] biomes, int chunkX, int chunkZ, BlockProvider blockProvider) { + super(biomes, chunkX, chunkZ); + this.blockProvider = blockProvider; + } - @Override - public void UNSAFE_removeCustomBlock(int x, int y, int z) { - //noop - } + @Override + public void UNSAFE_removeCustomBlock(int x, int y, int z) { + //noop + } - @Override - protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) { - //noop - } + @Override + protected void setBlock(int x, int y, int z, short blockStateId, short customId, Data data, UpdateConsumer updateConsumer) { + //noop + } - @Override - public short getBlockStateId(int x, int y, int z) { - return blockProvider.apply(new BlockPosition(x, y, z)); - } + @Override + public short getBlockStateId(int x, int y, int z) { + return blockProvider.getBlockStateId(x, y, z); + } - @Override - public short getCustomBlockId(int x, int y, int z) { - //noop - return 0; - } + @Override + public short getCustomBlockId(int x, int y, int z) { + //noop + return 0; + } - @Override - public CustomBlock getCustomBlock(int x, int y, int z) { - //noop - return null; - } + @Override + public CustomBlock getCustomBlock(int x, int y, int z) { + //noop + return null; + } - @Override - protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) { - //noop - } + @Override + protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customId) { + //noop + } - @Override - protected void refreshBlockStateId(int x, int y, int z, short blockStateId) { - //noop - } + @Override + protected void refreshBlockStateId(int x, int y, int z, short blockStateId) { + //noop + } - @Override - protected byte[] getSerializedData(){ - return null; - } + @Override + protected byte[] getSerializedData() { + return null; + } - @Override - protected ChunkDataPacket getFreshPacket() { - ChunkDataPacket fullDataPacket = new ChunkDataPacket(); - fullDataPacket.biomes = biomes.clone(); - fullDataPacket.chunkX = chunkX; - fullDataPacket.chunkZ = chunkZ; - short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; - for (int i = 0; i < blocksStateId.length; i++) { - blocksStateId[i] = blockProvider.apply(ChunkUtils.getBlockPosition(i, 0, 0)); - } - fullDataPacket.blocksStateId = blocksStateId; - fullDataPacket.customBlocksId = new short[0]; - fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities); - fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData); - return fullDataPacket; - } + @Override + protected ChunkDataPacket getFreshPacket() { + ChunkDataPacket fullDataPacket = new ChunkDataPacket(); + fullDataPacket.biomes = biomes.clone(); + fullDataPacket.chunkX = chunkX; + fullDataPacket.chunkZ = chunkZ; + short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; + for (int i = 0; i < blocksStateId.length; i++) { + final int[] pos = ChunkUtils.indexToPosition(i, 0, 0); + blocksStateId[i] = blockProvider.getBlockStateId(pos[0], pos[1], pos[2]); + } + fullDataPacket.blocksStateId = blocksStateId; + fullDataPacket.customBlocksId = new short[0]; + fullDataPacket.blockEntities = new CopyOnWriteArraySet<>(blockEntities); + fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData); + return fullDataPacket; + } } diff --git a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java index b0d47d317..9ba095d92 100644 --- a/src/main/java/net/minestom/server/instance/batch/BlockBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/BlockBatch.java @@ -74,9 +74,8 @@ public class BlockBatch implements InstanceBatch { data.apply(chunk); } - chunk.refreshDataPacket(() -> { - instance.sendChunkUpdate(chunk); - }); + // Refresh chunk for viewers + chunk.sendChunkUpdate(); if (isLast) { if (callback != null) diff --git a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java index e27b02d0f..52fa398de 100644 --- a/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java +++ b/src/main/java/net/minestom/server/instance/batch/ChunkBatch.java @@ -102,9 +102,8 @@ public class ChunkBatch implements InstanceBatch { data.apply(chunk); } - chunk.refreshDataPacket(() -> { - instance.sendChunkUpdate(chunk); - }); + // Refresh chunk for viewers + chunk.sendChunkUpdate(); if (callback != null) callback.accept(chunk); diff --git a/src/main/java/net/minestom/server/instance/block/BlockProvider.java b/src/main/java/net/minestom/server/instance/block/BlockProvider.java new file mode 100644 index 000000000..7b054c7a2 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/block/BlockProvider.java @@ -0,0 +1,6 @@ +package net.minestom.server.instance.block; + +@FunctionalInterface +public interface BlockProvider { + short getBlockStateId(int x, int y, int z); +} diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 2903aae46..314f72d8a 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -136,7 +136,7 @@ public class BlockPlacementListener { // Refresh chunk section if needed if (refreshChunk) { - instance.sendChunkSectionUpdate(chunk, ChunkUtils.getSectionAt(blockPosition.getY()), player); + chunk.sendChunkSectionUpdate(ChunkUtils.getSectionAt(blockPosition.getY()), player); } player.getInventory().refreshSlot(player.getHeldSlot());