Start work on chunks

- There are probably many errors and issues with this code (some dumb ones probably). As of right now it crashes the client.
This commit is contained in:
RednedEpic 2019-08-30 16:47:33 -05:00
parent bf8da2a3ad
commit 1c1785ac51
18 changed files with 10934 additions and 9649 deletions

View file

@ -72,15 +72,9 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.nukkitx.protocol</groupId> <groupId>com.nukkitx</groupId>
<artifactId>bedrock-v354</artifactId> <artifactId>fastutil-lite</artifactId>
<version>2.1.2</version> <version>8.1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v340</artifactId>
<version>2.1.2</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -43,7 +43,6 @@ import org.geysermc.connector.console.GeyserLogger;
import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.metrics.Metrics;
import org.geysermc.connector.network.ConnectorServerEventHandler; import org.geysermc.connector.network.ConnectorServerEventHandler;
import org.geysermc.connector.network.remote.RemoteJavaServer; import org.geysermc.connector.network.remote.RemoteJavaServer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.TranslatorsInit; import org.geysermc.connector.network.translators.TranslatorsInit;
import org.geysermc.connector.plugin.GeyserPluginLoader; import org.geysermc.connector.plugin.GeyserPluginLoader;
import org.geysermc.connector.plugin.GeyserPluginManager; import org.geysermc.connector.plugin.GeyserPluginManager;

View file

