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; 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.MinecraftServer;
import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.block.Block;
import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.chunk.ChunkUtils; 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.clone.PublicCloneable;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; 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_COUNT;
import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE; 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> { public class PaletteStorage implements PublicCloneable<PaletteStorage> {
/** private Section[] sections = new Section[CHUNK_SECTION_COUNT];
* The maximum bits per entry value.
*/
private final static int MAXIMUM_BITS_PER_ENTRY = 15;
/** private final int defaultBitsPerEntry;
* The minimum bits per entry value. private final int defaultBitsIncrement;
*/
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;
/** /**
* Creates a new palette storage. * Creates a new palette storage.
* *
* @param bitsPerEntry the number of bits used for one entry (block) * @param defaultBitsPerEntry 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 defaultBitsIncrement the number of bits to add per-block once the palette array is filled
*/ */
public PaletteStorage(int bitsPerEntry, int bitsIncrement) { public PaletteStorage(int defaultBitsPerEntry, int defaultBitsIncrement) {
Check.argCondition(bitsPerEntry > MAXIMUM_BITS_PER_ENTRY, "The maximum bits per entry is 15"); Check.argCondition(defaultBitsPerEntry > Section.MAXIMUM_BITS_PER_ENTRY,
bitsPerEntry = fixBitsPerEntry(bitsPerEntry); "The maximum bits per entry is 15");
this.defaultBitsPerEntry = defaultBitsPerEntry;
this.bitsPerEntry = bitsPerEntry; this.defaultBitsIncrement = defaultBitsIncrement;
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 void setBlockAt(int x, int y, int z, short blockId) { 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) { 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);
} }
/** public Section[] getSections() {
* Gets the number of bits that the palette currently take per block. return sections;
*
* @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;
} }
/** /**
@ -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. * is composed of almost-empty sections since the loop will not stop until a non-air block is discovered.
*/ */
public synchronized void clean() { public synchronized void clean() {
for (int i = 0; i < sectionBlocks.length; i++) { for (Section section : sections) {
long[] section = sectionBlocks[i]; section.clean();
if (section.length != 0) {
boolean canClear = true;
for (long blockGroup : section) {
if (blockGroup != 0) {
canClear = false;
break;
}
}
if (canClear) {
sectionBlocks[i] = new long[0];
}
}
} }
} }
@ -151,16 +91,9 @@ public class PaletteStorage implements PublicCloneable<PaletteStorage> {
* Clears all the data in the palette and data array. * Clears all the data in the palette and data array.
*/ */
public void clear() { public void clear() {
init(); for (Section section : sections) {
} section.clear();
}
/**
* @deprecated use {@link #clone()}
*/
@Deprecated
@NotNull
public PaletteStorage copy() {
return clone();
} }
@NotNull @NotNull
@ -168,10 +101,7 @@ public class PaletteStorage implements PublicCloneable<PaletteStorage> {
public PaletteStorage clone() { public PaletteStorage clone() {
try { try {
PaletteStorage paletteStorage = (PaletteStorage) super.clone(); PaletteStorage paletteStorage = (PaletteStorage) super.clone();
paletteStorage.sectionBlocks = sectionBlocks.clone(); paletteStorage.sections = CloneUtils.cloneArray(sections, Section[]::new);
paletteStorage.paletteBlockMaps = paletteBlockMaps.clone();
paletteStorage.blockPaletteMaps = blockPaletteMaps.clone();
return paletteStorage; return paletteStorage;
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
MinecraftServer.getExceptionManager().handleException(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. * Converts a world coordinate to a chunk one.
* *
@ -393,34 +124,4 @@ public class PaletteStorage implements PublicCloneable<PaletteStorage> {
return xz; 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; package net.minestom.server.network.packet.server.play;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSet;
import net.minestom.server.MinecraftServer; 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.BlockManager;
import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.palette.PaletteStorage; 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.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.BlockPosition;
import net.minestom.server.utils.BufUtils;
import net.minestom.server.utils.Utils; import net.minestom.server.utils.Utils;
import net.minestom.server.utils.binary.BinaryWriter; import net.minestom.server.utils.binary.BinaryWriter;
import net.minestom.server.utils.cache.CacheablePacket; import net.minestom.server.utils.cache.CacheablePacket;
@ -23,7 +24,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound; import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
public class ChunkDataPacket implements ServerPacket, CacheablePacket { public class ChunkDataPacket implements ServerPacket, CacheablePacket {
@ -63,13 +63,17 @@ public class ChunkDataPacket implements ServerPacket, CacheablePacket {
writer.writeBoolean(fullChunk); writer.writeBoolean(fullChunk);
int mask = 0; 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++) { for (byte i = 0; i < CHUNK_SECTION_COUNT; i++) {
if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) { if (fullChunk || (sections.length == CHUNK_SECTION_COUNT && sections[i] != 0)) {
final long[] section = paletteStorage.getSectionBlocks()[i]; final Section section = paletteStorage.getSections()[i];
if (section.length > 0) { // section contains at least one block if (section == null) {
// Section not loaded
continue;
}
if (section.getBlocks().length > 0) { // section contains at least one block
mask |= 1 << i; 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; package net.minestom.server.utils;
import io.netty.buffer.ByteBuf; 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 net.minestom.server.utils.binary.BinaryWriter;
import java.util.UUID; import java.util.UUID;
@ -92,27 +94,32 @@ public final class Utils {
return new UUID(uuidMost, uuidLeast); 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; /*short count = 0;
for (short id : blocksId) for (short id : blocksId)
if (id != 0) if (id != 0)
count++;*/ count++;*/
final int bitsPerEntry = section.getBitsPerEntry();
//buffer.writeShort(count); //buffer.writeShort(count);
// TODO count blocks
buffer.writeShort(200); buffer.writeShort(200);
buffer.writeByte((byte) bitsPerEntry); buffer.writeByte((byte) bitsPerEntry);
// Palette // Palette
if (bitsPerEntry < 9) { if (bitsPerEntry < 9) {
// Palette has to exist // Palette has to exist
writeVarIntBuf(buffer, palette.length); final Short2ShortLinkedOpenHashMap paletteBlockMap = section.getPaletteBlockMap();
for (short paletteValue : palette) { writeVarIntBuf(buffer, paletteBlockMap.size());
for (short paletteValue : paletteBlockMap.values()) {
writeVarIntBuf(buffer, paletteValue); writeVarIntBuf(buffer, paletteValue);
} }
} }
writeVarIntBuf(buffer, blocksId.length); final long[] blocks = section.getBlocks();
for (long datum : blocksId) { writeVarIntBuf(buffer, blocks.length);
for (long datum : blocks) {
buffer.writeLong(datum); buffer.writeLong(datum);
} }
} }

View File

@ -5,6 +5,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.IntFunction;
/** /**
* Convenient interface to deep-copy single object or collections. * Convenient interface to deep-copy single object or collections.
@ -14,15 +15,23 @@ import java.util.concurrent.CopyOnWriteArrayList;
public final class CloneUtils { public final class CloneUtils {
@Nullable @Nullable
public static <T extends PublicCloneable> T optionalClone(@Nullable T object) { public static <T extends PublicCloneable<T>> T optionalClone(@Nullable T object) {
return object != null ? (T) object.clone() : null; return object != null ? object.clone() : null;
} }
@NotNull @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<>(); CopyOnWriteArrayList<T> result = new CopyOnWriteArrayList<>();
for (T element : list) { 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; return result;
} }