mirror of
https://github.com/GeyserMC/MCProtocolLib.git
synced 2024-11-14 19:34:58 -05:00
Revise chunk storage code based on latest logic.
This commit is contained in:
parent
ddd5b3a034
commit
2ac116a075
8 changed files with 294 additions and 96 deletions
|
@ -198,7 +198,6 @@ public class MinecraftProtocolTest {
|
|||
private static void login() {
|
||||
MinecraftProtocol protocol = null;
|
||||
if(VERIFY_USERS) {
|
||||
|
||||
try {
|
||||
AuthenticationService authService = new AuthenticationService();
|
||||
authService.setUsername(USERNAME);
|
||||
|
|
|
@ -170,7 +170,6 @@ import lombok.Getter;
|
|||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.util.UUID;
|
||||
|
|
|
@ -1,24 +1,11 @@
|
|||
package com.github.steveice10.mc.protocol.data.game.chunk;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
@Data
|
||||
public class FlexibleStorage {
|
||||
private final @NonNull long[] data;
|
||||
private final int bitsPerEntry;
|
||||
private final int size;
|
||||
private final long maxEntryValue;
|
||||
|
||||
private final char valuesPerLong;
|
||||
private final int magicIndex;
|
||||
private final long divideMultiply;
|
||||
private final long divideAdd;
|
||||
private final long divideShift;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class BitStorage {
|
||||
private static final int[] MAGIC_VALUES = {
|
||||
-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
|
||||
0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
|
||||
|
@ -39,25 +26,48 @@ public class FlexibleStorage {
|
|||
79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
|
||||
0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
|
||||
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
|
||||
0, 5 };
|
||||
0, 5
|
||||
};
|
||||
|
||||
public FlexibleStorage(int bitsPerEntry) {
|
||||
this(bitsPerEntry, new long[(4096 + (64 / bitsPerEntry) - 1) / (64 / bitsPerEntry)]);
|
||||
@Getter
|
||||
private final @NonNull long[] data;
|
||||
@Getter
|
||||
private final int bitsPerEntry;
|
||||
@Getter
|
||||
private final int size;
|
||||
|
||||
private final long maxValue;
|
||||
private final int valuesPerLong;
|
||||
private final long divideMultiply;
|
||||
private final long divideAdd;
|
||||
private final int divideShift;
|
||||
|
||||
public BitStorage(int bitsPerEntry, int size) {
|
||||
this(bitsPerEntry, size, null);
|
||||
}
|
||||
|
||||
public FlexibleStorage(int bitsPerEntry, @NonNull long[] data) {
|
||||
this.bitsPerEntry = bitsPerEntry;
|
||||
this.data = Arrays.copyOf(data, data.length);
|
||||
this.size = data.length * 64 / bitsPerEntry;
|
||||
this.maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||
|
||||
this.valuesPerLong = (char) (64 / bitsPerEntry);
|
||||
int expectedLength = (4096 + valuesPerLong - 1) / valuesPerLong;
|
||||
if(data.length != expectedLength) {
|
||||
throw new IllegalArgumentException("Expected " + expectedLength + " longs but got " + data.length + " longs");
|
||||
public BitStorage(int bitsPerEntry, int size, long[] data) {
|
||||
if(bitsPerEntry < 1 || bitsPerEntry > 32) {
|
||||
throw new IllegalArgumentException("bitsPerEntry must be between 1 and 32, inclusive.");
|
||||
}
|
||||
|
||||
this.magicIndex = 3 * (valuesPerLong - 1);
|
||||
this.bitsPerEntry = bitsPerEntry;
|
||||
this.size = size;
|
||||
|
||||
this.maxValue = (1L << bitsPerEntry) - 1L;
|
||||
this.valuesPerLong = (char) (64 / bitsPerEntry);
|
||||
int expectedLength = (size + this.valuesPerLong - 1) / this.valuesPerLong;
|
||||
if(data != null) {
|
||||
if(data.length != expectedLength) {
|
||||
throw new IllegalArgumentException("Expected " + expectedLength + " longs but got " + data.length + " longs");
|
||||
}
|
||||
|
||||
this.data = data;
|
||||
} else {
|
||||
this.data = new long[expectedLength];
|
||||
}
|
||||
|
||||
int magicIndex = 3 * (this.valuesPerLong - 1);
|
||||
this.divideMultiply = Integer.toUnsignedLong(MAGIC_VALUES[magicIndex]);
|
||||
this.divideAdd = Integer.toUnsignedLong(MAGIC_VALUES[magicIndex + 1]);
|
||||
this.divideShift = MAGIC_VALUES[magicIndex + 2];
|
||||
|
@ -68,9 +78,9 @@ public class FlexibleStorage {
|
|||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int cellIndex = (int) (index * divideMultiply + divideAdd >> 32L >> divideShift);
|
||||
int bitIndex = (index - cellIndex * valuesPerLong) * bitsPerEntry;
|
||||
return (int) (data[cellIndex] >> bitIndex & maxEntryValue);
|
||||
int cellIndex = cellIndex(index);
|
||||
int bitIndex = bitIndex(index, cellIndex);
|
||||
return (int) (this.data[cellIndex] >> bitIndex & this.maxValue);
|
||||
}
|
||||
|
||||
public void set(int index, int value) {
|
||||
|
@ -78,20 +88,37 @@ public class FlexibleStorage {
|
|||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
if(value < 0 || value > this.maxEntryValue) {
|
||||
if(value < 0 || value > this.maxValue) {
|
||||
throw new IllegalArgumentException("Value cannot be outside of accepted range.");
|
||||
}
|
||||
|
||||
int cellIndex = (int) (index * divideMultiply + divideAdd >> 32L >> divideShift);
|
||||
int bitIndex = (index - cellIndex * valuesPerLong) * bitsPerEntry;
|
||||
this.data[cellIndex] = this.data[cellIndex] & ~(this.maxEntryValue << bitIndex) | ((long) value & this.maxEntryValue) << bitIndex;
|
||||
int cellIndex = cellIndex(index);
|
||||
int bitIndex = bitIndex(index, cellIndex);
|
||||
this.data[cellIndex] = this.data[cellIndex] & ~(this.maxValue << bitIndex) | ((long) value & this.maxValue) << bitIndex;
|
||||
}
|
||||
|
||||
public FlexibleStorage transferData(int newBitsPerEntry, IntFunction<Integer> valueGetter) {
|
||||
FlexibleStorage newStorage = new FlexibleStorage(newBitsPerEntry);
|
||||
for(int i = 0; i < 4096; i++) {
|
||||
newStorage.set(i, valueGetter.apply(i));
|
||||
public int[] toIntArray() {
|
||||
int[] result = new int[this.size];
|
||||
int index = 0;
|
||||
for(long cell : this.data) {
|
||||
for(int bitIndex = 0; bitIndex < this.valuesPerLong; bitIndex++) {
|
||||
result[index++] = (int) (cell & this.maxValue);
|
||||
cell >>= this.bitsPerEntry;
|
||||
|
||||
if (index >= this.size) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return newStorage;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int cellIndex(int index) {
|
||||
return (int) (index * this.divideMultiply + this.divideAdd >> 32 >> this.divideShift);
|
||||
}
|
||||
|
||||
private int bitIndex(int index, int cellIndex) {
|
||||
return (index - cellIndex * this.valuesPerLong) * this.bitsPerEntry;
|
||||
}
|
||||
}
|
|
@ -1,110 +1,126 @@
|
|||
package com.github.steveice10.mc.protocol.data.game.chunk;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.ListPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.MapPalette;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette;
|
||||
import com.github.steveice10.packetlib.io.NetInput;
|
||||
import com.github.steveice10.packetlib.io.NetOutput;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Setter(AccessLevel.NONE)
|
||||
@AllArgsConstructor
|
||||
public class Chunk {
|
||||
private static final int CHUNK_SIZE = 4096;
|
||||
private static final int MIN_PALETTE_BITS_PER_ENTRY = 4;
|
||||
private static final int MAX_PALETTE_BITS_PER_ENTRY = 8;
|
||||
private static final int GLOBAL_PALETTE_BITS_PER_ENTRY = 14;
|
||||
|
||||
private static final int AIR = 0;
|
||||
|
||||
private int blockCount;
|
||||
private int bitsPerEntry;
|
||||
|
||||
private @NonNull List<Integer> palette;
|
||||
private @NonNull FlexibleStorage storage;
|
||||
private @NonNull Palette palette;
|
||||
private @NonNull BitStorage storage;
|
||||
|
||||
public Chunk() {
|
||||
this(0, 4, new ArrayList<>(Collections.singletonList(AIR)), new FlexibleStorage(4));
|
||||
this(0, new ListPalette(MIN_PALETTE_BITS_PER_ENTRY), new BitStorage(MIN_PALETTE_BITS_PER_ENTRY, CHUNK_SIZE));
|
||||
}
|
||||
|
||||
public static Chunk read(NetInput in) throws IOException {
|
||||
int blockCount = in.readShort();
|
||||
int bitsPerEntry = in.readUnsignedByte();
|
||||
|
||||
List<Integer> palette = new ArrayList<>();
|
||||
if(bitsPerEntry <= 8) {
|
||||
Palette palette = createPalette(bitsPerEntry);
|
||||
if(!(palette instanceof GlobalPalette)) {
|
||||
int paletteLength = in.readVarInt();
|
||||
for(int i = 0; i < paletteLength; i++) {
|
||||
palette.add(in.readVarInt());
|
||||
palette.stateToId(in.readVarInt());
|
||||
}
|
||||
}
|
||||
|
||||
FlexibleStorage storage = new FlexibleStorage(bitsPerEntry, in.readLongs(in.readVarInt()));
|
||||
return new Chunk(blockCount, bitsPerEntry, palette, storage);
|
||||
BitStorage storage = new BitStorage(bitsPerEntry, CHUNK_SIZE, in.readLongs(in.readVarInt()));
|
||||
return new Chunk(blockCount, palette, storage);
|
||||
}
|
||||
|
||||
public static void write(NetOutput out, Chunk chunk) throws IOException {
|
||||
out.writeShort(chunk.getBlockCount());
|
||||
out.writeByte(chunk.getBitsPerEntry());
|
||||
out.writeShort(chunk.blockCount);
|
||||
out.writeByte(chunk.storage.getBitsPerEntry());
|
||||
|
||||
if(chunk.getBitsPerEntry() <= 8) {
|
||||
out.writeVarInt(chunk.getPalette().size());
|
||||
for (int state : chunk.getPalette()) {
|
||||
out.writeVarInt(state);
|
||||
if(!(chunk.palette instanceof GlobalPalette)) {
|
||||
int paletteLength = chunk.palette.size();
|
||||
out.writeVarInt(paletteLength);
|
||||
for(int i = 0; i < paletteLength; i++) {
|
||||
out.writeVarInt(chunk.palette.idToState(i));
|
||||
}
|
||||
}
|
||||
|
||||
long[] data = chunk.getStorage().getData();
|
||||
long[] data = chunk.storage.getData();
|
||||
out.writeVarInt(data.length);
|
||||
out.writeLongs(data);
|
||||
}
|
||||
|
||||
private static int index(int x, int y, int z) {
|
||||
return y << 8 | z << 4 | x;
|
||||
}
|
||||
|
||||
public int get(int x, int y, int z) {
|
||||
int id = this.storage.get(index(x, y, z));
|
||||
return this.bitsPerEntry <= 8 ? (id >= 0 && id < this.palette.size() ? this.palette.get(id) : AIR) : id;
|
||||
return this.palette.idToState(id);
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, @NonNull int state) {
|
||||
int id = this.bitsPerEntry <= 8 ? this.palette.indexOf(state) : state;
|
||||
int id = this.palette.stateToId(state);
|
||||
if(id == -1) {
|
||||
this.palette.add(state);
|
||||
if(this.palette.size() > 1 << this.bitsPerEntry) {
|
||||
this.bitsPerEntry++;
|
||||
|
||||
final List<Integer> oldStates = this.bitsPerEntry > 8 ? new ArrayList<>(this.palette) : this.palette;
|
||||
if(this.bitsPerEntry > 8) {
|
||||
this.palette.clear();
|
||||
this.bitsPerEntry = 14;
|
||||
}
|
||||
|
||||
FlexibleStorage oldStorage = this.storage;
|
||||
this.storage = oldStorage.transferData(this.bitsPerEntry, (index) ->
|
||||
this.bitsPerEntry <= 8 ? oldStorage.get(index) : oldStates.get(index));
|
||||
}
|
||||
|
||||
id = this.bitsPerEntry <= 8 ? this.palette.indexOf(state) : state;
|
||||
this.resizePalette();
|
||||
id = this.palette.stateToId(state);
|
||||
}
|
||||
|
||||
int ind = index(x, y, z);
|
||||
int curr = this.storage.get(ind);
|
||||
int index = index(x, y, z);
|
||||
int curr = this.storage.get(index);
|
||||
if(state != AIR && curr == AIR) {
|
||||
this.blockCount++;
|
||||
} else if(state == AIR && curr != AIR) {
|
||||
this.blockCount--;
|
||||
}
|
||||
|
||||
this.storage.set(ind, id);
|
||||
this.storage.set(index, id);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
for(int index = 0; index < this.storage.getSize(); index++) {
|
||||
if(this.storage.get(index) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.blockCount == 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
private int sanitizeBitsPerEntry(int bitsPerEntry) {
|
||||
if(bitsPerEntry <= MAX_PALETTE_BITS_PER_ENTRY) {
|
||||
return Math.max(MIN_PALETTE_BITS_PER_ENTRY, bitsPerEntry);
|
||||
} else {
|
||||
return GLOBAL_PALETTE_BITS_PER_ENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
private void resizePalette() {
|
||||
Palette oldPalette = this.palette;
|
||||
int[] oldData = this.storage.toIntArray();
|
||||
|
||||
int bitsPerEntry = sanitizeBitsPerEntry(this.storage.getBitsPerEntry() + 1);
|
||||
this.palette = createPalette(bitsPerEntry);
|
||||
this.storage = new BitStorage(bitsPerEntry, CHUNK_SIZE);
|
||||
|
||||
for(int i = 0; i < oldData.length; i++) {
|
||||
this.storage.set(i, this.palette.stateToId(oldPalette.idToState(oldData[i])));
|
||||
}
|
||||
}
|
||||
|
||||
private static Palette createPalette(int bitsPerEntry) {
|
||||
if(bitsPerEntry <= MIN_PALETTE_BITS_PER_ENTRY) {
|
||||
return new ListPalette(bitsPerEntry);
|
||||
} else if(bitsPerEntry <= MAX_PALETTE_BITS_PER_ENTRY) {
|
||||
return new MapPalette(bitsPerEntry);
|
||||
} else {
|
||||
return new GlobalPalette();
|
||||
}
|
||||
}
|
||||
|
||||
private static int index(int x, int y, int z) {
|
||||
return y << 8 | z << 4 | x;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package com.github.steveice10.mc.protocol.data.game.chunk.palette;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* A global palette that maps 1:1.
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class GlobalPalette implements Palette {
|
||||
@Override
|
||||
public int size() {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stateToId(int state) {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idToState(int id) {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package com.github.steveice10.mc.protocol.data.game.chunk.palette;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A palette backed by a List.
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class ListPalette implements Palette {
|
||||
private final int maxId;
|
||||
|
||||
private final List<Integer> data = new ArrayList<>();
|
||||
|
||||
public ListPalette(int bitsPerEntry) {
|
||||
this.maxId = (1 << bitsPerEntry) - 1;
|
||||
|
||||
this.data.add(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stateToId(int state) {
|
||||
int id = this.data.indexOf(state);
|
||||
if(id == -1 && this.size() < this.maxId + 1) {
|
||||
this.data.add(id);
|
||||
id = this.data.size() - 1;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idToState(int id) {
|
||||
if(id >= 0 && id < this.data.size()) {
|
||||
return this.data.get(id);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package com.github.steveice10.mc.protocol.data.game.chunk.palette;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A palette backed by a map.
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
public class MapPalette implements Palette {
|
||||
private final int maxId;
|
||||
|
||||
private final Map<Integer, Integer> idToState = new HashMap<>();
|
||||
private final Map<Integer, Integer> stateToId = new HashMap<>();
|
||||
private int nextId = 1;
|
||||
|
||||
public MapPalette(int bitsPerEntry) {
|
||||
this.maxId = (1 << bitsPerEntry) - 1;
|
||||
|
||||
this.idToState.put(0, 0);
|
||||
this.stateToId.put(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.idToState.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stateToId(int state) {
|
||||
Integer id = this.stateToId.get(state);
|
||||
if(id == null && this.size() < this.maxId + 1) {
|
||||
id = this.nextId++;
|
||||
this.idToState.put(id, state);
|
||||
this.stateToId.put(state, id);
|
||||
}
|
||||
|
||||
if(id != null) {
|
||||
return id;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idToState(int id) {
|
||||
Integer state = this.idToState.get(id);
|
||||
if(state != null) {
|
||||
return state;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.github.steveice10.mc.protocol.data.game.chunk.palette;
|
||||
|
||||
/**
|
||||
* A palette for mapping block states to storage IDs.
|
||||
*/
|
||||
public interface Palette {
|
||||
/**
|
||||
* Gets the number of block states known by this palette.
|
||||
* @return The palette's size.
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* Converts a block state to a storage ID. If the state has not been mapped,
|
||||
* the palette will attempt to map it, returning -1 if it cannot.
|
||||
*
|
||||
* @param state Block state to convert.
|
||||
* @return The resulting storage ID.
|
||||
*/
|
||||
public int stateToId(int state);
|
||||
|
||||
/**
|
||||
* Converts a storage ID to a block state. If the storage ID has no mapping,
|
||||
* it will return a block state of 0.
|
||||
*
|
||||
* @param id Storage ID to convert.
|
||||
* @return The resulting block state.
|
||||
*/
|
||||
public int idToState(int id);
|
||||
}
|
Loading…
Reference in a new issue