mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-09 09:57:45 +01:00
Improve block placement performance, more abstraction for future features
This commit is contained in:
parent
933b2663eb
commit
71d0d06f90
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
297
src/main/java/net/minestom/server/instance/palette/Section.java
Normal file
297
src/main/java/net/minestom/server/instance/palette/Section.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user