Archived
0

сериализация полного чанка

This commit is contained in:
2021-06-27 18:54:30 +03:00
parent 9ed6db2484
commit c1854c8f73
18 changed files with 203 additions and 190 deletions

View File

@@ -2,17 +2,12 @@ package mc.protocol.packets.play.server;
import io.netty.buffer.Unpooled;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.pool.ProtocolObjectPool;
import mc.protocol.world.Block;
import mc.protocol.utils.ChunkSerializeUtil;
import mc.protocol.world.Chunk;
import mc.protocol.world.ChunkSection;
import mc.utils.array.BitArray;
import mc.utils.array.BitByteArray;
import mc.utils.array.BitLongArray;
/**
* Данные чанка.
@@ -79,9 +74,7 @@ import mc.utils.array.BitLongArray;
@Data
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 _16_16_16 = 16 * 16 * 16;
private Chunk chunk;
@@ -90,12 +83,10 @@ public class ChunkDataPacket implements ServerSidePacket {
netByteBuf.writeInt(chunk.getX()); // Chunk X
netByteBuf.writeInt(chunk.getZ()); // Chunk Z
AvailableSections availableSections = createAvailableSections();
boolean fullChunk = availableSections.getBitMask() == FULL_BIT_MASK;
netByteBuf.writeBoolean(fullChunk); // Is Full chunk
netByteBuf.writeVarInt(availableSections.getBitMask()); // Available Sections
netByteBuf.writeBoolean(true); // Is Full chunk
netByteBuf.writeVarInt(FULL_BIT_MASK); // Available Sections
NetByteBuf data = createDataStructure(availableSections.getMaxHeight(), fullChunk);
NetByteBuf data = createDataStructure();
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
netByteBuf.writeBytes(data); // Data
@@ -105,108 +96,26 @@ public class ChunkDataPacket implements ServerSidePacket {
ProtocolObjectPool.getNetByteBufPool().returnObject(data);
}
private AvailableSections createAvailableSections() {
int bitMask = 0;
int maxH = 0;
for (int h = 15; h >= 0; h--) {
bitMask = bitMask << 1;
ChunkSection chunkSection = chunk.getSection(h);
if (chunkSection != null && chunkSection.getY() == h) {
bitMask |= 0x01;
maxH++;
} else {
bitMask |= 0x00;
}
}
return new AvailableSections(bitMask, maxH);
}
private NetByteBuf createDataStructure(int maxHeight, boolean fillBiomes) {
private NetByteBuf createDataStructure() {
NetByteBuf dataStructure = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
NetByteBuf biomes = fillBiomes ? ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer()) : null;
for (int h = 0; h < maxHeight; h++) {
for (int h = 0; h < 16; h++) {
ChunkSection section = chunk.getSection(h);
if (section == null) {
continue;
}
NetByteBuf data = createData(section, biomes);
NetByteBuf data = ChunkSerializeUtil.serializeSection(section);
dataStructure.writeBytes(data); // Data
ProtocolObjectPool.getNetByteBufPool().returnObject(data);
}
if (fillBiomes) {
dataStructure.writeBytes(biomes); // Biomes
ProtocolObjectPool.getNetByteBufPool().returnObject(biomes);
// <Biomes>
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
dataStructure.writeByte(chunk.getBiome(
(chunk.getX() << 4) + x,
(chunk.getZ() << 4) + z));
}
}
// </Biomes>
return dataStructure;
}
private NetByteBuf createData(ChunkSection section, NetByteBuf biomes) {
NetByteBuf data = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
BitArray blockLight = new BitByteArray(4, 2048 * 2);
BitArray skyLight = new BitByteArray(4, 2048 * 2);
// <Bits Per Block>
data.writeUnsignedByte(BITS_PER_BLOCK);
// </Bits Per Block>
// <Palette>
data.writeVarInt(0); // Direct mode
// </Palette>
// <Data Array Length>
data.writeVarInt(_16_16_16);
// </Data Array Length>
// <Data Array>
BitArray dataArray = new BitLongArray(BITS_PER_BLOCK, _16_16_16);
boolean writeBiomes = biomes != null;
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);
int blockState = (block.getId() << 4) | block.getMeta();
dataArray.put(blockState);
blockLight.put(block.getLight());
skyLight.put(section.getSkyLight(x, y, z));
if (writeBiomes) {
biomes.writeByte(chunk.getBiome(
(chunk.getX() << 4) + x,
(chunk.getZ() << 4) + z));
if (x == 15 && z == 15) {
writeBiomes = false;
}
}
}
}
}
data.writeBytes(dataArray.byteBuffer());
// </Data Array>
// <Block Light>
data.writeBytes(blockLight.byteBuffer());
// </Block Light>
// <Sky Light>
data.writeBytes(skyLight.byteBuffer());
// </Sky Light>
return data;
}
@RequiredArgsConstructor
@Getter
private static class AvailableSections {
private final int bitMask;
private final int maxHeight;
}
}

View File

@@ -0,0 +1,48 @@
package mc.protocol.utils;
import io.netty.buffer.Unpooled;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.pool.ProtocolObjectPool;
import mc.protocol.world.Block;
import mc.protocol.world.ChunkSection;
import mc.utils.array.BitArray;
import mc.utils.array.BitByteArray;
import mc.utils.array.BitLongArray;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ChunkSerializeUtil {
private static final int BITS_PER_BLOCK = 13;
private static final int ALL_BLOCKS = 16 * 16 * 16;
public static NetByteBuf serializeSection(ChunkSection section) {
BitArray blockArray = new BitLongArray(BITS_PER_BLOCK, ALL_BLOCKS);
BitArray blockLight = new BitByteArray(4, ALL_BLOCKS);
BitArray skyLight = new BitByteArray(4, ALL_BLOCKS);
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);
int blockState = (block.getId() << 4) | block.getMeta();
blockArray.put(blockState);
blockLight.put(block.getLight());
skyLight.put(section.getSkyLight(x, y, z));
}
}
}
NetByteBuf result = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
result.writeUnsignedByte(BITS_PER_BLOCK); // Bits Per Block
result.writeVarInt(0); // Palette, Direct mode
result.writeVarInt(blockArray.size()); // Data Array Length
result.writeBytes(blockArray.byteBuffer()); // Data Array
result.writeBytes(blockLight.byteBuffer()); // Block Light
result.writeBytes(skyLight.byteBuffer()); // Sky Light
return result;
}
}

View File

@@ -67,7 +67,7 @@ public class Main {
//endregion
ServerComponent serverComponent = DaggerServerComponent.builder()
.processorModule(new ScenarioModule(configComponent.getConfig()))
.scenarioModule(new ScenarioModule(configComponent.getConfig()))
.build();
serverComponent.getPacketScenarios().forEach(scenario -> scenario.setup(serverComponent.getProtocolHandlersBus()));

View File

@@ -3,7 +3,7 @@ package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.world.World;
import mc.server.world.VoidWorld;
import mc.server.world.SomeWorld;
import javax.inject.Singleton;
@@ -13,6 +13,6 @@ public class WorldModule {
@Provides
@Singleton
World provideWorld() {
return new VoidWorld();
return new SomeWorld();
}
}

View File

@@ -37,7 +37,7 @@ public class ScenarioLogin implements PacketScenario {
}
private void login(ChannelHandlerContext ctx, LoginStartPacket packet) {
Player player = playerManager.create(ctx, packet.getName(), GameMode.SURVIVAL, world.getSpawn());
Player player = playerManager.create(ctx, packet.getName(), GameMode.CREATIVE, world.getSpawn());
ctx.channel().attr(ServetAttributes.PLAYER).set(player);
sendLoginSuccess(player);
@@ -85,7 +85,7 @@ public class ScenarioLogin implements PacketScenario {
var playerAbilitiesPacket = new PlayerAbilitiesPacket();
playerAbilitiesPacket.setCatFly(true);
playerAbilitiesPacket.setFlying(true);
playerAbilitiesPacket.setCreativeMode(false);
playerAbilitiesPacket.setCreativeMode(true);
playerAbilitiesPacket.setInvulnerable(true);
playerAbilitiesPacket.setFieldOfView(0.0f);
playerAbilitiesPacket.setFlyingSpeed(0.05f);
@@ -96,7 +96,8 @@ public class ScenarioLogin implements PacketScenario {
@SuppressWarnings("java:S2589")
private void sendWorldData(Player player) {
Location chunkLocation = LocationUtils.toChunkXZ(player.getLocation());
Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
// Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
Chunk chunk = world.getChunk(-50, -16);
var chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setChunk(chunk);
@@ -112,12 +113,15 @@ public class ScenarioLogin implements PacketScenario {
for (int x = minX; x <= maxX; x++) {
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setChunk(world.getChunk(x, z));
chunk = world.getChunk(x, z);
if (chunk != null) {
chunkDataPacket.setChunk(chunk);
player.getCtx().write(chunkDataPacket);
}
}
}
}
}
player.getCtx().flush();
}

View File

@@ -1,33 +0,0 @@
package mc.server.world;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.model.Location;
import mc.protocol.world.Block;
@RequiredArgsConstructor
public class AirBlock implements Block {
@Getter
private final Location location;
@Override
public int getId() {
return 0;
}
@Override
public int getMeta() {
return 0;
}
@Override
public int getLight() {
return 0;
}
@Override
public void setLight(int value) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,14 @@
package mc.server.world;
import lombok.Data;
import mc.protocol.model.Location;
import mc.protocol.world.Block;
@Data
public class SomeBlock implements Block {
private int id;
private int meta;
private int light;
private Location location;
}

View File

@@ -8,19 +8,19 @@ import java.util.HashMap;
import java.util.Map;
@Data
public class VoidChunk implements Chunk {
public class SomeChunk implements Chunk {
private final int x;
private final int z;
private final Map<Integer, VoidChunkSection> sections = new HashMap<>();
private final Map<Integer, ChunkSection> sections = new HashMap<>();
@Override
public ChunkSection getSection(int height) {
return sections.computeIfAbsent(height, VoidChunkSection::new);
return sections.computeIfAbsent(height, SomeChunkSection::new);
}
@Override
public byte getBiome(int x, int z) {
return 127; // 127 | 7F | minecraft:void | The Void
return 0; //ocean
}
}

View File

@@ -0,0 +1,56 @@
package mc.server.world;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.model.Location;
import mc.protocol.world.Block;
import mc.protocol.world.ChunkSection;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@Getter
public class SomeChunkSection implements ChunkSection {
private final int y;
private final Map<Location, Block> blocks = new HashMap<>();
@Override
public Block getBlock(int x, int y, int z) {
if (this.y == 15) {
if (y == 0) {
return blocks.computeIfAbsent(new Location().set(x, y, z), location -> {
SomeBlock block = new SomeBlock();
block.setLocation(location);
block.setId(3);
return block;
});
} else {
return blocks.computeIfAbsent(new Location().set(x, y, z), location -> {
SomeBlock block = new SomeBlock();
block.setLocation(location);
block.setId(0);
return block;
});
}
} else {
return blocks.computeIfAbsent(new Location().set(x, y, z), location -> {
SomeBlock block = new SomeBlock();
block.setLocation(location);
block.setId(1);
return block;
});
}
}
@Override
public int getSkyLight(int x, int y, int z) {
if (this.y == 15 && y != 0) {
return 15;
} else {
return 0;
}
}
}

View File

@@ -3,14 +3,14 @@ package mc.server.world;
import mc.protocol.model.Location;
import mc.protocol.pool.ProtocolObjectPool;
import mc.protocol.utils.LevelType;
import mc.protocol.utils.Table;
import mc.protocol.world.Chunk;
import mc.protocol.world.World;
import mc.utils.Table;
public class VoidWorld implements World {
public class SomeWorld implements World {
private static final Location spawn = ProtocolObjectPool.getLocationPool().borrowObject().set(7d, 130d, 7d);
private final Table<Integer, Integer, VoidChunk> chunkTable = new Table<>();
private static final Location spawn = ProtocolObjectPool.getLocationPool().borrowObject().set(-790d, 256d, -263d + 16d);
private final Table<Integer, Integer, Chunk> chunkTable = new Table<>();
@Override
public LevelType getLevelType() {
@@ -19,14 +19,18 @@ public class VoidWorld implements World {
@Override
public Location getSpawn() {
return VoidWorld.spawn;
return SomeWorld.spawn;
}
@Override
public Chunk getChunk(int x, int z) {
VoidChunk chunk = chunkTable.getColumnAndRow(x, z);
if (x != -50 && z != -16) {
return null;
}
Chunk chunk = chunkTable.getColumnAndRow(x, z);
if (chunk == null) {
chunk = new VoidChunk(x, z);
chunk = new SomeChunk(x, z);
chunkTable.put(x, z, chunk);
}

View File

@@ -1,29 +0,0 @@
package mc.server.world;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.model.Location;
import mc.protocol.world.Block;
import mc.protocol.world.ChunkSection;
import java.util.HashMap;
import java.util.Map;
@RequiredArgsConstructor
@Getter
public class VoidChunkSection implements ChunkSection {
private final int y;
private final Map<Location, AirBlock> blocks = new HashMap<>();
@Override
public Block getBlock(int x, int y, int z) {
return blocks.computeIfAbsent(new Location().set(x, y, z), AirBlock::new);
}
@Override
public int getSkyLight(int x, int y, int z) {
return 0;
}
}

View File

@@ -23,5 +23,5 @@ icon {
}
world {
view-distance: 1
view-distance: 0
}

View File

@@ -8,8 +8,16 @@
</layout>
</appender>
<!--<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>out.log</file>
<encoder>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%35.35logger{34}] &#45;&#45; %msg%n</Pattern>
</encoder>
</appender>-->
<root level="info">
<appender-ref ref="CONSOLE"/>
<!--<appender-ref ref="FILE"/>-->
</root>
<logger name="LAUNCHER" level="debug" additivity="false">

View File

@@ -10,4 +10,5 @@ public interface BitArray {
int get(int index);
ByteBuffer byteBuffer();
int size();
}

View File

@@ -2,6 +2,8 @@ package mc.utils.array;
public class BitByteArray extends AbstractBitBufferArray {
private int countElements;
public BitByteArray(int bitPerEntity, int arraySize, boolean direct) {
super(bitPerEntity, arraySize, direct);
}
@@ -55,8 +57,16 @@ public class BitByteArray extends AbstractBitBufferArray {
}
}
@Override
public int size() {
return countElements;
}
@Override
protected int calculateCapacity() {
return (arraySize * bitPerEntity / Byte.SIZE + 1) * Byte.BYTES;
int bits = arraySize * bitPerEntity;
int var1 = bits % Byte.SIZE;
this.countElements = bits / Byte.SIZE + (var1 > 0 ? 1 : 0);
return countElements;
}
}

View File

@@ -5,6 +5,7 @@ import java.nio.LongBuffer;
public class BitLongArray extends AbstractBitBufferArray {
private final LongBuffer longBuffer;
private int countElements;
public BitLongArray(int bitPerEntity, int arraySize, boolean direct) {
super(bitPerEntity, arraySize, direct);
@@ -60,8 +61,17 @@ public class BitLongArray extends AbstractBitBufferArray {
}
}
@Override
public int size() {
return countElements;
}
@Override
protected int calculateCapacity() {
return (arraySize * bitPerEntity / Long.SIZE + 1) * Long.BYTES;
int bits = arraySize * bitPerEntity;
int var1 = bits % Long.SIZE;
int var2 = (bits + (var1 > 0 ? Long.SIZE - var1 : 0));
this.countElements = var2 / Long.SIZE;
return var2 / Byte.SIZE;
}
}

View File

@@ -17,12 +17,21 @@ class BitByteArrayTest {
assertEquals(1, byteBuffer.limit());
assertEquals(0, byteBuffer.position());
assertEquals(1, byteBuffer.array().length);
assertEquals(1, bitArray.size());
bitArray = new BitByteArray(4, 2);
byteBuffer = bitArray.byteBuffer();
assertEquals(1, byteBuffer.capacity());
assertEquals(1, byteBuffer.limit());
assertEquals(1, bitArray.size());
bitArray = new BitByteArray(4, 3);
byteBuffer = bitArray.byteBuffer();
assertEquals(2, byteBuffer.capacity());
assertEquals(2, byteBuffer.limit());
assertEquals(2, bitArray.size());
}
@Test

View File

@@ -18,12 +18,14 @@ class BitLongArrayTest {
assertEquals(8, byteBuffer.limit());
assertEquals(0, byteBuffer.position());
assertEquals(8, byteBuffer.array().length);
assertEquals(1, bitArray.size());
bitArray = new BitLongArray(13, 5);
byteBuffer = bitArray.byteBuffer();
assertEquals(16, byteBuffer.capacity());
assertEquals(16, byteBuffer.limit());
assertEquals(2, bitArray.size());
}
@Test