From 71d0d06f90671840ccd31938771fbbb01446c634 Mon Sep 17 00:00:00 2001 From: themode Date: Fri, 19 Mar 2021 05:51:42 +0100 Subject: [PATCH] Improve block placement performance, more abstraction for future features --- .../instance/palette/PaletteStorage.java | 391 +++--------------- .../server/instance/palette/Section.java | 297 +++++++++++++ .../packet/server/play/ChunkDataPacket.java | 16 +- .../java/net/minestom/server/utils/Utils.java | 17 +- .../server/utils/clone/CloneUtils.java | 17 +- 5 files changed, 378 insertions(+), 360 deletions(-) create mode 100644 src/main/java/net/minestom/server/instance/palette/Section.java diff --git a/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java b/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java index d9a19e720..0591a85a8 100644 --- a/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java +++ b/src/main/java/net/minestom/server/instance/palette/PaletteStorage.java @@ -1,15 +1,14 @@ package net.minestom.server.instance.palette; -import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap; -import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; import net.minestom.server.MinecraftServer; import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.block.Block; import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.chunk.ChunkUtils; +import net.minestom.server.utils.clone.CloneUtils; import net.minestom.server.utils.clone.PublicCloneable; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import static net.minestom.server.instance.Chunk.CHUNK_SECTION_COUNT; import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE; @@ -22,102 +21,58 @@ import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE; */ public class PaletteStorage implements PublicCloneable { - /** - * The maximum bits per entry value. - */ - private final static int MAXIMUM_BITS_PER_ENTRY = 15; + private Section[] sections = new Section[CHUNK_SECTION_COUNT]; - /** - * The minimum bits per entry value. - */ - private final static int MINIMUM_BITS_PER_ENTRY = 4; - - /** - * The maximum bits per entry value which allow for a data palette. - */ - private final static int PALETTE_MAXIMUM_BITS = 8; - - /** - * The number of blocks that should be in one chunk section. - */ - private final static int BLOCK_COUNT = CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE; - - private int bitsPerEntry; - private final int bitsIncrement; - - private int valuesPerLong; - private boolean hasPalette; - - private long[][] sectionBlocks; - - // chunk section - palette index = block id - private Short2ShortLinkedOpenHashMap[] paletteBlockMaps; - // chunk section - block id = palette index - private Short2ShortOpenHashMap[] blockPaletteMaps; + private final int defaultBitsPerEntry; + private final int defaultBitsIncrement; /** * Creates a new palette storage. * - * @param bitsPerEntry the number of bits used for one entry (block) - * @param bitsIncrement the number of bits to add per-block once the palette array is filled + * @param defaultBitsPerEntry the number of bits used for one entry (block) + * @param defaultBitsIncrement the number of bits to add per-block once the palette array is filled */ - public PaletteStorage(int bitsPerEntry, int bitsIncrement) { - Check.argCondition(bitsPerEntry > MAXIMUM_BITS_PER_ENTRY, "The maximum bits per entry is 15"); - bitsPerEntry = fixBitsPerEntry(bitsPerEntry); - - this.bitsPerEntry = bitsPerEntry; - this.bitsIncrement = bitsIncrement; - - this.valuesPerLong = Long.SIZE / bitsPerEntry; - this.hasPalette = bitsPerEntry <= PALETTE_MAXIMUM_BITS; - - init(); - } - - private void init() { - this.sectionBlocks = new long[CHUNK_SECTION_COUNT][0]; - - this.paletteBlockMaps = new Short2ShortLinkedOpenHashMap[CHUNK_SECTION_COUNT]; - this.blockPaletteMaps = new Short2ShortOpenHashMap[CHUNK_SECTION_COUNT]; + public PaletteStorage(int defaultBitsPerEntry, int defaultBitsIncrement) { + Check.argCondition(defaultBitsPerEntry > Section.MAXIMUM_BITS_PER_ENTRY, + "The maximum bits per entry is 15"); + this.defaultBitsPerEntry = defaultBitsPerEntry; + this.defaultBitsIncrement = defaultBitsIncrement; } public void setBlockAt(int x, int y, int z, short blockId) { - PaletteStorage.setBlockAt(this, x, y, z, blockId); + if (!MathUtils.isBetween(y, 0, Chunk.CHUNK_SIZE_Y - 1)) { + return; + } + final int sectionIndex = ChunkUtils.getSectionAt(y); + x = toChunkCoordinate(x); + z = toChunkCoordinate(z); + + Section section = sections[sectionIndex]; + if (section == null) { + section = new Section(defaultBitsPerEntry, defaultBitsIncrement); + sections[sectionIndex] = section; + } + section.setBlockAt(x, y, z, blockId); } public short getBlockAt(int x, int y, int z) { - return PaletteStorage.getBlockAt(this, x, y, z); + if (y < 0 || y >= Chunk.CHUNK_SIZE_Y) { + return 0; + } + + final int sectionIndex = ChunkUtils.getSectionAt(y); + final Section section = sections[sectionIndex]; + if (section == null) { + return Block.AIR.getBlockId(); + } + x = toChunkCoordinate(x); + z = toChunkCoordinate(z); + + return section.getBlockAt(x, y, z); } - /** - * Gets the number of bits that the palette currently take per block. - * - * @return the bits per entry - */ - public int getBitsPerEntry() { - return bitsPerEntry; - } - - /** - * Gets the palette with the index and the block id as the value. - * - * @param section the chunk section to get the palette from - * @return the palette - */ - @Nullable - public short[] getPalette(int section) { - Short2ShortLinkedOpenHashMap paletteBlockMap = paletteBlockMaps[section]; - return paletteBlockMap != null ? paletteBlockMap.values().toShortArray() : null; - } - - /** - * Gets the sections of this object, - * the first array representing the chunk section and the second the block position from {@link #getSectionIndex(int, int, int)}. - * - * @return the section blocks - */ - public long[][] getSectionBlocks() { - return sectionBlocks; + public Section[] getSections() { + return sections; } /** @@ -127,23 +82,8 @@ public class PaletteStorage implements PublicCloneable { * is composed of almost-empty sections since the loop will not stop until a non-air block is discovered. */ public synchronized void clean() { - for (int i = 0; i < sectionBlocks.length; i++) { - long[] section = sectionBlocks[i]; - - if (section.length != 0) { - boolean canClear = true; - for (long blockGroup : section) { - if (blockGroup != 0) { - canClear = false; - break; - } - } - if (canClear) { - sectionBlocks[i] = new long[0]; - } - - } - + for (Section section : sections) { + section.clean(); } } @@ -151,16 +91,9 @@ public class PaletteStorage implements PublicCloneable { * Clears all the data in the palette and data array. */ public void clear() { - init(); - } - - /** - * @deprecated use {@link #clone()} - */ - @Deprecated - @NotNull - public PaletteStorage copy() { - return clone(); + for (Section section : sections) { + section.clear(); + } } @NotNull @@ -168,10 +101,7 @@ public class PaletteStorage implements PublicCloneable { public PaletteStorage clone() { try { PaletteStorage paletteStorage = (PaletteStorage) super.clone(); - paletteStorage.sectionBlocks = sectionBlocks.clone(); - - paletteStorage.paletteBlockMaps = paletteBlockMaps.clone(); - paletteStorage.blockPaletteMaps = blockPaletteMaps.clone(); + paletteStorage.sections = CloneUtils.cloneArray(sections, Section[]::new); return paletteStorage; } catch (CloneNotSupportedException e) { MinecraftServer.getExceptionManager().handleException(e); @@ -179,205 +109,6 @@ public class PaletteStorage implements PublicCloneable { } } - /** - * Retrieves the palette index for the specified block id. - *

- * Also responsible for resizing the palette when full. - * - * @param section the chunk section - * @param blockId the block id to convert - * @return the palette index of {@code blockId} - */ - private short getPaletteIndex(int section, short blockId) { - if (!hasPalette) { - return blockId; - } - - Short2ShortOpenHashMap blockPaletteMap = blockPaletteMaps[section]; - if (blockPaletteMap == null) { - blockPaletteMap = createBlockPaletteMap(); - blockPaletteMaps[section] = blockPaletteMap; - } - - if (!blockPaletteMap.containsKey(blockId)) { - Short2ShortLinkedOpenHashMap paletteBlockMap = paletteBlockMaps[section]; - if (paletteBlockMap == null) { - paletteBlockMap = createPaletteBlockMap(); - paletteBlockMaps[section] = paletteBlockMap; - } - - // Resize the palette if full - if (paletteBlockMap.size() >= getMaxPaletteSize()) { - resize(bitsPerEntry + bitsIncrement); - } - - final short paletteIndex = (short) (paletteBlockMap.lastShortKey() + 1); - paletteBlockMap.put(paletteIndex, blockId); - blockPaletteMap.put(blockId, paletteIndex); - return paletteIndex; - } - - return blockPaletteMap.get(blockId); - } - - /** - * Resizes the array. - *

- * Will create a new palette storage to set all the current blocks, and the data will be transferred to 'this'. - * - * @param newBitsPerEntry the new bits per entry count - */ - private void resize(int newBitsPerEntry) { - newBitsPerEntry = fixBitsPerEntry(newBitsPerEntry); - - PaletteStorage paletteStorageCache = new PaletteStorage(newBitsPerEntry, bitsIncrement); - paletteStorageCache.paletteBlockMaps = paletteBlockMaps; - paletteStorageCache.blockPaletteMaps = blockPaletteMaps; - - for (int y = 0; y < Chunk.CHUNK_SIZE_Y; y++) { - for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { - for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { - final short blockId = getBlockAt(x, y, z); - paletteStorageCache.setBlockAt(x, y, z, blockId); - } - } - } - - this.bitsPerEntry = paletteStorageCache.bitsPerEntry; - - this.valuesPerLong = paletteStorageCache.valuesPerLong; - this.hasPalette = paletteStorageCache.hasPalette; - - this.sectionBlocks = paletteStorageCache.sectionBlocks; - } - - /** - * Gets the maximum number of blocks that the current palette (could be the global one) can take. - * - * @return the number of blocks possible in the palette - */ - private int getMaxPaletteSize() { - return 1 << bitsPerEntry; - } - - // Magic values generated with "Integer.MAX_VALUE >> (31 - bitsPerIndex)" for bitsPerIndex between 4 and 15 - private static final int[] MAGIC_MASKS = - {0, 0, 0, 0, - 15, 31, 63, 127, 255, - 511, 1023, 2047, 4095, - 8191, 16383, 32767}; - - private static void setBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z, short blockId) { - if (!MathUtils.isBetween(y, 0, Chunk.CHUNK_SIZE_Y - 1)) { - return; - } - - final int section = ChunkUtils.getSectionAt(y); - - int valuesPerLong = paletteStorage.valuesPerLong; - - if (paletteStorage.sectionBlocks[section].length == 0) { - if (blockId == 0) { - // Section is empty and method is trying to place an air block, stop unnecessary computation - return; - } - - // Initialize the section - paletteStorage.sectionBlocks[section] = new long[getSize(valuesPerLong)]; - } - - // Convert world coordinates to chunk coordinates - x = toChunkCoordinate(x); - z = toChunkCoordinate(z); - - // Change to palette value - blockId = paletteStorage.getPaletteIndex(section, blockId); - - // The storage could have been resized - valuesPerLong = paletteStorage.valuesPerLong; - - final int sectionIndex = getSectionIndex(x, y, z); - - final int index = sectionIndex / valuesPerLong; - final int bitsPerEntry = paletteStorage.bitsPerEntry; - - final int bitIndex = (sectionIndex % valuesPerLong) * bitsPerEntry; - - final long[] sectionBlock = paletteStorage.sectionBlocks[section]; - - long block = sectionBlock[index]; - { - final long clear = MAGIC_MASKS[bitsPerEntry]; - - block |= clear << bitIndex; - block ^= clear << bitIndex; - block |= (long) blockId << bitIndex; - - sectionBlock[index] = block; - } - } - - private static short getBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z) { - if (y < 0 || y >= Chunk.CHUNK_SIZE_Y) { - return 0; - } - - final int section = ChunkUtils.getSectionAt(y); - final long[] blocks; - - // Retrieve the longs and check if the section is empty - { - blocks = paletteStorage.sectionBlocks[section]; - - if (blocks.length == 0) { - // Section is not loaded, can only be air - return 0; - } - } - - x = toChunkCoordinate(x); - z = toChunkCoordinate(z); - - final int sectionIndex = getSectionIndex(x, y, z); - - final int valuesPerLong = paletteStorage.valuesPerLong; - final int bitsPerEntry = paletteStorage.bitsPerEntry; - - final int index = sectionIndex / valuesPerLong; - final int bitIndex = sectionIndex % valuesPerLong * bitsPerEntry; - - final long value = blocks[index] >> bitIndex & MAGIC_MASKS[bitsPerEntry]; - - // Change to palette value and return - return paletteStorage.hasPalette ? - paletteStorage.paletteBlockMaps[section].get((short) value) : - (short) value; - } - - private static Short2ShortLinkedOpenHashMap createPaletteBlockMap() { - Short2ShortLinkedOpenHashMap map = new Short2ShortLinkedOpenHashMap(CHUNK_SECTION_SIZE); - map.put((short) 0, (short) 0); - - return map; - } - - private static Short2ShortOpenHashMap createBlockPaletteMap() { - Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(CHUNK_SECTION_SIZE); - map.put((short) 0, (short) 0); - - return map; - } - - /** - * Gets the array length of one section based on the number of values which can be stored in one long. - * - * @param valuesPerLong the number of values per long - * @return the array length based on {@code valuesPerLong} - */ - private static int getSize(int valuesPerLong) { - return (BLOCK_COUNT + valuesPerLong - 1) / valuesPerLong; - } - /** * Converts a world coordinate to a chunk one. * @@ -393,34 +124,4 @@ public class PaletteStorage implements PublicCloneable { return xz; } - /** - * Gets the index of the block on the section array based on the block position. - * - * @param x the chunk X - * @param y the chunk Y - * @param z the chunk Z - * @return the section index of the position - */ - public static int getSectionIndex(int x, int y, int z) { - y %= CHUNK_SECTION_SIZE; - return y << 8 | z << 4 | x; - } - - /** - * Fixes invalid bitsPerEntry values. - *

- * See https://wiki.vg/Chunk_Format#Direct - * - * @param bitsPerEntry the bits per entry value before fixing - * @return the fixed bits per entry value - */ - private static int fixBitsPerEntry(int bitsPerEntry) { - if (bitsPerEntry < MINIMUM_BITS_PER_ENTRY) { - return MINIMUM_BITS_PER_ENTRY; - } else if (MathUtils.isBetween(bitsPerEntry, 9, 14)) { - return MAXIMUM_BITS_PER_ENTRY; - } - return bitsPerEntry; - } - } diff --git a/src/main/java/net/minestom/server/instance/palette/Section.java b/src/main/java/net/minestom/server/instance/palette/Section.java new file mode 100644 index 000000000..4f3827767 --- /dev/null +++ b/src/main/java/net/minestom/server/instance/palette/Section.java @@ -0,0 +1,297 @@ +package net.minestom.server.instance.palette; + +import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; +import net.minestom.server.MinecraftServer; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.block.Block; +import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.clone.PublicCloneable; +import org.jetbrains.annotations.NotNull; + +import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE; + +public class Section implements PublicCloneable

{ + + /** + * The maximum bits per entry value. + */ + public final static int MAXIMUM_BITS_PER_ENTRY = 15; + + /** + * The minimum bits per entry value. + */ + public final static int MINIMUM_BITS_PER_ENTRY = 4; + + /** + * The maximum bits per entry value which allow for a data palette. + */ + public final static int PALETTE_MAXIMUM_BITS = 8; + + /** + * The number of blocks that should be in one chunk section. + */ + public final static int BLOCK_COUNT = CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE * CHUNK_SECTION_SIZE; + + // Magic values generated with "Integer.MAX_VALUE >> (31 - bitsPerIndex)" for bitsPerIndex between 4 and 15 + private static final int[] MAGIC_MASKS = + {0, 0, 0, 0, + 15, 31, 63, 127, 255, + 511, 1023, 2047, 4095, + 8191, 16383, 32767}; + + private long[] blocks; + + // chunk section - palette index = block id + private Short2ShortLinkedOpenHashMap paletteBlockMap; + // chunk section - block id = palette index + private Short2ShortOpenHashMap blockPaletteMap; + + private int bitsPerEntry; + private final int bitsIncrement; + + private int valuesPerLong; + private boolean hasPalette; + + protected Section(int bitsPerEntry, int bitsIncrement) { + this.bitsPerEntry = bitsPerEntry; + this.bitsIncrement = bitsIncrement; + + this.valuesPerLong = Long.SIZE / bitsPerEntry; + this.hasPalette = bitsPerEntry <= PALETTE_MAXIMUM_BITS; + + clear(); + } + + public void setBlockAt(int x, int y, int z, short blockId) { + if (blocks.length == 0) { + if (blockId == 0) { + // Section is empty and method is trying to place an air block, stop unnecessary computation + return; + } + + // Initialize the section + blocks = new long[getSize(valuesPerLong)]; + } + + // Change to palette value + blockId = getPaletteIndex(blockId); + + final int sectionIndex = getSectionIndex(x, y, z); + + final int index = sectionIndex / valuesPerLong; + + final int bitIndex = (sectionIndex % valuesPerLong) * bitsPerEntry; + + long block = blocks[index]; + { + final long clear = MAGIC_MASKS[bitsPerEntry]; + + block |= clear << bitIndex; + block ^= clear << bitIndex; + block |= (long) blockId << bitIndex; + + blocks[index] = block; + } + } + + public short getBlockAt(int x, int y, int z) { + if (blocks.length == 0) { + // Section is not loaded, can only be air + return Block.AIR.getBlockId(); + } + + final int sectionIdentifier = getSectionIndex(x, y, z); + + final int index = sectionIdentifier / valuesPerLong; + final int bitIndex = sectionIdentifier % valuesPerLong * bitsPerEntry; + + final long value = blocks[index] >> bitIndex & MAGIC_MASKS[bitsPerEntry]; + + // Change to palette value and return + return hasPalette ? + paletteBlockMap.get((short) value) : + (short) value; + } + + /** + * Resizes the array. + *

+ * Will create a new palette storage to set all the current blocks, and the data will be transferred to 'this'. + * + * @param newBitsPerEntry the new bits per entry count + */ + private void resize(int newBitsPerEntry) { + newBitsPerEntry = fixBitsPerEntry(newBitsPerEntry); + + Section section = new Section(newBitsPerEntry, bitsIncrement); + section.paletteBlockMap = paletteBlockMap; + section.blockPaletteMap = blockPaletteMap; + + for (int y = 0; y < Chunk.CHUNK_SIZE_Y; y++) { + for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) { + for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + final short blockId = getBlockAt(x, y, z); + section.setBlockAt(x, y, z, blockId); + } + } + } + + this.bitsPerEntry = section.bitsPerEntry; + + this.valuesPerLong = section.valuesPerLong; + this.hasPalette = section.hasPalette; + + this.blocks = section.blocks; + } + + /** + * Loops through all the sections and blocks to find unused array (empty chunk section) + *

+ * Useful after clearing one or multiple sections of a chunk. Can be unnecessarily expensive if the chunk + * is composed of almost-empty sections since the loop will not stop until a non-air block is discovered. + */ + public synchronized void clean() { + if (blocks.length != 0) { + boolean canClear = true; + for (long blockGroup : blocks) { + if (blockGroup != 0) { + canClear = false; + break; + } + } + if (canClear) { + this.blocks = new long[0]; + } + + } + } + + public void clear() { + this.blocks = new long[0]; + this.paletteBlockMap = createPaletteBlockMap(); + this.blockPaletteMap = createBlockPaletteMap(); + } + + public long[] getBlocks() { + return blocks; + } + + public Short2ShortLinkedOpenHashMap getPaletteBlockMap() { + return paletteBlockMap; + } + + public Short2ShortOpenHashMap getBlockPaletteMap() { + return blockPaletteMap; + } + + public int getBitsPerEntry() { + return bitsPerEntry; + } + + /** + * Retrieves the palette index for the specified block id. + *

+ * Also responsible for resizing the palette when full. + * + * @param blockId the block id to convert + * @return the palette index of {@code blockId} + */ + private short getPaletteIndex(short blockId) { + if (!hasPalette) { + return blockId; + } + + if (!blockPaletteMap.containsKey(blockId)) { + // Resize the palette if full + if (paletteBlockMap.size() >= getMaxPaletteSize()) { + resize(bitsPerEntry + bitsIncrement); + } + + final short paletteIndex = (short) (paletteBlockMap.lastShortKey() + 1); + paletteBlockMap.put(paletteIndex, blockId); + blockPaletteMap.put(blockId, paletteIndex); + return paletteIndex; + } + + return blockPaletteMap.get(blockId); + } + + /** + * Gets the maximum number of blocks that the current palette (could be the global one) can take. + * + * @return the number of blocks possible in the palette + */ + private int getMaxPaletteSize() { + return 1 << bitsPerEntry; + } + + private static Short2ShortLinkedOpenHashMap createPaletteBlockMap() { + Short2ShortLinkedOpenHashMap map = new Short2ShortLinkedOpenHashMap(CHUNK_SECTION_SIZE); + map.put((short) 0, (short) 0); + + return map; + } + + private static Short2ShortOpenHashMap createBlockPaletteMap() { + Short2ShortOpenHashMap map = new Short2ShortOpenHashMap(CHUNK_SECTION_SIZE); + map.put((short) 0, (short) 0); + + return map; + } + + /** + * Gets the index of the block on the section array based on the block position. + * + * @param x the chunk X + * @param y the chunk Y + * @param z the chunk Z + * @return the section index of the position + */ + public static int getSectionIndex(int x, int y, int z) { + y %= CHUNK_SECTION_SIZE; + return y << 8 | z << 4 | x; + } + + /** + * Gets the array length of one section based on the number of values which can be stored in one long. + * + * @param valuesPerLong the number of values per long + * @return the array length based on {@code valuesPerLong} + */ + private static int getSize(int valuesPerLong) { + return (BLOCK_COUNT + valuesPerLong - 1) / valuesPerLong; + } + + /** + * Fixes invalid bitsPerEntry values. + *

+ * See https://wiki.vg/Chunk_Format#Direct + * + * @param bitsPerEntry the bits per entry value before fixing + * @return the fixed bits per entry value + */ + private static int fixBitsPerEntry(int bitsPerEntry) { + if (bitsPerEntry < MINIMUM_BITS_PER_ENTRY) { + return MINIMUM_BITS_PER_ENTRY; + } else if (MathUtils.isBetween(bitsPerEntry, 9, 14)) { + return MAXIMUM_BITS_PER_ENTRY; + } + return bitsPerEntry; + } + + @NotNull + @Override + public Section clone() { + try { + Section section = (Section) super.clone(); + section.blocks = blocks.clone(); + section.paletteBlockMap = paletteBlockMap.clone(); + section.blockPaletteMap = blockPaletteMap.clone(); + return section; + } catch (CloneNotSupportedException e) { + MinecraftServer.getExceptionManager().handleException(e); + throw new IllegalStateException("Weird thing happened"); + } + } +} 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 9b25a5df2..35f13a9e9 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 @@ -1,7 +1,6 @@ package net.minestom.server.network.packet.server.play; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntSet; import net.minestom.server.MinecraftServer; @@ -9,9 +8,11 @@ import net.minestom.server.data.Data; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.instance.palette.PaletteStorage; +import net.minestom.server.instance.palette.Section; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.BufUtils; import net.minestom.server.utils.Utils; import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.cache.CacheablePacket; @@ -23,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; -import java.util.Arrays; import java.util.UUID; public class ChunkDataPacket implements ServerPacket, CacheablePacket { @@ -63,13 +63,17 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket { writer.writeBoolean(fullChunk); int mask = 0; - ByteBuf blocks = Unpooled.buffer(MAX_BUFFER_SIZE); + ByteBuf blocks = BufUtils.getBuffer(MAX_BUFFER_SIZE); for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) { if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) { - final long[] section = paletteStorage.getSectionBlocks()[i]; - if (section.length > 0) { // section contains at least one block + final Section section = paletteStorage.getSections()[i]; + if (section == null) { + // Section not loaded + continue; + } + if (section.getBlocks().length > 0) { // section contains at least one block mask |= 1 << i; - Utils.writeBlocks(blocks, paletteStorage.getPalette(i), section, paletteStorage.getBitsPerEntry()); + Utils.writeSectionBlocks(blocks, section); } } } diff --git a/src/main/java/net/minestom/server/utils/Utils.java b/src/main/java/net/minestom/server/utils/Utils.java index 96bc364d6..ca249e8fc 100644 --- a/src/main/java/net/minestom/server/utils/Utils.java +++ b/src/main/java/net/minestom/server/utils/Utils.java @@ -1,6 +1,8 @@ package net.minestom.server.utils; import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.shorts.Short2ShortLinkedOpenHashMap; +import net.minestom.server.instance.palette.Section; import net.minestom.server.utils.binary.BinaryWriter; import java.util.UUID; @@ -92,27 +94,32 @@ public final class Utils { return new UUID(uuidMost, uuidLeast); } - public static void writeBlocks(ByteBuf buffer, short[] palette, long[] blocksId, int bitsPerEntry) { + public static void writeSectionBlocks(ByteBuf buffer, Section section) { /*short count = 0; for (short id : blocksId) if (id != 0) count++;*/ + final int bitsPerEntry = section.getBitsPerEntry(); + //buffer.writeShort(count); + // TODO count blocks buffer.writeShort(200); buffer.writeByte((byte) bitsPerEntry); // Palette if (bitsPerEntry < 9) { // Palette has to exist - writeVarIntBuf(buffer, palette.length); - for (short paletteValue : palette) { + final Short2ShortLinkedOpenHashMap paletteBlockMap = section.getPaletteBlockMap(); + writeVarIntBuf(buffer, paletteBlockMap.size()); + for (short paletteValue : paletteBlockMap.values()) { writeVarIntBuf(buffer, paletteValue); } } - writeVarIntBuf(buffer, blocksId.length); - for (long datum : blocksId) { + final long[] blocks = section.getBlocks(); + writeVarIntBuf(buffer, blocks.length); + for (long datum : blocks) { buffer.writeLong(datum); } } diff --git a/src/main/java/net/minestom/server/utils/clone/CloneUtils.java b/src/main/java/net/minestom/server/utils/clone/CloneUtils.java index 3990fb722..1234ab51f 100644 --- a/src/main/java/net/minestom/server/utils/clone/CloneUtils.java +++ b/src/main/java/net/minestom/server/utils/clone/CloneUtils.java @@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.IntFunction; /** * Convenient interface to deep-copy single object or collections. @@ -14,15 +15,23 @@ import java.util.concurrent.CopyOnWriteArrayList; public final class CloneUtils { @Nullable - public static T optionalClone(@Nullable T object) { - return object != null ? (T) object.clone() : null; + public static > T optionalClone(@Nullable T object) { + return object != null ? object.clone() : null; } @NotNull - public static CopyOnWriteArrayList cloneCopyOnWriteArrayList(@NotNull List list) { + public static > CopyOnWriteArrayList cloneCopyOnWriteArrayList(@NotNull List list) { CopyOnWriteArrayList result = new CopyOnWriteArrayList<>(); for (T element : list) { - result.add((T) element.clone()); + result.add(element.clone()); + } + return result; + } + + public static > T[] cloneArray(@NotNull T[] array, IntFunction arraySupplier) { + T[] result = arraySupplier.apply(array.length); + for (int i = 0; i < result.length; i++) { + result[i] = optionalClone(array[i]); } return result; }