mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-29 15:07:36 +02:00
Initial palette rework
Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
parent
41563ba973
commit
5f0c4aedbe
@ -59,7 +59,7 @@ public class DynamicChunk extends Chunk {
|
|||||||
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
|
columnarOcclusionFieldList.onBlockChanged(x, y, z, blockDescription, 0);
|
||||||
}
|
}
|
||||||
Section section = getSection(ChunkUtils.getSectionAt(y));
|
Section section = getSection(ChunkUtils.getSectionAt(y));
|
||||||
section.setBlockAt(x, y, z, block.stateId());
|
section.blockPalette().set(toChunkRelativeCoordinate(x), y, toChunkRelativeCoordinate(z), block.stateId());
|
||||||
|
|
||||||
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
final int index = ChunkUtils.getBlockIndex(x, y, z);
|
||||||
// Handler
|
// Handler
|
||||||
@ -113,9 +113,9 @@ public class DynamicChunk extends Chunk {
|
|||||||
// Retrieve the block from state id
|
// Retrieve the block from state id
|
||||||
final Section section = getOptionalSection(y);
|
final Section section = getOptionalSection(y);
|
||||||
if (section == null) return Block.AIR; // Section is unloaded
|
if (section == null) return Block.AIR; // Section is unloaded
|
||||||
final short blockStateId = section.getBlockAt(x, y, z);
|
final int blockStateId = section.blockPalette().get(toChunkRelativeCoordinate(x), y, toChunkRelativeCoordinate(z));
|
||||||
if (blockStateId == -1) return Block.AIR; // Section is empty
|
if (blockStateId == -1) return Block.AIR; // Section is empty
|
||||||
return Objects.requireNonNullElse(Block.fromStateId(blockStateId), Block.AIR);
|
return Objects.requireNonNullElse(Block.fromStateId((short) blockStateId), Block.AIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -176,10 +176,10 @@ public class DynamicChunk extends Chunk {
|
|||||||
final BinaryWriter writer = new BinaryWriter();
|
final BinaryWriter writer = new BinaryWriter();
|
||||||
for (int i = 0; i < 16; i++) { // TODO: variable section count
|
for (int i = 0; i < 16; i++) { // TODO: variable section count
|
||||||
final Section section = Objects.requireNonNullElseGet(sectionMap.get(i), Section::new);
|
final Section section = Objects.requireNonNullElseGet(sectionMap.get(i), Section::new);
|
||||||
final Palette blockPalette = section.getPalette();
|
final Palette blockPalette = section.blockPalette();
|
||||||
writer.writeShort(blockPalette.getBlockCount());
|
writer.writeShort((short) blockPalette.count());
|
||||||
blockPalette.write(writer); // Blocks
|
blockPalette.write(writer); // Blocks
|
||||||
new Palette(2, 2).write(writer); // Biomes
|
section.biomePalette().write(writer); // Biomes
|
||||||
}
|
}
|
||||||
return new ChunkDataPacket(chunkX, chunkZ,
|
return new ChunkDataPacket(chunkX, chunkZ,
|
||||||
new ChunkData(heightmapsNBT, writer.toByteArray(), entries),
|
new ChunkData(heightmapsNBT, writer.toByteArray(), entries),
|
||||||
@ -221,6 +221,14 @@ public class DynamicChunk extends Chunk {
|
|||||||
skyLights, blockLights);
|
skyLights, blockLights);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int toChunkRelativeCoordinate(int xz) {
|
||||||
|
xz %= 16;
|
||||||
|
if (xz < 0) {
|
||||||
|
xz += Chunk.CHUNK_SECTION_SIZE;
|
||||||
|
}
|
||||||
|
return xz;
|
||||||
|
}
|
||||||
|
|
||||||
private @Nullable Section getOptionalSection(int y) {
|
private @Nullable Section getOptionalSection(int y) {
|
||||||
final int sectionIndex = ChunkUtils.getSectionAt(y);
|
final int sectionIndex = ChunkUtils.getSectionAt(y);
|
||||||
return sectionMap.get(sectionIndex);
|
return sectionMap.get(sectionIndex);
|
||||||
|
@ -1,34 +1,33 @@
|
|||||||
package net.minestom.server.instance;
|
package net.minestom.server.instance;
|
||||||
|
|
||||||
import net.minestom.server.instance.palette.Palette;
|
import net.minestom.server.instance.palette.Palette;
|
||||||
import net.minestom.server.utils.clone.PublicCloneable;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class Section implements PublicCloneable<Section> {
|
public final class Section {
|
||||||
|
private Palette blockPalette;
|
||||||
|
private Palette biomePalette;
|
||||||
|
private byte[] skyLight;
|
||||||
|
private byte[] blockLight;
|
||||||
|
|
||||||
private final Palette palette;
|
private Section(Palette blockPalette, Palette biomePalette,
|
||||||
|
byte[] skyLight, byte[] blockLight) {
|
||||||
private byte[] skyLight = new byte[0];
|
this.blockPalette = blockPalette;
|
||||||
private byte[] blockLight = new byte[0];
|
this.biomePalette = biomePalette;
|
||||||
|
this.skyLight = skyLight;
|
||||||
private Section(Palette palette) {
|
this.blockLight = blockLight;
|
||||||
this.palette = palette;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Section() {
|
public Section() {
|
||||||
this(new Palette(8, 2));
|
this(Palette.blocks(), Palette.biomes(),
|
||||||
|
new byte[0], new byte[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getBlockAt(int x, int y, int z) {
|
public Palette blockPalette() {
|
||||||
x = toChunkRelativeCoordinate(x);
|
return blockPalette;
|
||||||
z = toChunkRelativeCoordinate(z);
|
|
||||||
return palette.getBlockAt(x, y, z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBlockAt(int x, int y, int z, short blockId) {
|
public Palette biomePalette() {
|
||||||
x = toChunkRelativeCoordinate(x);
|
return biomePalette;
|
||||||
z = toChunkRelativeCoordinate(z);
|
|
||||||
palette.setBlockAt(x, y, z, blockId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getSkyLight() {
|
public byte[] getSkyLight() {
|
||||||
@ -47,34 +46,16 @@ public class Section implements PublicCloneable<Section> {
|
|||||||
this.blockLight = blockLight;
|
this.blockLight = blockLight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clean() {
|
|
||||||
palette.clean();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
palette.clear();
|
this.blockPalette = Palette.blocks();
|
||||||
}
|
this.biomePalette = Palette.biomes();
|
||||||
|
this.skyLight = new byte[0];
|
||||||
public Palette getPalette() {
|
this.blockLight = new byte[0];
|
||||||
return palette;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Section clone() {
|
public @NotNull Section clone() {
|
||||||
return new Section(palette.clone());
|
return new Section(blockPalette.clone(), biomePalette.clone(),
|
||||||
}
|
skyLight.clone(), blockLight.clone());
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a coordinate that is relative to a chunk the provided coordinate is in.
|
|
||||||
*
|
|
||||||
* @param xz the world coordinate
|
|
||||||
* @return a coordinate relative to the closest chunk
|
|
||||||
*/
|
|
||||||
private static int toChunkRelativeCoordinate(int xz) {
|
|
||||||
xz %= 16;
|
|
||||||
if (xz < 0) {
|
|
||||||
xz += Chunk.CHUNK_SECTION_SIZE;
|
|
||||||
}
|
|
||||||
return xz;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,325 +1,41 @@
|
|||||||
package net.minestom.server.instance.palette;
|
package net.minestom.server.instance.palette;
|
||||||
|
|
||||||
import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import net.minestom.server.utils.MathUtils;
|
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
|
||||||
import net.minestom.server.utils.binary.Writeable;
|
import net.minestom.server.utils.binary.Writeable;
|
||||||
import net.minestom.server.utils.clone.PublicCloneable;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a palette storing a complete chunk section.
|
* Represents a palette used to store blocks & biomes.
|
||||||
* <p>
|
* <p>
|
||||||
* 0 is always interpreted as being air, reason being that the block array will be filled with it during initialization.
|
* 0 is the default value.
|
||||||
*/
|
*/
|
||||||
@ApiStatus.Internal
|
public interface Palette extends Writeable {
|
||||||
public final class Palette implements Writeable, PublicCloneable<Palette> {
|
static Palette blocks() {
|
||||||
|
return new PaletteImpl(16 * 16 * 16, 8, 8, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Palette biomes() {
|
||||||
|
return new PaletteImpl(4 * 4 * 4, 2, 2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get(int x, int y, int z);
|
||||||
|
|
||||||
|
void set(int x, int y, int z, int value);
|
||||||
|
|
||||||
|
int count();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum bits per entry value.
|
* Returns the number of bits used per entry.
|
||||||
*/
|
*/
|
||||||
public final static int MAXIMUM_BITS_PER_ENTRY = 15;
|
int bitsPerEntry();
|
||||||
|
|
||||||
|
int maxBitsPerEntry();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The minimum bits per entry value.
|
* Returns the number of entries in this palette.
|
||||||
*/
|
*/
|
||||||
public final static int MINIMUM_BITS_PER_ENTRY = 4;
|
int size();
|
||||||
|
|
||||||
/**
|
long[] data();
|
||||||
* The maximum bits per entry value which allow for a data palette.
|
|
||||||
*/
|
|
||||||
public final static int PALETTE_MAXIMUM_BITS = 8;
|
|
||||||
|
|
||||||
/**
|
@NotNull Palette clone();
|
||||||
* 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 static final Short2ShortOpenHashMap MAP_TEMPLATE = new Short2ShortOpenHashMap(CHUNK_SECTION_SIZE);
|
|
||||||
|
|
||||||
static {
|
|
||||||
MAP_TEMPLATE.put((short) 0, (short) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long[] blocks;
|
|
||||||
|
|
||||||
// palette index = block id
|
|
||||||
private short[] paletteBlockArray;
|
|
||||||
// block id = palette index
|
|
||||||
private Short2ShortOpenHashMap blockPaletteMap;
|
|
||||||
|
|
||||||
private int bitsPerEntry;
|
|
||||||
private final int bitsIncrement;
|
|
||||||
|
|
||||||
private int valuesPerLong;
|
|
||||||
private boolean hasPalette;
|
|
||||||
private int lastPaletteIndex;
|
|
||||||
|
|
||||||
private short blockCount = 0;
|
|
||||||
|
|
||||||
public Palette(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) {
|
|
||||||
final boolean placedAir = blockId == 0;
|
|
||||||
if (blocks.length == 0) {
|
|
||||||
if (placedAir) {
|
|
||||||
// Section is empty and method is trying to place an air block, stop unnecessary computation
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Initialize the section
|
|
||||||
this.blocks = new long[(BLOCK_COUNT + valuesPerLong - 1) / 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];
|
|
||||||
|
|
||||||
final long oldBlock = block >> bitIndex & clear;
|
|
||||||
if (oldBlock == blockId)
|
|
||||||
return; // Trying to place the same block
|
|
||||||
final boolean currentAir = oldBlock == 0;
|
|
||||||
|
|
||||||
final long indexClear = clear << bitIndex;
|
|
||||||
block |= indexClear;
|
|
||||||
block ^= indexClear;
|
|
||||||
block |= (long) blockId << bitIndex;
|
|
||||||
|
|
||||||
if (currentAir != placedAir) {
|
|
||||||
// Block count changed
|
|
||||||
this.blockCount += (short) (currentAir ? 1 : -1);
|
|
||||||
}
|
|
||||||
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 -1;
|
|
||||||
}
|
|
||||||
final int sectionIdentifier = getSectionIndex(x, y, z);
|
|
||||||
|
|
||||||
final int index = sectionIdentifier / valuesPerLong;
|
|
||||||
final int bitIndex = sectionIdentifier % valuesPerLong * bitsPerEntry;
|
|
||||||
|
|
||||||
final short value = (short) (blocks[index] >> bitIndex & MAGIC_MASKS[bitsPerEntry]);
|
|
||||||
// Change to palette value and return
|
|
||||||
return hasPalette ? paletteBlockArray[value] : 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
|
|
||||||
*/
|
|
||||||
public void resize(int newBitsPerEntry) {
|
|
||||||
newBitsPerEntry = fixBitsPerEntry(newBitsPerEntry);
|
|
||||||
|
|
||||||
Palette palette = new Palette(newBitsPerEntry, bitsIncrement);
|
|
||||||
|
|
||||||
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
|
||||||
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
|
||||||
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
|
||||||
palette.setBlockAt(x, y, z, getBlockAt(x, y, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.paletteBlockArray = palette.paletteBlockArray;
|
|
||||||
this.lastPaletteIndex = palette.lastPaletteIndex;
|
|
||||||
|
|
||||||
this.bitsPerEntry = palette.bitsPerEntry;
|
|
||||||
|
|
||||||
this.valuesPerLong = palette.valuesPerLong;
|
|
||||||
this.hasPalette = palette.hasPalette;
|
|
||||||
|
|
||||||
this.blocks = palette.blocks;
|
|
||||||
this.blockCount = palette.blockCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.paletteBlockArray = new short[1 << bitsPerEntry];
|
|
||||||
this.blockPaletteMap = MAP_TEMPLATE.clone();
|
|
||||||
this.blockCount = 0;
|
|
||||||
this.lastPaletteIndex = 1; // First index is air
|
|
||||||
}
|
|
||||||
|
|
||||||
public long[] getBlocks() {
|
|
||||||
return blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlocks(long[] blocks) {
|
|
||||||
this.blocks = blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the amount of non-air blocks in this section.
|
|
||||||
*
|
|
||||||
* @return The amount of blocks in this section.
|
|
||||||
*/
|
|
||||||
public short getBlockCount() {
|
|
||||||
return blockCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlockCount(short blockCount) {
|
|
||||||
this.blockCount = blockCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short[] getPaletteBlockArray() {
|
|
||||||
return paletteBlockArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLastPaletteIndex() {
|
|
||||||
return lastPaletteIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
final short value = blockPaletteMap.getOrDefault(blockId, (short) -1);
|
|
||||||
if (value != -1) return value;
|
|
||||||
|
|
||||||
if (lastPaletteIndex >= paletteBlockArray.length) {
|
|
||||||
// Palette is full, must resize
|
|
||||||
resize(bitsPerEntry + bitsIncrement);
|
|
||||||
if (!hasPalette) return blockId;
|
|
||||||
}
|
|
||||||
final short paletteIndex = (short) lastPaletteIndex++;
|
|
||||||
this.paletteBlockArray[paletteIndex] = blockId;
|
|
||||||
this.blockPaletteMap.put(blockId, paletteIndex);
|
|
||||||
return paletteIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 = Math.floorMod(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(@NotNull BinaryWriter writer) {
|
|
||||||
writer.writeByte((byte) bitsPerEntry);
|
|
||||||
// Palette
|
|
||||||
if (bitsPerEntry < 9) {
|
|
||||||
// Palette has to exist
|
|
||||||
final short[] paletteBlockArray = getPaletteBlockArray();
|
|
||||||
final int paletteSize = getLastPaletteIndex() + 1;
|
|
||||||
writer.writeVarInt(paletteSize);
|
|
||||||
for (int i = 0; i < paletteSize; i++) {
|
|
||||||
writer.writeVarInt(paletteBlockArray[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Raw
|
|
||||||
writer.writeVarInt(blocks.length);
|
|
||||||
for (long datum : blocks) {
|
|
||||||
writer.writeLong(datum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Palette clone() {
|
|
||||||
try {
|
|
||||||
Palette palette = (Palette) super.clone();
|
|
||||||
palette.blocks = blocks.clone();
|
|
||||||
palette.paletteBlockArray = paletteBlockArray.clone();
|
|
||||||
palette.blockPaletteMap = blockPaletteMap.clone();
|
|
||||||
palette.blockCount = blockCount;
|
|
||||||
return palette;
|
|
||||||
} catch (CloneNotSupportedException e) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
throw new IllegalStateException("Weird thing happened");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
package net.minestom.server.instance.palette;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.instance.Chunk;
|
||||||
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE;
|
||||||
|
|
||||||
|
final class PaletteImpl implements Palette {
|
||||||
|
// 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};
|
||||||
|
|
||||||
|
// Specific to this palette type
|
||||||
|
private final int size;
|
||||||
|
private final int maxBitsPerEntry;
|
||||||
|
|
||||||
|
private int bitsPerEntry;
|
||||||
|
private final int bitsIncrement;
|
||||||
|
|
||||||
|
private int valuesPerLong;
|
||||||
|
private boolean hasPalette;
|
||||||
|
private int lastPaletteIndex = 1; // First index is air
|
||||||
|
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
private long[] values = new long[0];
|
||||||
|
// palette index = value
|
||||||
|
private int[] paletteToValueArray;
|
||||||
|
// value = palette index
|
||||||
|
private Int2IntOpenHashMap valueToPaletteMap = new Int2IntOpenHashMap();
|
||||||
|
|
||||||
|
PaletteImpl(int size, int maxBitsPerEntry, int bitsPerEntry, int bitsIncrement) {
|
||||||
|
this.size = size;
|
||||||
|
this.maxBitsPerEntry = maxBitsPerEntry;
|
||||||
|
|
||||||
|
this.bitsPerEntry = bitsPerEntry;
|
||||||
|
this.bitsIncrement = bitsIncrement;
|
||||||
|
|
||||||
|
this.valuesPerLong = Long.SIZE / bitsPerEntry;
|
||||||
|
this.hasPalette = bitsPerEntry <= maxBitsPerEntry;
|
||||||
|
|
||||||
|
this.paletteToValueArray = new int[1 << bitsPerEntry];
|
||||||
|
|
||||||
|
this.valueToPaletteMap.put(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int get(int x, int y, int z) {
|
||||||
|
if (values.length == 0) {
|
||||||
|
// Section is not loaded, can only be air
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
final int sectionIdentifier = getSectionIndex(x, y, z);
|
||||||
|
final int index = sectionIdentifier / valuesPerLong;
|
||||||
|
final int bitIndex = sectionIdentifier % valuesPerLong * bitsPerEntry;
|
||||||
|
final short value = (short) (values[index] >> bitIndex & MAGIC_MASKS[bitsPerEntry]);
|
||||||
|
// Change to palette value and return
|
||||||
|
return hasPalette ? paletteToValueArray[value] : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, int value) {
|
||||||
|
final boolean placedAir = value == 0;
|
||||||
|
if (values.length == 0) {
|
||||||
|
if (placedAir) {
|
||||||
|
// Section is empty and method is trying to place an air block, stop unnecessary computation
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Initialize the section
|
||||||
|
this.values = new long[(size + valuesPerLong - 1) / valuesPerLong];
|
||||||
|
}
|
||||||
|
// Change to palette value
|
||||||
|
value = getPaletteIndex(value);
|
||||||
|
final int sectionIndex = getSectionIndex(x, y, z);
|
||||||
|
final int index = sectionIndex / valuesPerLong;
|
||||||
|
final int bitIndex = (sectionIndex % valuesPerLong) * bitsPerEntry;
|
||||||
|
|
||||||
|
long block = values[index];
|
||||||
|
{
|
||||||
|
final long clear = MAGIC_MASKS[bitsPerEntry];
|
||||||
|
|
||||||
|
final long oldBlock = block >> bitIndex & clear;
|
||||||
|
if (oldBlock == value)
|
||||||
|
return; // Trying to place the same block
|
||||||
|
final boolean currentAir = oldBlock == 0;
|
||||||
|
|
||||||
|
final long indexClear = clear << bitIndex;
|
||||||
|
block |= indexClear;
|
||||||
|
block ^= indexClear;
|
||||||
|
block |= (long) value << bitIndex;
|
||||||
|
|
||||||
|
if (currentAir != placedAir) {
|
||||||
|
// Block count changed
|
||||||
|
this.count += currentAir ? 1 : -1;
|
||||||
|
}
|
||||||
|
values[index] = block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int count() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int bitsPerEntry() {
|
||||||
|
return bitsPerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int maxBitsPerEntry() {
|
||||||
|
return maxBitsPerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long[] data() {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Palette clone() {
|
||||||
|
try {
|
||||||
|
PaletteImpl palette = (PaletteImpl) super.clone();
|
||||||
|
palette.values = values.clone();
|
||||||
|
palette.paletteToValueArray = paletteToValueArray.clone();
|
||||||
|
palette.valueToPaletteMap = valueToPaletteMap.clone();
|
||||||
|
palette.count = count;
|
||||||
|
return palette;
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
throw new IllegalStateException("Weird thing happened");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
|
writer.writeByte((byte) bitsPerEntry);
|
||||||
|
// Palette
|
||||||
|
if (bitsPerEntry < 9) {
|
||||||
|
// Palette has to exist
|
||||||
|
final int paletteSize = lastPaletteIndex + 1;
|
||||||
|
writer.writeVarInt(paletteSize);
|
||||||
|
for (int i = 0; i < paletteSize; i++) {
|
||||||
|
writer.writeVarInt(paletteToValueArray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Raw
|
||||||
|
writer.writeVarInt(values.length);
|
||||||
|
for (long datum : values) {
|
||||||
|
writer.writeLong(datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int fixBitsPerEntry(int bitsPerEntry) {
|
||||||
|
return bitsPerEntry > maxBitsPerEntry ? 15 : bitsPerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resize(int newBitsPerEntry) {
|
||||||
|
newBitsPerEntry = fixBitsPerEntry(newBitsPerEntry);
|
||||||
|
PaletteImpl palette = new PaletteImpl(size, maxBitsPerEntry, newBitsPerEntry, bitsIncrement);
|
||||||
|
for (int y = 0; y < Chunk.CHUNK_SECTION_SIZE; y++) {
|
||||||
|
for (int x = 0; x < Chunk.CHUNK_SIZE_X; x++) {
|
||||||
|
for (int z = 0; z < Chunk.CHUNK_SIZE_Z; z++) {
|
||||||
|
palette.set(x, y, z, get(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.paletteToValueArray = palette.paletteToValueArray;
|
||||||
|
this.lastPaletteIndex = palette.lastPaletteIndex;
|
||||||
|
|
||||||
|
this.bitsPerEntry = palette.bitsPerEntry;
|
||||||
|
|
||||||
|
this.valuesPerLong = palette.valuesPerLong;
|
||||||
|
this.hasPalette = palette.hasPalette;
|
||||||
|
|
||||||
|
this.values = palette.values;
|
||||||
|
this.count = palette.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPaletteIndex(int value) {
|
||||||
|
if (!hasPalette) return value;
|
||||||
|
final int lookup = valueToPaletteMap.getOrDefault(value, (short) -1);
|
||||||
|
if (lookup != -1) return lookup;
|
||||||
|
|
||||||
|
if (lastPaletteIndex >= paletteToValueArray.length) {
|
||||||
|
// Palette is full, must resize
|
||||||
|
resize(bitsPerEntry + bitsIncrement);
|
||||||
|
if (!hasPalette) return value;
|
||||||
|
}
|
||||||
|
final int paletteIndex = lastPaletteIndex++;
|
||||||
|
this.paletteToValueArray[paletteIndex] = value;
|
||||||
|
this.valueToPaletteMap.put(value, paletteIndex);
|
||||||
|
return paletteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getSectionIndex(int x, int y, int z) {
|
||||||
|
y = Math.floorMod(y, CHUNK_SECTION_SIZE);
|
||||||
|
return y << 8 | z << 4 | x;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user