diff --git a/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java b/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java index 19957330a..9965acfad 100644 --- a/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java +++ b/connector/src/main/java/org/geysermc/connector/world/GlobalBlockPalette.java @@ -1,6 +1,7 @@ package org.geysermc.connector.world; -import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicInteger; @@ -10,8 +11,8 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class GlobalBlockPalette { - private static final Int2IntArrayMap legacyToRuntimeId = new Int2IntArrayMap(); - private static final Int2IntArrayMap runtimeIdToLegacy = new Int2IntArrayMap(); + private static final Int2IntMap legacyToRuntimeId = new Int2IntOpenHashMap(); + private static final Int2IntMap runtimeIdToLegacy = new Int2IntOpenHashMap(); private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0); static { @@ -39,4 +40,8 @@ public class GlobalBlockPalette { legacyToRuntimeId.put(legacyId, runtimeId); return runtimeId; } + + public static int getLegacyId(int runtimeId) { + return runtimeIdToLegacy.get(runtimeId); + } } diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java index f2c9a517b..c957d8409 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java @@ -4,8 +4,8 @@ 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; +import org.geysermc.connector.world.chunk.bitarray.BitArray; +import org.geysermc.connector.world.chunk.bitarray.BitArrayVersion; /** * Adapted from NukkitX: https://github.com/NukkitX/Nukkit @@ -14,76 +14,76 @@ public class BlockStorage { private static final int SIZE = 4096; - private final TIntArrayList ids; - private Palette palette; + private final TIntArrayList palette; + private BitArray bitArray; public BlockStorage() { - this(PaletteVersion.V2); + this(BitArrayVersion.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. + public BlockStorage(BitArrayVersion version) { + this.bitArray = version.createPalette(SIZE); + this.palette = new TIntArrayList(16, -1); + this.palette.add(0); // Air is at the start of every palette. } - private BlockStorage(Palette palette, TIntArrayList ids) { - this.ids = ids; + private BlockStorage(BitArray bitArray, TIntArrayList palette) { this.palette = palette; + this.bitArray = bitArray; } - public synchronized int getFullBlock(int xzy) { - return this.palette.get(xzy); + private static int getPaletteHeader(BitArrayVersion version, boolean runtime) { + return (version.getId() << 1) | (runtime ? 1 : 0); + } + + private static BitArrayVersion getVersionFromHeader(byte header) { + return BitArrayVersion.get(header >> 1, true); + } + + public synchronized int getFullBlock(int index) { + return this.legacyIdFor(this.bitArray.get(index)); } public synchronized void setFullBlock(int index, int legacyId) { - this.palette.set(index, this.idFor(legacyId)); + int idx = this.idFor(legacyId); + this.bitArray.set(index, idx); } public synchronized void writeToNetwork(ByteBuf buffer) { - buffer.writeByte(getPaletteHeader(palette.getVersion(), true)); + buffer.writeByte(getPaletteHeader(bitArray.getVersion(), true)); - for (int word : palette.getWords()) { + for (int word : bitArray.getWords()) { buffer.writeIntLE(word); } - VarInts.writeUnsignedInt(buffer, ids.size()); - ids.forEach(id -> { + VarInts.writeUnsignedInt(buffer, palette.size()); + palette.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); + private void onResize(BitArrayVersion version) { + BitArray newBitArray = version.createPalette(SIZE); for (int i = 0; i < SIZE; i++) { - this.palette.set(i, oldPalette.get(i)); + newBitArray.set(i, this.bitArray.get(i)); } + this.bitArray = newBitArray; } private int idFor(int legacyId) { int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(legacyId); - int index = this.ids.indexOf(runtimeId); + int index = this.palette.indexOf(runtimeId); if (index != -1) { return index; } - index = this.ids.size(); - this.ids.add(runtimeId); - PaletteVersion version = this.palette.getVersion(); + index = this.palette.size(); + this.palette.add(runtimeId); + BitArrayVersion version = this.bitArray.getVersion(); if (index > version.getMaxEntryValue()) { - PaletteVersion next = version.next(); + BitArrayVersion next = version.next(); if (next != null) { this.onResize(next); } @@ -91,13 +91,17 @@ public class BlockStorage { return index; } - private static int getPaletteHeader(PaletteVersion version, boolean runtime) { - return (version.getVersion() << 1) | (runtime ? 1 : 0); + private int legacyIdFor(int index) { + int runtimeId = this.palette.get(index); + return GlobalBlockPalette.getLegacyId(runtimeId); } public boolean isEmpty() { - for (int word : this.palette.getWords()) { - if (word != 0) { + if (this.palette.size() == 1) { + return true; + } + for (int word : this.bitArray.getWords()) { + if (Integer.toUnsignedLong(word) != 0L) { return false; } } @@ -105,6 +109,6 @@ public class BlockStorage { } public BlockStorage copy() { - return new BlockStorage(this.palette.copy(), new TIntArrayList(this.ids)); + return new BlockStorage(this.bitArray.copy(), new TIntArrayList(this.palette)); } -} +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Palette.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java similarity index 55% rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/Palette.java rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java index 07640bf1d..2f08ae460 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Palette.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArray.java @@ -1,9 +1,9 @@ -package org.geysermc.connector.world.chunk.palette; +package org.geysermc.connector.world.chunk.bitarray; /** * Adapted from NukkitX: https://github.com/NukkitX/Nukkit */ -public interface Palette { +public interface BitArray { void set(int index, int value); @@ -13,7 +13,7 @@ public interface Palette { int[] getWords(); - PaletteVersion getVersion(); + BitArrayVersion getVersion(); - Palette copy(); + BitArray copy(); } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaletteVersion.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java similarity index 58% rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaletteVersion.java rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java index 61029a8fe..7bbf9fe81 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaletteVersion.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/BitArrayVersion.java @@ -1,12 +1,11 @@ -package org.geysermc.connector.world.chunk.palette; +package org.geysermc.connector.world.chunk.bitarray; import org.geysermc.connector.utils.MathUtils; /** * Adapted from NukkitX: https://github.com/NukkitX/Nukkit */ -public enum PaletteVersion { - +public enum BitArrayVersion { V16(16, 2, null), V8(8, 4, V16), V6(6, 5, V8), // 2 bit padding @@ -19,20 +18,29 @@ public enum PaletteVersion { final byte bits; final byte entriesPerWord; final int maxEntryValue; - final PaletteVersion next; + final BitArrayVersion next; - PaletteVersion(int bits, int entriesPerWord, PaletteVersion next) { + BitArrayVersion(int bits, int entriesPerWord, BitArrayVersion next) { this.bits = (byte) bits; this.entriesPerWord = (byte) entriesPerWord; this.maxEntryValue = (1 << this.bits) - 1; this.next = next; } - public Palette createPalette(int size) { + public static BitArrayVersion get(int version, boolean read) { + for (BitArrayVersion ver : values()) { + if ((!read && ver.entriesPerWord <= version) || (read && ver.bits == version)) { + return ver; + } + } + throw new IllegalArgumentException("Invalid palette version: " + version); + } + + public BitArray createPalette(int size) { return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]); } - public byte getVersion() { + public byte getId() { return bits; } @@ -40,25 +48,20 @@ public enum PaletteVersion { return maxEntryValue; } - public PaletteVersion next() { + public int getWordsForSize(int size) { + return MathUtils.ceil((float) size / entriesPerWord); + } + + public BitArrayVersion next() { return next; } - public Palette createPalette(int size, int[] words) { + public BitArray 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); + return new PaddedBitArray(this, size, words); } else { - return new Pow2Palette(this, size, words); + return new Pow2BitArray(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); - } } diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaddedPalette.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java similarity index 83% rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaddedPalette.java rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java index 9f88d0dd4..016fa840b 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/PaddedPalette.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/PaddedBitArray.java @@ -1,4 +1,4 @@ -package org.geysermc.connector.world.chunk.palette; +package org.geysermc.connector.world.chunk.bitarray; import com.nukkitx.network.util.Preconditions; import org.geysermc.connector.utils.MathUtils; @@ -8,7 +8,7 @@ import java.util.Arrays; /** * Adapted from NukkitX: https://github.com/NukkitX/Nukkit */ -public class PaddedPalette implements Palette { +public class PaddedBitArray implements BitArray { /** * Array used to store data @@ -18,14 +18,14 @@ public class PaddedPalette implements Palette { /** * Palette version information */ - private final PaletteVersion version; + private final BitArrayVersion version; /** * Number of entries in this palette (not the length of the words array that internally backs this palette) */ private final int size; - PaddedPalette(PaletteVersion version, int size, int[] words) { + PaddedBitArray(BitArrayVersion version, int size, int[] words) { this.size = size; this.version = version; this.words = words; @@ -66,12 +66,12 @@ public class PaddedPalette implements Palette { } @Override - public PaletteVersion getVersion() { + public BitArrayVersion getVersion() { return this.version; } @Override - public Palette copy() { - return new PaddedPalette(this.version, this.size, Arrays.copyOf(this.words, this.words.length)); + public BitArray copy() { + return new PaddedBitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length)); } } diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Pow2Palette.java b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java similarity index 83% rename from connector/src/main/java/org/geysermc/connector/world/chunk/palette/Pow2Palette.java rename to connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java index 98ee87559..303d0709e 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/palette/Pow2Palette.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/bitarray/Pow2BitArray.java @@ -1,4 +1,4 @@ -package org.geysermc.connector.world.chunk.palette; +package org.geysermc.connector.world.chunk.bitarray; import com.nukkitx.network.util.Preconditions; import org.geysermc.connector.utils.MathUtils; @@ -8,7 +8,7 @@ import java.util.Arrays; /** * Adapted from NukkitX: https://github.com/NukkitX/Nukkit */ -public class Pow2Palette implements Palette { +public class Pow2BitArray implements BitArray { /** * Array used to store data @@ -18,14 +18,14 @@ public class Pow2Palette implements Palette { /** * Palette version information */ - private final PaletteVersion version; + private final BitArrayVersion version; /** * Number of entries in this palette (not the length of the words array that internally backs this palette) */ private final int size; - Pow2Palette(PaletteVersion version, int size, int[] words) { + Pow2BitArray(BitArrayVersion version, int size, int[] words) { this.size = size; this.version = version; this.words = words; @@ -41,7 +41,7 @@ public class Pow2Palette implements Palette { */ public void set(int index, int value) { Preconditions.checkElementIndex(index, this.size); - Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value"); + Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value %s", value); int bitIndex = index * this.version.bits; int arrayIndex = bitIndex >> 5; int offset = bitIndex & 31; @@ -75,12 +75,12 @@ public class Pow2Palette implements Palette { return this.words; } - public PaletteVersion getVersion() { + public BitArrayVersion getVersion() { return version; } @Override - public Palette copy() { - return new Pow2Palette(this.version, this.size, Arrays.copyOf(this.words, this.words.length)); + public BitArray copy() { + return new Pow2BitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length)); } } \ No newline at end of file