Improve block placement performance, more abstraction for future features

This commit is contained in:
themode 2021-03-19 05:51:42 +01:00
parent 933b2663eb
commit 71d0d06f90
5 changed files with 378 additions and 360 deletions

View File

@ -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<PaletteStorage> {
/**
* 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<PaletteStorage> {
* 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<PaletteStorage> {
* 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<PaletteStorage> {
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<PaletteStorage> {
}
}
/**
* Retrieves the palette index for the specified block id.
* <p>
* 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.
* <p>
* 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<PaletteStorage> {
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.
* <p>
* 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;
}
}

View File

@ -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<Section> {
/**
* 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.
* <p>
* 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)
* <p>
* 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.
* <p>
* 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.
* <p>
* 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");
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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 extends PublicCloneable> T optionalClone(@Nullable T object) {
return object != null ? (T) object.clone() : null;
public static <T extends PublicCloneable<T>> T optionalClone(@Nullable T object) {
return object != null ? object.clone() : null;
}
@NotNull
public static <T extends PublicCloneable> CopyOnWriteArrayList cloneCopyOnWriteArrayList(@NotNull List<T> list) {
public static <T extends PublicCloneable<T>> CopyOnWriteArrayList<T> cloneCopyOnWriteArrayList(@NotNull List<T> list) {
CopyOnWriteArrayList<T> result = new CopyOnWriteArrayList<>();
for (T element : list) {
result.add((T) element.clone());
result.add(element.clone());
}
return result;
}
public static <T extends PublicCloneable<T>> T[] cloneArray(@NotNull T[] array, IntFunction<T[]> arraySupplier) {
T[] result = arraySupplier.apply(array.length);
for (int i = 0; i < result.length; i++) {
result[i] = optionalClone(array[i]);
}
return result;
}