отказываемся от Palette в пользу Direct mode
This commit is contained in:
@@ -6,10 +6,9 @@ import lombok.Getter;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import mc.protocol.buffer.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
import mc.protocol.pool.ProtocolObjectPool;
|
import mc.protocol.utils.Bit13LongArray;
|
||||||
|
import mc.protocol.utils.HalfByteArray;
|
||||||
import mc.protocol.utils.NibbleArray;
|
import mc.protocol.utils.NibbleArray;
|
||||||
import mc.protocol.utils.PaletteChunkSection;
|
|
||||||
import mc.protocol.utils.PaletteChunkSection.PaletteCoords;
|
|
||||||
import mc.protocol.world.Block;
|
import mc.protocol.world.Block;
|
||||||
import mc.protocol.world.Chunk;
|
import mc.protocol.world.Chunk;
|
||||||
import mc.protocol.world.ChunkSection;
|
import mc.protocol.world.ChunkSection;
|
||||||
@@ -79,14 +78,18 @@ import mc.protocol.world.ChunkSection;
|
|||||||
@Data
|
@Data
|
||||||
public class ChunkDataPacket implements ServerSidePacket {
|
public class ChunkDataPacket implements ServerSidePacket {
|
||||||
|
|
||||||
|
private static final int BITS_PER_BLOCK = 13;
|
||||||
private static final int FULL_BIT_MASK = 0b11111111_11111111;
|
private static final int FULL_BIT_MASK = 0b11111111_11111111;
|
||||||
private static final int _16_16_16 = 16 * 16 * 16;
|
private static final int _16_16_16 = 16 * 16 * 16;
|
||||||
private static final int SIZE_OF_LONG_IN_BITS = 64;
|
|
||||||
|
|
||||||
private Chunk chunk;
|
private Chunk chunk;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
/*
|
||||||
|
TODO необходимо наладить работу objectpool при записи пакета
|
||||||
|
*/
|
||||||
|
|
||||||
netByteBuf.writeInt(chunk.getX()); // Chunk X
|
netByteBuf.writeInt(chunk.getX()); // Chunk X
|
||||||
netByteBuf.writeInt(chunk.getZ()); // Chunk Z
|
netByteBuf.writeInt(chunk.getZ()); // Chunk Z
|
||||||
|
|
||||||
@@ -146,63 +149,35 @@ public class ChunkDataPacket implements ServerSidePacket {
|
|||||||
// NetByteBuf data = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
|
// NetByteBuf data = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
|
||||||
NetByteBuf data = new NetByteBuf().setByteBuf(Unpooled.buffer());
|
NetByteBuf data = new NetByteBuf().setByteBuf(Unpooled.buffer());
|
||||||
|
|
||||||
PaletteChunkSection paletteSection = new PaletteChunkSection();
|
NibbleArray blockLight = new HalfByteArray(2048);
|
||||||
NibbleArray blockLight = new NibbleArray();
|
NibbleArray skyLight = new HalfByteArray(2048);
|
||||||
NibbleArray skyLight = new NibbleArray();
|
|
||||||
fillPalette(section, paletteSection, blockLight, skyLight);
|
|
||||||
|
|
||||||
// <Bits Per Block>
|
// <Bits Per Block>
|
||||||
int bitsPerBlock = paletteSection.bitsPerBlock();
|
data.writeUnsignedByte(BITS_PER_BLOCK);
|
||||||
data.writeUnsignedByte(bitsPerBlock);
|
|
||||||
// </Bits Per Block>
|
// </Bits Per Block>
|
||||||
|
|
||||||
// <Palette>
|
// <Palette>
|
||||||
paletteSection.writePalette(data);
|
data.writeVarInt(0); // Direct mode
|
||||||
// </Palette>
|
// </Palette>
|
||||||
|
|
||||||
// <Data Array Length>
|
// <Data Array Length>
|
||||||
int dataLength = (_16_16_16 * bitsPerBlock) / SIZE_OF_LONG_IN_BITS;
|
int dataArraySize = _16_16_16 * BITS_PER_BLOCK;
|
||||||
data.writeVarInt(dataLength);
|
data.writeVarInt(dataArraySize / Long.SIZE);
|
||||||
// </Data Array Length>
|
// </Data Array Length>
|
||||||
|
|
||||||
// <Data Array>
|
// <Data Array>
|
||||||
//TODO алгоритм побитовой записи в long вынести в utils
|
NibbleArray dataArray = new Bit13LongArray(dataArraySize);
|
||||||
// Возможно даже пораднив с NibbleArray
|
|
||||||
int lastPos = 0;
|
|
||||||
long value = 0;
|
|
||||||
boolean fairy = false;
|
|
||||||
long fairyValue = 0;
|
|
||||||
boolean writeBiomes = biomes != null;
|
boolean writeBiomes = biomes != null;
|
||||||
|
|
||||||
for (int y = 0; y < 16; y++) {
|
for (int y = 0; y < 16; y++) {
|
||||||
for (int z = 0; z < 16; z++) {
|
for (int z = 0; z < 16; z++) {
|
||||||
for (int x = 0; x < 16; x++) {
|
for (int x = 0; x < 16; x++) {
|
||||||
//@formatter:off
|
Block block = section.getBlock(x, y, z);
|
||||||
int blockNumber = (((y << 4) + z) << 4) + x;
|
int blockState = (block.getId() << 4) | block.getMeta();
|
||||||
int startLong = ( blockNumber * bitsPerBlock ) / SIZE_OF_LONG_IN_BITS;
|
dataArray.put(blockState);
|
||||||
int startOffset = ( blockNumber * bitsPerBlock ) % SIZE_OF_LONG_IN_BITS;
|
|
||||||
int endLong = ((blockNumber + 1) * bitsPerBlock - 1) / SIZE_OF_LONG_IN_BITS;
|
|
||||||
//@formatter:on
|
|
||||||
|
|
||||||
long idxBlockInPalette = paletteSection.getIndexBlockInPalette(x, y, z);
|
blockLight.put(block.getLight());
|
||||||
|
skyLight.put(section.getSkyLight(x, y, z));
|
||||||
if (startLong != lastPos) {
|
|
||||||
data.writeLong(value);
|
|
||||||
lastPos = startLong;
|
|
||||||
if (fairy) {
|
|
||||||
value = fairyValue;
|
|
||||||
fairy = false;
|
|
||||||
} else {
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value |= (idxBlockInPalette << startOffset);
|
|
||||||
|
|
||||||
if (startLong != endLong) {
|
|
||||||
fairyValue = idxBlockInPalette >> (SIZE_OF_LONG_IN_BITS - startOffset);
|
|
||||||
fairy = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (writeBiomes) {
|
if (writeBiomes) {
|
||||||
biomes.writeByte(chunk.getBiome(
|
biomes.writeByte(chunk.getBiome(
|
||||||
@@ -216,35 +191,20 @@ public class ChunkDataPacket implements ServerSidePacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.writeLong(value);
|
data.writeBytes(dataArray.byteBuffer());
|
||||||
// </Data Array>
|
// </Data Array>
|
||||||
|
|
||||||
// <Block Light>
|
// <Block Light>
|
||||||
data.writeBytes(blockLight.getRawData());
|
data.writeBytes(blockLight.byteBuffer());
|
||||||
// </Block Light>
|
// </Block Light>
|
||||||
|
|
||||||
// <Sky Light>
|
// <Sky Light>
|
||||||
data.writeBytes(skyLight.getRawData());
|
data.writeBytes(skyLight.byteBuffer());
|
||||||
// </Sky Light>
|
// </Sky Light>
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillPalette(ChunkSection section, PaletteChunkSection paletteSection, NibbleArray blockLight, NibbleArray skyLight) {
|
|
||||||
for (int y = 0; y < 16; y++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
Block block = section.getBlock(x, y, z);
|
|
||||||
PaletteCoords paletteCoords = PaletteCoords.createByBlock(block);
|
|
||||||
|
|
||||||
paletteSection.addBlock(x, y, z, block);
|
|
||||||
blockLight.set(paletteCoords.getX(), paletteCoords.getY(), paletteCoords.getZ(), block.getLight());
|
|
||||||
skyLight.set(paletteCoords.getX(), paletteCoords.getY(), paletteCoords.getZ(), section.getSkyLight(x, y, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
private static class AvailableSections {
|
private static class AvailableSections {
|
||||||
|
|||||||
50
protocol/src/main/java/mc/protocol/utils/Bit13LongArray.java
Normal file
50
protocol/src/main/java/mc/protocol/utils/Bit13LongArray.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package mc.protocol.utils;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class Bit13LongArray implements NibbleArray {
|
||||||
|
|
||||||
|
private static final int BITS = 13;
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
|
private long longValue = 0L;
|
||||||
|
private int nibbleIndex = 0;
|
||||||
|
private int lastWriteIndex = 0;
|
||||||
|
|
||||||
|
public Bit13LongArray(int capacity) {
|
||||||
|
this.buffer = ByteBuffer.allocate(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(int value) {
|
||||||
|
if (Integer.bitCount(value) > BITS) {
|
||||||
|
throw new IllegalArgumentException("Value is to big: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@formetter:off
|
||||||
|
int headValueIndex = ((nibbleIndex + 1) * BITS - 1) / Long.SIZE;
|
||||||
|
int tailValueIndex = ((nibbleIndex ) * BITS ) / Long.SIZE;
|
||||||
|
int offsetValue = ((nibbleIndex ) * BITS ) % Long.SIZE;
|
||||||
|
//@formetter:on
|
||||||
|
|
||||||
|
if (tailValueIndex != lastWriteIndex) {
|
||||||
|
buffer.asLongBuffer().put(longValue);
|
||||||
|
lastWriteIndex++;
|
||||||
|
longValue = 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
longValue |= ((long) value << offsetValue);
|
||||||
|
nibbleIndex++;
|
||||||
|
|
||||||
|
if (headValueIndex != tailValueIndex) {
|
||||||
|
buffer.asLongBuffer().put(longValue);
|
||||||
|
lastWriteIndex++;
|
||||||
|
longValue = value >> (Long.SIZE - offsetValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer byteBuffer() {
|
||||||
|
return this.buffer.rewind();
|
||||||
|
}
|
||||||
|
}
|
||||||
38
protocol/src/main/java/mc/protocol/utils/HalfByteArray.java
Normal file
38
protocol/src/main/java/mc/protocol/utils/HalfByteArray.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package mc.protocol.utils;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class HalfByteArray implements NibbleArray {
|
||||||
|
|
||||||
|
private static final int BITS = 4;
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
private Byte halfValue = null;
|
||||||
|
|
||||||
|
public HalfByteArray(int capacity) {
|
||||||
|
this.buffer = ByteBuffer.allocate(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(int value) {
|
||||||
|
if (Integer.bitCount(value) > BITS) {
|
||||||
|
throw new IllegalArgumentException("Value is to big: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (halfValue == null) {
|
||||||
|
halfValue = (byte) (value << BITS);
|
||||||
|
} else {
|
||||||
|
buffer.put((byte) (halfValue | value));
|
||||||
|
halfValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer byteBuffer() {
|
||||||
|
if (halfValue != null) {
|
||||||
|
buffer.put(halfValue);
|
||||||
|
halfValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.buffer.rewind();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,10 @@
|
|||||||
package mc.protocol.utils;
|
package mc.protocol.utils;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
public interface NibbleArray {
|
||||||
public class NibbleArray {
|
|
||||||
|
|
||||||
private final byte[] data;
|
void put(int value);
|
||||||
|
|
||||||
public NibbleArray(int capacity) {
|
ByteBuffer byteBuffer();
|
||||||
this.data = new byte[capacity];
|
|
||||||
}
|
|
||||||
|
|
||||||
public NibbleArray() {
|
|
||||||
this(2048);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int get(int x, int y, int z) {
|
|
||||||
int idx = coordsToIndex(x, y, z);
|
|
||||||
|
|
||||||
int ni = nibbleIndex(idx);
|
|
||||||
return isLowerNibble(idx) ? this.data[ni] & 0x0F : this.data[ni] >> 4 & 0x0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set(int x, int y, int z, int value) {
|
|
||||||
//@formatter:off
|
|
||||||
if (value < 0) value = 0;
|
|
||||||
else if (value > 15) value = 15;
|
|
||||||
//@formatter:on
|
|
||||||
|
|
||||||
int idx = coordsToIndex(x, y, z);
|
|
||||||
int ni = nibbleIndex(idx);
|
|
||||||
|
|
||||||
if (isLowerNibble(idx)) {
|
|
||||||
this.data[ni] = (byte) (value);
|
|
||||||
} else {
|
|
||||||
this.data[ni] = (byte) (this.data[ni] | value << 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getRawData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int coordsToIndex(int x, int y, int z) {
|
|
||||||
return y << 8 | z << 4 | x;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int nibbleIndex(int index) {
|
|
||||||
return index >> 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLowerNibble(int index) {
|
|
||||||
return (index & 1) == 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
package mc.protocol.utils;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import mc.protocol.buffer.NetByteBuf;
|
|
||||||
import mc.protocol.world.Block;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class PaletteChunkSection {
|
|
||||||
|
|
||||||
private final byte[] blocks = new byte[4096];
|
|
||||||
private final List<Integer> palette = new ArrayList<>();
|
|
||||||
|
|
||||||
public void addBlock(int x, int y, int z, Block block) {
|
|
||||||
blocks[coordsToIndex(x, y, z)] = putPalette(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int bitsPerBlock() {
|
|
||||||
if (palette.size() <= 15) {
|
|
||||||
return 4;
|
|
||||||
} else if (palette.size() <= 31) {
|
|
||||||
return 5;
|
|
||||||
} else if (palette.size() <= 63) {
|
|
||||||
return 6;
|
|
||||||
} else if (palette.size() <= 127) {
|
|
||||||
return 7;
|
|
||||||
} else if (palette.size() <= 255) {
|
|
||||||
return 8;
|
|
||||||
} else {
|
|
||||||
return 13;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writePalette(NetByteBuf netByteBuf) {
|
|
||||||
netByteBuf.writeVarInt(palette.size()); // Size of palette
|
|
||||||
palette.forEach(netByteBuf::writeVarInt); // Palette
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getIndexBlockInPalette(int x, int y, int z) {
|
|
||||||
return blocks[coordsToIndex(x, y, z)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private int coordsToIndex(int x, int y, int z) {
|
|
||||||
return y << 8 | z << 4 | x;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte putPalette(Block block) {
|
|
||||||
int blockState = (block.getId() << 4) | block.getMeta();
|
|
||||||
|
|
||||||
int idx = palette.indexOf(blockState);
|
|
||||||
if (idx == -1) {
|
|
||||||
palette.add(blockState);
|
|
||||||
idx = palette.size() - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (byte) idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
@Getter
|
|
||||||
public static class PaletteCoords {
|
|
||||||
private final int x;
|
|
||||||
private final int y;
|
|
||||||
private final int z;
|
|
||||||
|
|
||||||
public static PaletteCoords createByBlock(Block block) {
|
|
||||||
int bx = (int) block.getLocation().getX() - (((int) block.getLocation().getX() >> 4) << 4);
|
|
||||||
int by = (int) block.getLocation().getY() - (((int) block.getLocation().getY() >> 4) << 4);
|
|
||||||
int bz = (int) block.getLocation().getZ() - (((int) block.getLocation().getZ() >> 4) << 4);
|
|
||||||
|
|
||||||
return new PaletteCoords(bx, by, bz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user