Rename palette to BitArray and fix various chunk issues.

No idea if this works since I have no Java Edition server to test on.
This commit is contained in:
SupremeMortal 2019-09-10 22:50:34 +01:00
parent 383429d71b
commit 72589fabcd
No known key found for this signature in database
GPG key ID: DDBB25F8EE4FA29A
6 changed files with 96 additions and 84 deletions

View file

@ -1,6 +1,7 @@
package org.geysermc.connector.world; 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.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -10,8 +11,8 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public class GlobalBlockPalette { public class GlobalBlockPalette {
private static final Int2IntArrayMap legacyToRuntimeId = new Int2IntArrayMap(); private static final Int2IntMap legacyToRuntimeId = new Int2IntOpenHashMap();
private static final Int2IntArrayMap runtimeIdToLegacy = new Int2IntArrayMap(); private static final Int2IntMap runtimeIdToLegacy = new Int2IntOpenHashMap();
private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0); private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0);
static { static {
@ -39,4 +40,8 @@ public class GlobalBlockPalette {
legacyToRuntimeId.put(legacyId, runtimeId); legacyToRuntimeId.put(legacyId, runtimeId);
return runtimeId; return runtimeId;
} }
public static int getLegacyId(int runtimeId) {
return runtimeIdToLegacy.get(runtimeId);
}
} }

View file

