mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-14 20:21:53 +01: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);
|
||||
}
|
||||
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);
|
||||
// Handler
|
||||
@ -113,9 +113,9 @@ public class DynamicChunk extends Chunk {
|
||||
// Retrieve the block from state id
|
||||
final Section section = getOptionalSection(y);
|
||||
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
|
||||
return Objects.requireNonNullElse(Block.fromStateId(blockStateId), Block.AIR);
|
||||
return Objects.requireNonNullElse(Block.fromStateId((short) blockStateId), Block.AIR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -176,10 +176,10 @@ public class DynamicChunk extends Chunk {
|
||||
final BinaryWriter writer = new BinaryWriter();
|
||||
for (int i = 0; i < 16; i++) { // TODO: variable section count
|
||||
final Section section = Objects.requireNonNullElseGet(sectionMap.get(i), Section::new);
|
||||
final Palette blockPalette = section.getPalette();
|
||||
writer.writeShort(blockPalette.getBlockCount());
|
||||
final Palette blockPalette = section.blockPalette();
|
||||
writer.writeShort((short) blockPalette.count());
|
||||
blockPalette.write(writer); // Blocks
|
||||
new Palette(2, 2).write(writer); // Biomes
|
||||
section.biomePalette().write(writer); // Biomes
|
||||
}
|
||||
return new ChunkDataPacket(chunkX, chunkZ,
|
||||
new ChunkData(heightmapsNBT, writer.toByteArray(), entries),
|
||||
@ -221,6 +221,14 @@ public class DynamicChunk extends Chunk {
|
||||
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) {
|
||||
final int sectionIndex = ChunkUtils.getSectionAt(y);
|
||||
return sectionMap.get(sectionIndex);
|
||||
|
@ -1,34 +1,33 @@
|
||||
package net.minestom.server.instance;
|
||||
|
||||
import net.minestom.server.instance.palette.Palette;
|
||||
import net.minestom.server.utils.clone.PublicCloneable;
|
||||
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 byte[] skyLight = new byte[0];
|
||||
private byte[] blockLight = new byte[0];
|
||||
|
||||
private Section(Palette palette) {
|
||||
this.palette = palette;
|
||||
private Section(Palette blockPalette, Palette biomePalette,
|
||||
byte[] skyLight, byte[] blockLight) {
|
||||
this.blockPalette = blockPalette;
|
||||
this.biomePalette = biomePalette;
|
||||
this.skyLight = skyLight;
|
||||
this.blockLight = blockLight;
|
||||
}
|
||||
|
||||
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) {
|
||||
x = toChunkRelativeCoordinate(x);
|
||||
z = toChunkRelativeCoordinate(z);
|
||||
return palette.getBlockAt(x, y, z);
|
||||
public Palette blockPalette() {
|
||||
return blockPalette;
|
||||
}
|
||||
|
||||
public void setBlockAt(int x, int y, int z, short blockId) {
|
||||
x = toChunkRelativeCoordinate(x);
|
||||
z = toChunkRelativeCoordinate(z);
|
||||
palette.setBlockAt(x, y, z, blockId);
|
||||
public Palette biomePalette() {
|
||||
return biomePalette;
|
||||
}
|
||||
|
||||
public byte[] getSkyLight() {
|
||||
@ -47,34 +46,16 @@ public class Section implements PublicCloneable<Section> {
|
||||
this.blockLight = blockLight;
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
palette.clean();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
palette.clear();
|
||||
}
|
||||
|
||||
public Palette getPalette() {
|
||||
return palette;
|
||||
this.blockPalette = Palette.blocks();
|
||||
this.biomePalette = Palette.biomes();
|
||||
this.skyLight = new byte[0];
|
||||
this.blockLight = new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Section clone() {
|
||||
return new Section(palette.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;
|
||||
return new Section(blockPalette.clone(), biomePalette.clone(),
|
||||
skyLight.clone(), blockLight.clone());
|
||||
}
|
||||
}
|
||||
|
@ -1,325 +1,41 @@
|
||||
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.clone.PublicCloneable;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
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>
|
||||
* 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 final class Palette implements Writeable, PublicCloneable<Palette> {
|
||||
public interface Palette extends Writeable {
|
||||
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();
|
||||
|
||||
/**
|
||||
* The maximum bits per entry value which allow for a data palette.
|
||||
*/
|
||||
public final static int PALETTE_MAXIMUM_BITS = 8;
|
||||
long[] data();
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@NotNull Palette clone();
|
||||
}
|
||||
|
@ -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