diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index f2b192f59..5543c1cb5 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -42,6 +42,11 @@ import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +/** + * Could be a player, a monster, or an object. + *

+ * To create your own entity you probably want to extends {@link ObjectEntity} or {@link EntityCreature} instead. + */ public abstract class Entity implements Viewable, EventHandler, DataContainer { private static final Map entityById = new ConcurrentHashMap<>(); diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 959edf807..3a18e89ce 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -1,6 +1,5 @@ package net.minestom.server.instance; -import io.netty.buffer.ByteBuf; import net.minestom.server.MinecraftServer; import net.minestom.server.Viewable; import net.minestom.server.data.Data; @@ -32,7 +31,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.function.Consumer; // TODO light data & API @@ -69,11 +67,6 @@ public abstract class Chunk implements Viewable, DataContainer { private final boolean shouldGenerate; private boolean readOnly; - // Packet cache - private volatile boolean enableCachePacket; - protected volatile boolean packetUpdated; - private ByteBuf fullDataPacket; - protected volatile boolean loaded = true; protected final Set viewers = new CopyOnWriteArraySet<>(); @@ -89,9 +82,6 @@ public abstract class Chunk implements Viewable, DataContainer { this.chunkZ = chunkZ; this.shouldGenerate = shouldGenerate; - // true by default - this.enableCachePacket = true; - if (biomes != null && biomes.length == BIOME_COUNT) { this.biomes = biomes; } else { @@ -324,51 +314,6 @@ public abstract class Chunk implements Viewable, DataContainer { this.readOnly = readOnly; } - /** - * Gets if this chunk automatically cache the latest {@link ChunkDataPacket} version. - *

- * Retrieved with {@link #retrieveDataBuffer(Consumer)}. - * - * @return true if the chunk automatically cache the chunk packet - */ - public boolean enableCachePacket() { - return enableCachePacket; - } - - /** - * Enables or disable the automatic {@link ChunkDataPacket} caching. - * - * @param enableCachePacket true to enable to chunk packet caching - */ - public synchronized void setEnableCachePacket(boolean enableCachePacket) { - this.enableCachePacket = enableCachePacket; - if (enableCachePacket && fullDataPacket != null) { - this.fullDataPacket.release(); - this.fullDataPacket = null; - } - } - - /** - * Gets the cached data packet. - *

- * Use {@link #retrieveDataBuffer(Consumer)} to be sure to get the updated version. - * - * @return the last cached data packet, can be null or outdated - */ - public ByteBuf getFullDataPacket() { - return fullDataPacket; - } - - /** - * Sets the cached {@link ChunkDataPacket} of this chunk. - * - * @param fullDataPacket the new cached chunk packet - */ - public void setFullDataPacket(ByteBuf fullDataPacket) { - this.fullDataPacket = fullDataPacket; - this.packetUpdated = true; - } - /** * Changes this chunk columnar space. * @@ -378,27 +323,6 @@ public abstract class Chunk implements Viewable, DataContainer { this.columnarSpace = columnarSpace; } - /** - * Retrieves (and cache if needed) 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) { - // Packet has never been wrote or is outdated, write it - PacketWriterUtils.writeCallbackPacket(getFreshFullDataPacket(), packet -> { - if (enableCachePacket) { - setFullDataPacket(packet); - } - consumer.accept(packet); - }); - } else { - // Packet is up-to-date - consumer.accept(data); - } - } - /** * Gets a {@link ChunkDataPacket} which should contain the full chunk. * @@ -509,7 +433,7 @@ public abstract class Chunk implements Viewable, DataContainer { final PlayerConnection playerConnection = player.getPlayerConnection(); // Retrieve & send the buffer to the connection - retrieveDataBuffer(buf -> playerConnection.sendPacket(buf, true)); + playerConnection.sendPacket(getFreshFullDataPacket()); // TODO do not hardcode if (MinecraftServer.isFixLighting()) { @@ -542,10 +466,8 @@ public abstract class Chunk implements Viewable, DataContainer { * @param player the player to update the chunk to */ public void sendChunkUpdate(@NotNull Player player) { - retrieveDataBuffer(buf -> { - final PlayerConnection playerConnection = player.getPlayerConnection(); - playerConnection.sendPacket(buf, true); - }); + final PlayerConnection playerConnection = player.getPlayerConnection(); + playerConnection.sendPacket(getFreshFullDataPacket()); } /** @@ -554,14 +476,10 @@ public abstract class Chunk implements Viewable, DataContainer { public void sendChunkUpdate() { final Set chunkViewers = getViewers(); if (!chunkViewers.isEmpty()) { - retrieveDataBuffer(buf -> chunkViewers.forEach(player -> { + chunkViewers.forEach(player -> { final PlayerConnection playerConnection = player.getPlayerConnection(); - if (!PlayerUtils.isNettyClient(playerConnection)) - return; - - playerConnection.sendPacket(buf, true); - })); - + playerConnection.sendPacket(getFreshFullDataPacket()); + }); } } diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index f378a3b7f..5ba8408c1 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -13,7 +13,6 @@ import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.packet.server.play.ChunkDataPacket; import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.BlockPosition; -import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.block.CustomBlockUtils; @@ -36,6 +35,8 @@ import java.util.concurrent.CopyOnWriteArraySet; */ public class DynamicChunk extends Chunk { + private static final int BITS_PER_ENTRY = 15; + /** * Represents the version which will be present in the serialized output. * Used to define which deserializer to use. @@ -45,9 +46,11 @@ public class DynamicChunk extends Chunk { // blocks id based on coordinate, see Chunk#getBlockIndex // WARNING: those arrays are NOT thread-safe // and modifying them can cause issue with block data, update, block entity and the cached chunk packet - protected final short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; + //protected final short[] blocksStateId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; protected final short[] customBlocksId = new short[CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z]; + protected long[][] sectionBlocks = new long[CHUNK_SECTION_COUNT][0]; + // Used to get all blocks with data (no null) // Key is still chunk coordinates (see #getBlockIndex) protected final Int2ObjectMap blocksData = new Int2ObjectOpenHashMap<>(); @@ -80,12 +83,13 @@ public class DynamicChunk extends Chunk { // True if the block is not complete air without any custom block capabilities final boolean hasBlock = blockStateId != 0 || customBlockId != 0; if (hasBlock) { - this.blocksStateId[index] = blockStateId; + setBlockAt(x, y, z, blockStateId); this.customBlocksId[index] = customBlockId; } else { // Block has been deleted, clear cache and return - this.blocksStateId[index] = 0; // Set to air + setBlockAt(x, y, z, (short) 0); + //this.blocksStateId[index] = 0; // Set to air this.customBlocksId[index] = 0; // Remove custom block this.blocksData.remove(index); @@ -94,8 +98,6 @@ public class DynamicChunk extends Chunk { this.updatableBlocksLastUpdate.remove(index); this.blockEntities.remove(index); - - this.packetUpdated = false; return; } @@ -121,8 +123,6 @@ public class DynamicChunk extends Chunk { } else { this.blockEntities.remove(index); } - - this.packetUpdated = false; } @Override @@ -156,40 +156,26 @@ public class DynamicChunk extends Chunk { @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 - } - return blocksStateId[index]; + return getBlockAt(x, y, z); } @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 - } return customBlocksId[index]; } @Override protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customBlockId) { final int blockIndex = getBlockIndex(x, y, z); - if (!MathUtils.isBetween(blockIndex, 0, blocksStateId.length)) { - return; - } - - this.blocksStateId[blockIndex] = blockStateId; + setBlockAt(x, y, z, blockStateId); this.customBlocksId[blockIndex] = customBlockId; } @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; + setBlockAt(x, y, z, blockStateId); } @Override @@ -260,7 +246,7 @@ public class DynamicChunk extends Chunk { for (byte z = 0; z < CHUNK_SIZE_Z; z++) { final int index = getBlockIndex(x, y, z); - final short blockStateId = blocksStateId[index]; + final short blockStateId = getBlockAt(x, y, z); final short customBlockId = customBlocksId[index]; // No block at the position @@ -398,7 +384,8 @@ public class DynamicChunk extends Chunk { fullDataPacket.biomes = biomes.clone(); fullDataPacket.chunkX = chunkX; fullDataPacket.chunkZ = chunkZ; - fullDataPacket.blocksStateId = blocksStateId.clone(); + fullDataPacket.bitsPerEntry = BITS_PER_ENTRY; + fullDataPacket.sectionBlocks = sectionBlocks.clone(); fullDataPacket.customBlocksId = customBlocksId.clone(); fullDataPacket.blockEntities = new HashSet<>(blockEntities); fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(blocksData); @@ -409,7 +396,7 @@ public class DynamicChunk extends Chunk { @Override public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) { DynamicChunk dynamicChunk = new DynamicChunk(instance, biomes.clone(), chunkX, chunkZ); - ArrayUtils.copyToDestination(blocksStateId, dynamicChunk.blocksStateId); + dynamicChunk.sectionBlocks = sectionBlocks.clone(); ArrayUtils.copyToDestination(customBlocksId, dynamicChunk.customBlocksId); dynamicChunk.blocksData.putAll(blocksData); dynamicChunk.updatableBlocks.addAll(updatableBlocks); @@ -418,4 +405,82 @@ public class DynamicChunk extends Chunk { return dynamicChunk; } + + private void setBlockAt(int x, int y, int z, short blockId) { + x %= 16; + if (x < 0) { + x = CHUNK_SIZE_X + x; + } + z %= 16; + if (z < 0) { + z = CHUNK_SIZE_Z + z; + } + + final char valuesPerLong = (char) (Long.SIZE / BITS_PER_ENTRY); + + int sectionY = y % CHUNK_SECTION_SIZE; + int sectionIndex = (((sectionY * 16) + z) * 16) + x; + + final int index = sectionIndex / valuesPerLong; + final int bitIndex = sectionIndex % valuesPerLong * BITS_PER_ENTRY; + + final int section = y / CHUNK_SECTION_SIZE; + + if (sectionBlocks[section].length == 0) { + sectionBlocks[section] = new long[getSize()]; + } + + long[] sectionBlock = sectionBlocks[section]; + + //System.out.println("test1 " + binary(sectionBlock[index])); + //System.out.println("test2 " + binary(((long) blockId << (bitIndex)))); + sectionBlock[index] |= ((long) blockId << (bitIndex)); + } + + private static String binary(long value) { + return "0b" + Long.toBinaryString(value); + } + + private short getBlockAt(int x, int y, int z) { + x %= 16; + if (x < 0) { + x = CHUNK_SIZE_X + x; + } + z %= 16; + if (z < 0) { + z = CHUNK_SIZE_Z + z; + } + + final char valuesPerLong = (char) (Long.SIZE / BITS_PER_ENTRY); + + int sectionY = y % CHUNK_SECTION_SIZE; + int sectionIndex = (((sectionY * 16) + z) * 16) + x; + + final int index = sectionIndex / valuesPerLong; + final int bitIndex = sectionIndex % valuesPerLong * BITS_PER_ENTRY; + + final int section = y / CHUNK_SECTION_SIZE; + + long[] blocks = sectionBlocks[section]; + + if (blocks.length == 0) { + return 0; + } + + long mask = (1 << (bitIndex)) - 1; + + /*System.out.println("data " + index + " " + bitIndex + " " + sectionIndex); + System.out.println("POS " + x + " " + y + " " + z); + System.out.println("mask " + binary(mask)); + System.out.println("bin " + binary(blocks[index])); + System.out.println("result " + ((blocks[index] >> bitIndex) & mask));*/ + return (short) (blocks[index] >> bitIndex & mask); + } + + private int getSize() { + final int blockCount = 16 * 16 * 16; // A whole chunk section + final char valuesPerLong = (char) (Long.SIZE / BITS_PER_ENTRY); + final int arraySize = blockCount / valuesPerLong; + return arraySize; + } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/instance/StaticChunk.java b/src/main/java/net/minestom/server/instance/StaticChunk.java index 7fd749d26..b34a7757d 100644 --- a/src/main/java/net/minestom/server/instance/StaticChunk.java +++ b/src/main/java/net/minestom/server/instance/StaticChunk.java @@ -1,6 +1,5 @@ package net.minestom.server.instance; -import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minestom.server.data.Data; import net.minestom.server.instance.block.BlockProvider; @@ -105,7 +104,7 @@ public class StaticChunk extends Chunk { final int z = ChunkUtils.blockIndexToChunkPositionZ(i); blocksStateId[i] = blockProvider.getBlockStateId(x, y, z); } - fullDataPacket.blocksStateId = blocksStateId; + //fullDataPacket.blocksStateId = blocksStateId; fullDataPacket.customBlocksId = new short[0]; fullDataPacket.blockEntities = new HashSet<>(); fullDataPacket.blocksData = new Int2ObjectOpenHashMap<>(); @@ -117,10 +116,10 @@ public class StaticChunk extends Chunk { public Chunk copy(@NotNull Instance instance, int chunkX, int chunkZ) { StaticChunk staticChunk = new StaticChunk(instance, biomes.clone(), chunkX, chunkZ, blockProvider); // Prevent re-writing the whole packet since it is static anyway - final ByteBuf packetBuffer = getFullDataPacket(); + /*final ByteBuf packetBuffer = getFullDataPacket(); if (packetBuffer != null) { staticChunk.setFullDataPacket(packetBuffer); - } + }*/ return staticChunk; } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java index d16aa6499..2a74778e6 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ChunkDataPacket.java @@ -5,7 +5,6 @@ import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minestom.server.MinecraftServer; import net.minestom.server.data.Data; -import net.minestom.server.instance.Chunk; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.packet.server.ServerPacket; @@ -28,18 +27,18 @@ public class ChunkDataPacket implements ServerPacket { public Biome[] biomes; public int chunkX, chunkZ; - public short[] blocksStateId; + public int bitsPerEntry; + public long[][] sectionBlocks; public short[] customBlocksId; public Set blockEntities; public Int2ObjectMap blocksData; - //public Chunk chunk; public int[] sections; private static final byte CHUNK_SECTION_COUNT = 16; - private static final int BITS_PER_ENTRY = 15; - private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES; + private static final int MAX_BITS_PER_ENTRY = 15; + private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES; @Override public void write(@NotNull BinaryWriter writer) { @@ -51,10 +50,11 @@ public class ChunkDataPacket implements ServerPacket { ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE); for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) { if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) { - short[] section = getSection(i); - if (section != null) { // section contains at least one block + final long[] section = sectionBlocks[i]; + if (section.length > 0) { // section contains at least one block + //if (true) { mask |= 1 << i; - Utils.writeBlocks(blocks, section, BITS_PER_ENTRY); + Utils.writeBlocks(blocks, section, bitsPerEntry); } else { mask |= 0; } @@ -116,26 +116,6 @@ public class ChunkDataPacket implements ServerPacket { } } - private short[] getSection(byte section) { - short[] blocks = new short[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z]; - boolean empty = true; - for (byte y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { - for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++) { - for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - final int yPos = (y + Chunk.CHUNK_SECTION_SIZE * section); - final int index = ChunkUtils.getBlockIndex(x, yPos, z); - final short blockStateId = blocksStateId[index]; - if (blockStateId != 0) - empty = false; - - final int packetIndex = (((y * 16) + x) * 16) + z; - blocks[packetIndex] = blockStateId; - } - } - } - return empty ? null : blocks; - } - @Override public int getId() { return ServerPacketIdentifier.CHUNK_DATA; diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index 13f185d00..8753945a8 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -2,6 +2,7 @@ package net.minestom.server.utils; import io.netty.buffer.ByteBuf; import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.block.Block; import net.minestom.server.utils.binary.BinaryWriter; import java.util.UUID; @@ -115,31 +116,57 @@ public final class Utils { 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE, 0, 5}; - public static void writeBlocks(ByteBuf buffer, short[] blocksId, int bitsPerEntry) { - short count = 0; + public static void writeBlocks(ByteBuf buffer, long[] blocksId, int bitsPerEntry) { + /*short count = 0; for (short id : blocksId) if (id != 0) - count++; + count++;*/ - buffer.writeShort(count); + + //buffer.writeShort(count); + buffer.writeShort(200); buffer.writeByte((byte) bitsPerEntry); - int[] blocksData = new int[Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SECTION_SIZE * Chunk.CHUNK_SIZE_Z]; - for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { - for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { - for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - int sectionIndex = (((y * 16) + x) * 16) + z; - int index = y << 8 | z << 4 | x; - blocksData[index] = blocksId[sectionIndex]; - } - } - } - final long[] data = encodeBlocks(blocksData, bitsPerEntry); + + final long[] data = blocksId;//encodeBlocksTEST(bitsPerEntry); writeVarIntBuf(buffer, data.length); for (long datum : data) { buffer.writeLong(datum); } } + public synchronized static long[] encodeBlocksTEST(int bitsPerEntry) { + //long test = (Block.TORCH.getBlockId() << (64 - 50 - bitsPerEntry + 1)); + //System.out.println("BINARY: 0b" + Long.toBinaryString(test) + " " + (64 - 50 - bitsPerEntry + 1)); + final int blockCount = 16 * 16 * 16; // A whole chunk section + final int longSize = Long.SIZE; // 64 + final char valuesPerLong = (char) (longSize / bitsPerEntry); + final int arraySize = blockCount / valuesPerLong; + + long[] data = new long[arraySize]; + //data[0] = 0b000000000000001_000000000000001_000000000000001_000000000000001L; + //data[1] = 0b000000000000001_000000000000001_000000000000001_000000000000010L; + + for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) { + for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { + for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + final long blockId = x % 2 == 0 && z % 2 == 0 ? Block.AIR.getBlockId() : Block.LAVA.getBlockId(); + int sectionIndex = (((y * 16) + z) * 16) + x; + + final int index = sectionIndex / valuesPerLong; + final int bitIndex = sectionIndex % valuesPerLong * bitsPerEntry; + + data[index] |= (blockId << bitIndex); + } + } + } + + return data; + } + + private static String binary(long value) { + return "0b" + Long.toBinaryString(value); + } + public static long[] encodeBlocks(int[] blocks, int bitsPerEntry) { final long maxEntryValue = (1L << bitsPerEntry) - 1; final char valuesPerLong = (char) (64 / bitsPerEntry); @@ -160,4 +187,5 @@ public final class Utils { return data; } + }