@ -4,8 +4,8 @@ import com.nukkitx.network.VarInts;
import gnu.trove.list.array.TIntArrayList; import gnu.trove.list.array.TIntArrayList;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.geysermc.connector.world.GlobalBlockPalette; import org.geysermc.connector.world.GlobalBlockPalette;
import org.geysermc.connector.world.chunk.palette.Palette; import org.geysermc.connector.world.chunk.bitarray.BitArray;
import org.geysermc.connector.world.chunk.palette.PaletteVersion; import org.geysermc.connector.world.chunk.bitarray.BitArrayVersion;
/** /**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
@ -14,76 +14,76 @@ public class BlockStorage {
private static final int SIZE = 4096; private static final int SIZE = 4096;
private final TIntArrayList ids; private final TIntArrayList palette;
private Palette palette; private BitArray bitArray;
public BlockStorage() { public BlockStorage() {
this(PaletteVersion.V2); this(BitArrayVersion.V2);
} }
public BlockStorage(PaletteVersion version) { public BlockStorage(BitArrayVersion version) {
this.palette = version.createPalette(SIZE); this.bitArray = version.createPalette(SIZE);
this.ids = new TIntArrayList(16, -1); this.palette = new TIntArrayList(16, -1);
this.ids.add(0); // Air is at the start of every palette. this.palette.add(0); // Air is at the start of every palette.
} }
private BlockStorage(Palette palette, TIntArrayList ids) { private BlockStorage(BitArray bitArray, TIntArrayList palette) {
this.ids = ids;
this.palette = palette; this.palette = palette;
this.bitArray = bitArray;
} }
public synchronized int getFullBlock(int xzy) { private static int getPaletteHeader(BitArrayVersion version, boolean runtime) {
return this.palette.get(xzy); 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) { 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) { 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); buffer.writeIntLE(word);
} }
VarInts.writeUnsignedInt(buffer, ids.size()); VarInts.writeUnsignedInt(buffer, palette.size());
ids.forEach(id -> { palette.forEach(id -> {
VarInts.writeUnsignedInt(buffer, id); VarInts.writeUnsignedInt(buffer, id);
return true; return true;
}); });
} }
public synchronized void writeToStorage(ByteBuf buffer) { private void onResize(BitArrayVersion version) {
buffer.writeByte(getPaletteHeader(palette.getVersion(), false)); BitArray newBitArray = version.createPalette(SIZE);
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++) { 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) { private int idFor(int legacyId) {
int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(legacyId); int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(legacyId);
int index = this.ids.indexOf(runtimeId); int index = this.palette.indexOf(runtimeId);
if (index != -1) { if (index != -1) {
return index; return index;
} }
index = this.ids.size(); index = this.palette.size();
this.ids.add(runtimeId); this.palette.add(runtimeId);
PaletteVersion version = this.palette.getVersion(); BitArrayVersion version = this.bitArray.getVersion();
if (index > version.getMaxEntryValue()) { if (index > version.getMaxEntryValue()) {
PaletteVersion next = version.next(); BitArrayVersion next = version.next();
if (next != null) { if (next != null) {
this.onResize(next); this.onResize(next);
} }
@ -91,13 +91,17 @@ public class BlockStorage {
return index; return index;
} }
private static int getPaletteHeader(PaletteVersion version, boolean runtime) { private int legacyIdFor(int index) {
return (version.getVersion() << 1) | (runtime ? 1 : 0); int runtimeId = this.palette.get(index);
return GlobalBlockPalette.getLegacyId(runtimeId);
} }
public boolean isEmpty() { public boolean isEmpty() {
for (int word : this.palette.getWords()) { if (this.palette.size() == 1) {
if (word != 0) { return true;
}
for (int word : this.bitArray.getWords()) {
if (Integer.toUnsignedLong(word) != 0L) {
return false; return false;
} }
} }
@ -105,6 +109,6 @@ public class BlockStorage {
} }
public BlockStorage copy() { public BlockStorage copy() {
return new BlockStorage(this.palette.copy(), new TIntArrayList(this.ids)); return new BlockStorage(this.bitArray.copy(), new TIntArrayList(this.palette));
} }
} }

View file

@ -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 * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/ */
public interface Palette { public interface BitArray {
void set(int index, int value); void set(int index, int value);
@ -13,7 +13,7 @@ public interface Palette {
int[] getWords(); int[] getWords();
PaletteVersion getVersion(); BitArrayVersion getVersion();
Palette copy(); BitArray copy();
} }

View file

@ -1,12 +1,11 @@
package org.geysermc.connector.world.chunk.palette; package org.geysermc.connector.world.chunk.bitarray;
import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.MathUtils;
/** /**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/ */
public enum PaletteVersion { public enum BitArrayVersion {
V16(16, 2, null), V16(16, 2, null),
V8(8, 4, V16), V8(8, 4, V16),
V6(6, 5, V8), // 2 bit padding V6(6, 5, V8), // 2 bit padding
@ -19,20 +18,29 @@ public enum PaletteVersion {
final byte bits; final byte bits;
final byte entriesPerWord; final byte entriesPerWord;
final int maxEntryValue; 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.bits = (byte) bits;
this.entriesPerWord = (byte) entriesPerWord; this.entriesPerWord = (byte) entriesPerWord;
this.maxEntryValue = (1 << this.bits) - 1; this.maxEntryValue = (1 << this.bits) - 1;
this.next = next; 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)]); return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]);
} }
public byte getVersion() { public byte getId() {
return bits; return bits;
} }
@ -40,25 +48,20 @@ public enum PaletteVersion {
return maxEntryValue; return maxEntryValue;
} }
public PaletteVersion next() { public int getWordsForSize(int size) {
return MathUtils.ceil((float) size / entriesPerWord);
}
public BitArrayVersion next() {
return next; return next;
} }
public Palette createPalette(int size, int[] words) { public BitArray createPalette(int size, int[] words) {
if (this == V3 || this == V5 || this == V6) { if (this == V3 || this == V5 || this == V6) {
// Padded palettes aren't able to use bitwise operations due to their padding. // 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 { } 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);
}
} }

View file

@ -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 com.nukkitx.network.util.Preconditions;
import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.MathUtils;
@ -8,7 +8,7 @@ import java.util.Arrays;
/** /**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/ */
public class PaddedPalette implements Palette { public class PaddedBitArray implements BitArray {
/** /**
* Array used to store data * Array used to store data
@ -18,14 +18,14 @@ public class PaddedPalette implements Palette {
/** /**
* Palette version information * Palette version information
*/ */
private final PaletteVersion version; private final BitArrayVersion version;
/** /**
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette) * Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
*/ */
private final int size; private final int size;
PaddedPalette(PaletteVersion version, int size, int[] words) { PaddedBitArray(BitArrayVersion version, int size, int[] words) {
this.size = size; this.size = size;
this.version = version; this.version = version;
this.words = words; this.words = words;
@ -66,12 +66,12 @@ public class PaddedPalette implements Palette {
} }
@Override @Override
public PaletteVersion getVersion() { public BitArrayVersion getVersion() {
return this.version; return this.version;
} }
@Override @Override
public Palette copy() { public BitArray copy() {
return new PaddedPalette(this.version, this.size, Arrays.copyOf(this.words, this.words.length)); return new PaddedBitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
} }
} }

View file

@ -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 com.nukkitx.network.util.Preconditions;
import org.geysermc.connector.utils.MathUtils; import org.geysermc.connector.utils.MathUtils;
@ -8,7 +8,7 @@ import java.util.Arrays;
/** /**
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit * Adapted from NukkitX: https://github.com/NukkitX/Nukkit
*/ */
public class Pow2Palette implements Palette { public class Pow2BitArray implements BitArray {
/** /**
* Array used to store data * Array used to store data
@ -18,14 +18,14 @@ public class Pow2Palette implements Palette {
/** /**
* Palette version information * Palette version information
*/ */
private final PaletteVersion version; private final BitArrayVersion version;
/** /**
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette) * Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
*/ */
private final int size; private final int size;
Pow2Palette(PaletteVersion version, int size, int[] words) { Pow2BitArray(BitArrayVersion version, int size, int[] words) {
this.size = size; this.size = size;
this.version = version; this.version = version;
this.words = words; this.words = words;
@ -41,7 +41,7 @@ public class Pow2Palette implements Palette {
*/ */
public void set(int index, int value) { public void set(int index, int value) {
Preconditions.checkElementIndex(index, this.size); 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 bitIndex = index * this.version.bits;
int arrayIndex = bitIndex >> 5; int arrayIndex = bitIndex >> 5;
int offset = bitIndex & 31; int offset = bitIndex & 31;
@ -75,12 +75,12 @@ public class Pow2Palette implements Palette {
return this.words; return this.words;
} }
public PaletteVersion getVersion() { public BitArrayVersion getVersion() {
return version; return version;
} }
@Override @Override
public Palette copy() { public BitArray copy() {
return new Pow2Palette(this.version, this.size, Arrays.copyOf(this.words, this.words.length)); return new Pow2BitArray(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
} }
} }