@ -27,8 +27,10 @@ package org.geysermc.connector.entity.attribute;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
@Getter @Getter
@Setter
@AllArgsConstructor @AllArgsConstructor
public class Attribute { public class Attribute {

View file

@ -98,6 +98,9 @@ public class ItemTranslator {
public static BedrockItem getBedrockBlock(BlockState stack) { public static BedrockItem getBedrockBlock(BlockState stack) {
Map<String, Object> m = Remapper.JAVA_TO_BEDROCK_BLOCKS.get(stack.getId()); Map<String, Object> m = Remapper.JAVA_TO_BEDROCK_BLOCKS.get(stack.getId());
if (m == null)
return BedrockItem.AIR;
return new BedrockItem((String) m.get("name"), (Integer) m.get("id"), (Integer) m.get("data")); return new BedrockItem((String) m.get("name"), (Integer) m.get("id"), (Integer) m.get("data"));
} }

View file

@ -1,25 +1,55 @@
package org.geysermc.connector.network.translators.java.world; package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
import com.github.steveice10.packetlib.packet.Packet;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.geysermc.connector.console.GeyserLogger; import org.geysermc.connector.console.GeyserLogger;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.utils.Chunks; import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.world.chunk.ChunkSection;
public class JavaChunkDataPacket extends PacketTranslator<ServerChunkDataPacket> { public class JavaChunkDataPacket extends PacketTranslator<ServerChunkDataPacket> {
@Override @Override
public void translate(ServerChunkDataPacket packet, GeyserSession session) { public void translate(ServerChunkDataPacket packet, GeyserSession session) {
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
Chunks.ChunkData data = Chunks.getData(packet.getColumn()); try {
levelChunkPacket.setSubChunksLength(data.count); byte[] buffer = new byte[32];
levelChunkPacket.setData(data.bytes); ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
levelChunkPacket.setChunkX(packet.getColumn().getX());
levelChunkPacket.setChunkZ(packet.getColumn().getZ()); int count = 0;
ChunkSection[] sections = chunkData.sections;
for (int i = sections.length - 1; i >= 0; i--) {
if (sections[i].isEmpty())
continue;
count = i + 1;
break;
}
for (int i = 0; i < count; i++) {
ChunkUtils.putBytes(count, buffer, new byte[]{(byte) 0});
ChunkSection section = chunkData.sections[i];
ByteBuf byteBuf = Unpooled.buffer();
section.writeToNetwork(byteBuf);
byte[] byteData = byteBuf.array();
ChunkUtils.putBytes(count, buffer, byteData);
}
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
levelChunkPacket.setSubChunksLength(16);
levelChunkPacket.setCachingEnabled(true);
levelChunkPacket.setChunkX(packet.getColumn().getX());
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
levelChunkPacket.setData(buffer);
session.getUpstream().sendPacket(levelChunkPacket);
} catch (Exception ex) {
ex.printStackTrace();
}
GeyserLogger.DEFAULT.info("Sent chunk packet!"); GeyserLogger.DEFAULT.info("Sent chunk packet!");
session.getUpstream().sendPacket(levelChunkPacket);
} }
} }

View file

@ -0,0 +1,91 @@
package org.geysermc.connector.utils;
import com.github.steveice10.mc.protocol.data.game.chunk.BlockStorage;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import org.geysermc.connector.network.translators.TranslatorsInit;
import org.geysermc.connector.network.translators.item.BedrockItem;
import org.geysermc.connector.world.chunk.ChunkSection;
import java.util.Arrays;
public class ChunkUtils {
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
public static ChunkData translateToBedrock(Column column) {
ChunkData chunkData = new ChunkData();
chunkData.sections = new ChunkSection[16];
for (int i = 0; i < 16; i++) {
chunkData.sections[i] = new ChunkSection();
}
for (int y = 0; y < 256; y++) {
int chunkY = y >> 4;
Chunk chunk = null;
try {
chunk = column.getChunks()[chunkY];
} catch (Exception ex) {
ex.printStackTrace();
}
if (chunk == null || chunk.isEmpty())
continue;
BlockStorage storage = chunk.getBlocks();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
BlockState block = storage.get(x, y & 0xF, z);
BedrockItem bedrockBlock = TranslatorsInit.getItemTranslator().getBedrockBlock(block);
ChunkSection section = chunkData.sections[chunkY];
org.geysermc.connector.world.chunk.BlockStorage blockStorage = new org.geysermc.connector.world.chunk.BlockStorage();
blockStorage.setFullBlock(ChunkSection.blockPosition(x, y, z), bedrockBlock.getId());
section.getBlockStorageArray()[0] = blockStorage;
section.getBlockStorageArray()[1] = blockStorage;
}
}
}
return chunkData;
}
public static final class ChunkData {
public ChunkSection[] sections;
}
public static void putBytes(int count, byte[] buffer, byte[] bytes) {
if (bytes == null) {
return;
}
int minCapacity = count + bytes.length;
if ((minCapacity) - buffer.length > 0) {
int oldCapacity = buffer.length;
int newCapacity = oldCapacity << 1;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
buffer = Arrays.copyOf(buffer, newCapacity);
}
System.arraycopy(bytes, 0, buffer, count, bytes.length);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
}

View file

@ -1,81 +0,0 @@
package org.geysermc.connector.utils;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import gnu.trove.list.TByteList;
import gnu.trove.list.array.TByteArrayList;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import java.util.List;
import java.util.Objects;
public class Chunks {
public static ChunkData getData(Column c) {
Objects.requireNonNull(c);
int count = 0;
for(Chunk chunk : c.getChunks()) {
if(chunk != null) {
count++;
}
}
int block = 0;
TByteList list = new TByteArrayList(4096 * 4);
for(int i = 0; i < 256; i++) {
list.add((byte) 0);
}
for(Chunk chunk : c.getChunks()) {
if (chunk != null) {
list.add((byte) 0);
for (int x = 0; x < 16; x++) {
for (int y = 0; x < 16; x++) {
for (int z = 0; x < 16; x++) {
try {
list.add((byte) ItemTranslator.getBedrockBlock(chunk.getBlocks().get(x, y, z)).getId());
} catch (NullPointerException e) {
list.add((byte) 0);
}
block++;
}
}
}
for (int x = 0; x < 16; x++) {
for (int y = 0; x < 16; x++) {
for (int z = 0; x < 16; x++) {
try {
list.add((byte) ItemTranslator.getBedrockBlock(chunk.getBlocks().get(x, y, z)).getData());
} catch (NullPointerException e) {
list.add((byte) 0);
}
block++;
}
}
}
}
}
list.add((byte) 0);
list.add((byte) 0);
return new ChunkData(count, list.toArray());
}
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public static final class ChunkData {
public final int count;
public final byte[] bytes;
}
}

View file

@ -0,0 +1,9 @@
package org.geysermc.connector.utils;
public class MathUtils {
public static int ceil(float floatNumber) {
int truncated = (int) floatNumber;
return floatNumber > truncated ? truncated + 1 : truncated;
}
}

View file

@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import org.geysermc.connector.network.translators.item.BedrockItem; import org.geysermc.connector.network.translators.item.BedrockItem;
import org.geysermc.connector.network.translators.item.JavaItem; import org.geysermc.connector.network.translators.item.JavaItem;
import org.geysermc.connector.world.GlobalBlockPalette;
import java.io.InputStream; import java.io.InputStream;
import java.util.*; import java.util.*;
@ -38,6 +39,7 @@ public class Toolbox {
ByteBuf b = Unpooled.buffer(); ByteBuf b = Unpooled.buffer();
VarInts.writeUnsignedInt(b, entries.size()); VarInts.writeUnsignedInt(b, entries.size());
for (Map<String, Object> e : entries) { for (Map<String, Object> e : entries) {
GlobalBlockPalette.registerMapping((int) e.get("id"));
BedrockUtils.writeString(b, (String) e.get("name")); BedrockUtils.writeString(b, (String) e.get("name"));
b.writeShortLE((int) e.get("data")); b.writeShortLE((int) e.get("data"));
b.writeShortLE((int) e.get("id")); b.writeShortLE((int) e.get("id"));

View file

@ -0,0 +1,42 @@
package org.geysermc.connector.world;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public class GlobalBlockPalette {
private static final Int2IntArrayMap legacyToRuntimeId = new Int2IntArrayMap();
private static final Int2IntArrayMap runtimeIdToLegacy = new Int2IntArrayMap();
private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0);
static {
legacyToRuntimeId.defaultReturnValue(-1);
runtimeIdToLegacy.defaultReturnValue(-1);
}
public static int getOrCreateRuntimeId(int id, int meta) {
return getOrCreateRuntimeId((id << 4) | meta);
}
public static int getOrCreateRuntimeId(int legacyId) throws NoSuchElementException {
int runtimeId = legacyToRuntimeId.get(legacyId);
if (!legacyToRuntimeId.containsKey(legacyId) || runtimeId == -1) {
//runtimeId = registerMapping(runtimeIdAllocator.incrementAndGet(), legacyId);
// throw new NoSuchElementException("Unmapped block registered id:" + (legacyId >>> 4) + " meta:" + (legacyId & 0xf));
return 0;
}
return runtimeId;
}
public static int registerMapping(int legacyId) {
int runtimeId = runtimeIdAllocator.getAndIncrement();
runtimeIdToLegacy.put(runtimeId, legacyId);
legacyToRuntimeId.put(legacyId, runtimeId);
return runtimeId;
}
}

View file

@ -0,0 +1,110 @@
package org.geysermc.connector.world.chunk;
import com.nukkitx.network.VarInts;
import gnu.trove.list.array.TIntArrayList;
import io.netty.buffer.ByteBuf;
import org.geysermc.connector.world.GlobalBlockPalette;
import org.geysermc.connector.world.chunk.palette.Palette;
import org.geysermc.connector.world.chunk.palette.PaletteVersion;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public class BlockStorage {
private static final int SIZE = 4096;
private final TIntArrayList ids;
private Palette palette;
public BlockStorage() {
this(PaletteVersion.V2);
}
public BlockStorage(PaletteVersion version) {
this.palette = version.createPalette(SIZE);
this.ids = new TIntArrayList(16, -1);
this.ids.add(0); // Air is at the start of every palette.
}
private BlockStorage(Palette palette, TIntArrayList ids) {
this.ids = ids;
this.palette = palette;
}
public synchronized int getFullBlock(int xzy) {
return this.palette.get(xzy);
}
public synchronized void setFullBlock(int index, int legacyId) {
this.palette.set(index, this.idFor(legacyId));
}
public synchronized void writeToNetwork(ByteBuf buffer) {
buffer.writeByte(getPaletteHeader(palette.getVersion(), true));
for (int word : palette.getWords()) {
buffer.writeIntLE(word);
}
VarInts.writeUnsignedInt(buffer, ids.size());
ids.forEach(id -> {
VarInts.writeUnsignedInt(buffer, id);
return true;
});
}
public synchronized void writeToStorage(ByteBuf buffer) {
buffer.writeByte(getPaletteHeader(palette.getVersion(), false));
for (int word : palette.getWords()) {
buffer.writeIntLE(word);
}
//TODO: Write persistent NBT tags
}
private synchronized void onResize(PaletteVersion version) {
Palette oldPalette = this.palette;
this.palette = version.createPalette(SIZE);
for (int i = 0; i < SIZE; i++) {
this.palette.set(i, oldPalette.get(i));
}
}
private int idFor(int legacyId) {
int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(legacyId);
int index = this.ids.indexOf(runtimeId);
if (index != -1) {
return index;
}
index = this.ids.size();
this.ids.add(runtimeId);
PaletteVersion version = this.palette.getVersion();
if (index > version.getMaxEntryValue()) {
PaletteVersion next = version.next();
if (next != null) {
this.onResize(next);
}
}
return index;
}
private static int getPaletteHeader(PaletteVersion version, boolean runtime) {
return (version.getVersion() << 1) | (runtime ? 1 : 0);
}
public boolean isEmpty() {
for (int word : this.palette.getWords()) {
if (word != 0) {
return false;
}
}
return true;
}
public BlockStorage copy() {
return new BlockStorage(this.palette.copy(), new TIntArrayList(this.ids));
}
}

View file

@ -0,0 +1,128 @@
package org.geysermc.connector.world.chunk;
import com.nukkitx.network.util.Preconditions;
import io.netty.buffer.ByteBuf;
import lombok.Synchronized;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public class ChunkSection {
private static final int CHUNK_SECTION_VERSION = 8;
public static final int SIZE = 4096;
private final BlockStorage[] storage;
private final NibbleArray blockLight;
private final NibbleArray skyLight;
public ChunkSection() {
this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}, new NibbleArray(SIZE),
new NibbleArray(SIZE));
}
public ChunkSection(BlockStorage[] blockStorage) {
this(blockStorage, new NibbleArray(SIZE), new NibbleArray(SIZE));
}
public ChunkSection(BlockStorage[] storage, byte[] blockLight, byte[] skyLight) {
Preconditions.checkNotNull(storage, "storage");
Preconditions.checkArgument(storage.length > 1, "Block storage length must be at least 2");
for (BlockStorage blockStorage : storage) {
Preconditions.checkNotNull(blockStorage, "storage");
}
this.storage = storage;
this.blockLight = new NibbleArray(blockLight);
this.skyLight = new NibbleArray(skyLight);
}
private ChunkSection(BlockStorage[] storage, NibbleArray blockLight, NibbleArray skyLight) {
this.storage = storage;
this.blockLight = blockLight;
this.skyLight = skyLight;
}
public int getFullBlock(int x, int y, int z, int layer) {
checkBounds(x, y, z);
Preconditions.checkElementIndex(layer, this.storage.length);
return this.storage[layer].getFullBlock(blockPosition(x, y, z));
}
public void setFullBlock(int x, int y, int z, int layer, int fullBlock) {
checkBounds(x, y, z);
Preconditions.checkElementIndex(layer, this.storage.length);
this.storage[layer].setFullBlock(blockPosition(x, y, z), fullBlock);
}
@Synchronized("skyLight")
public byte getSkyLight(int x, int y, int z) {
checkBounds(x, y, z);
return this.skyLight.get(blockPosition(x, y, z));
}
@Synchronized("skyLight")
public void setSkyLight(int x, int y, int z, byte val) {
checkBounds(x, y, z);
this.skyLight.set(blockPosition(x, y, z), val);
}
@Synchronized("blockLight")
public byte getBlockLight(int x, int y, int z) {
checkBounds(x, y, z);
return this.blockLight.get(blockPosition(x, y, z));
}
@Synchronized("blockLight")
public void setBlockLight(int x, int y, int z, byte val) {
checkBounds(x, y, z);
this.blockLight.set(blockPosition(x, y, z), val);
}
public void writeToNetwork(ByteBuf buffer) {
buffer.writeByte(CHUNK_SECTION_VERSION);
buffer.writeByte(this.storage.length);
for (BlockStorage blockStorage : this.storage) {
blockStorage.writeToNetwork(buffer);
}
}
public NibbleArray getSkyLightArray() {
return skyLight;
}
public NibbleArray getBlockLightArray() {
return blockLight;
}
public BlockStorage[] getBlockStorageArray() {
return storage;
}
public boolean isEmpty() {
for (BlockStorage blockStorage : this.storage) {
if (!blockStorage.isEmpty()) {
return false;
}
}
return true;
}
public ChunkSection copy() {
BlockStorage[] storage = new BlockStorage[this.storage.length];
for (int i = 0; i < storage.length; i++) {
storage[i] = this.storage[i].copy();
}
return new ChunkSection(storage, skyLight.copy(), blockLight.copy());
}
public static int blockPosition(int x, int y, int z) {
return (x << 8) | (z << 4) | y;
}
private static void checkBounds(int x, int y, int z) {
Preconditions.checkArgument(x >= 0 && x < 16, "x (%s) is not between 0 and 15", x);
Preconditions.checkArgument(y >= 0 && y < 16, "y (%s) is not between 0 and 15", y);
Preconditions.checkArgument(z >= 0 && z < 16, "z (%s) is not between 0 and 15", z);
}
}

View file

@ -0,0 +1,70 @@
package org.geysermc.connector.world.chunk;
import com.nukkitx.network.util.Preconditions;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public class NibbleArray implements Cloneable {
private final byte[] data;
public NibbleArray(int length) {
data = new byte[length / 2];
}
public NibbleArray(byte[] array) {
data = array;
}
public byte get(int index) {
Preconditions.checkElementIndex(index, data.length * 2);
byte val = data[index / 2];
if ((index & 1) == 0) {
return (byte) (val & 0x0f);
} else {
return (byte) ((val & 0xf0) >>> 4);
}
}
public void set(int index, byte value) {
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
Preconditions.checkElementIndex(index, data.length * 2);
value &= 0xf;
int half = index / 2;
byte previous = data[half];
if ((index & 1) == 0) {
data[half] = (byte) (previous & 0xf0 | value);
} else {
data[half] = (byte) (previous & 0x0f | value << 4);
}
}
public void fill(byte value) {
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
value &= 0xf;
for (int i = 0; i < data.length; i++) {
data[i] = (byte) ((value << 4) | value);
}
}
public void copyFrom(byte[] bytes) {
Preconditions.checkNotNull(bytes, "bytes");
Preconditions.checkArgument(bytes.length == data.length, "length of provided byte array is %s but expected %s", bytes.length,
data.length);
System.arraycopy(bytes, 0, data, 0, data.length);
}
public void copyFrom(NibbleArray array) {
Preconditions.checkNotNull(array, "array");
copyFrom(array.data);
}
public byte[] getData() {
return data;
}
public NibbleArray copy() {
return new NibbleArray(getData().clone());
}
}

View file

@ -0,0 +1,77 @@
package org.geysermc.connector.world.chunk.palette;
import com.nukkitx.network.util.Preconditions;
import org.geysermc.connector.utils.MathUtils;
import java.util.Arrays;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public class PaddedPalette implements Palette {
/**
* Array used to store data
*/
private final int[] words;
/**
* Palette version information
*/
private final PaletteVersion version;
/**
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
*/
private final int size;
PaddedPalette(PaletteVersion version, int size, int[] words) {
this.size = size;
this.version = version;
this.words = words;
int expectedWordsLength = MathUtils.ceil((float) size / version.entriesPerWord);
if (words.length != expectedWordsLength) {
throw new IllegalArgumentException("Invalid length given for storage, got: " + words.length +
" but expected: " + expectedWordsLength);
}
}
@Override
public void set(int index, int value) {
Preconditions.checkElementIndex(index, this.size);
Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value");
int arrayIndex = index / this.version.entriesPerWord;
int offset = (index % this.version.entriesPerWord) * this.version.bits;
this.words[arrayIndex] = this.words[arrayIndex] & ~(this.version.maxEntryValue << offset) | (value & this.version.maxEntryValue) << offset;
}
@Override
public int get(int index) {
Preconditions.checkElementIndex(index, this.size);
int arrayIndex = index / this.version.entriesPerWord;
int offset = (index % this.version.entriesPerWord) * this.version.bits;
return (this.words[arrayIndex] >>> offset) & this.version.maxEntryValue;
}
@Override
public int size() {
return this.size;
}
@Override
public int[] getWords() {
return this.words;
}
@Override
public PaletteVersion getVersion() {
return this.version;
}
@Override
public Palette copy() {
return new PaddedPalette(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
}
}

View file

@ -0,0 +1,19 @@
package org.geysermc.connector.world.chunk.palette;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public interface Palette {
void set(int index, int value);
int get(int index);
int size();
int[] getWords();
PaletteVersion getVersion();
Palette copy();
}

View file

@ -0,0 +1,64 @@
package org.geysermc.connector.world.chunk.palette;
import org.geysermc.connector.utils.MathUtils;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public enum PaletteVersion {
V16(16, 2, null),
V8(8, 4, V16),
V6(6, 5, V8), // 2 bit padding
V5(5, 6, V6), // 2 bit padding
V4(4, 8, V5),
V3(3, 10, V4), // 2 bit padding
V2(2, 16, V3),
V1(1, 32, V2);
final byte bits;
final byte entriesPerWord;
final int maxEntryValue;
final PaletteVersion next;
PaletteVersion(int bits, int entriesPerWord, PaletteVersion next) {
this.bits = (byte) bits;
this.entriesPerWord = (byte) entriesPerWord;
this.maxEntryValue = (1 << this.bits) - 1;
this.next = next;
}
public Palette createPalette(int size) {
return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]);
}
public byte getVersion() {
return bits;
}
public int getMaxEntryValue() {
return maxEntryValue;
}
public PaletteVersion next() {
return next;
}
public Palette createPalette(int size, int[] words) {
if (this == V3 || this == V5 || this == V6) {
// Padded palettes aren't able to use bitwise operations due to their padding.
return new PaddedPalette(this, size, words);
} else {
return new Pow2Palette(this, size, words);
}
}
private static PaletteVersion getVersion(int version, boolean read) {
for (PaletteVersion ver : values()) {
if ( ( !read && ver.entriesPerWord <= version ) || ( read && ver.bits == version ) ) {
return ver;
}
}
throw new IllegalArgumentException("Invalid palette version: " + version);
}
}

View file

@ -0,0 +1,86 @@
package org.geysermc.connector.world.chunk.palette;
import com.nukkitx.network.util.Preconditions;
import org.geysermc.connector.utils.MathUtils;
import java.util.Arrays;
/**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/
public class Pow2Palette implements Palette {
/**
* Array used to store data
*/
private final int[] words;
/**
* Palette version information
*/
private final PaletteVersion version;
/**
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
*/
private final int size;
Pow2Palette(PaletteVersion version, int size, int[] words) {
this.size = size;
this.version = version;
this.words = words;
int expectedWordsLength = MathUtils.ceil((float) size / version.entriesPerWord);
if (words.length != expectedWordsLength) {
throw new IllegalArgumentException("Invalid length given for storage, got: " + words.length +
" but expected: " + expectedWordsLength);
}
}
/**
* Sets the entry at the given location to the given value
*/
public void set(int index, int value) {
Preconditions.checkElementIndex(index, this.size);
Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value");
int bitIndex = index * this.version.bits;
int arrayIndex = bitIndex >> 5;
int offset = bitIndex & 31;
this.words[arrayIndex] = this.words[arrayIndex] & ~(this.version.maxEntryValue << offset) | (value & this.version.maxEntryValue) << offset;
}
/**
* Gets the entry at the given index
*/
public int get(int index) {
Preconditions.checkElementIndex(index, this.size);
int bitIndex = index * this.version.bits;
int arrayIndex = bitIndex >> 5;
int wordOffset = bitIndex & 31;
return this.words[arrayIndex] >>> wordOffset & this.version.maxEntryValue;
}
/**
* Gets the long array that is used to store the data in this BitArray. This is useful for sending packet data.
*/
public int size() {
return this.size;
}
/**
* {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
public int[] getWords() {
return this.words;
}
public PaletteVersion getVersion() {
return version;
}
@Override
public Palette copy() {
return new Pow2Palette(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
}
}

File diff suppressed because it is too large Load diff