From badee15c46415e55b9357da2841b582b9c761233 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 14 Nov 2021 17:59:14 -0500 Subject: [PATCH] Implement V0 bits-per-block for Bedrock This also improves sending empty chunks by having an empty BlockStorage array. The empty biome data has shrunk from 32k bytes to 65. With thanks to @dktapps. --- .../JavaLevelChunkWithLightTranslator.java | 31 +++++++++- .../translators/world/BiomeTranslator.java | 4 +- .../translators/world/chunk/BlockStorage.java | 2 +- .../world/chunk/bitarray/BitArray.java | 10 +++ .../world/chunk/bitarray/BitArrayVersion.java | 5 +- .../SingletonBitArray.java} | 61 +++++++++++-------- .../populator/BlockRegistryPopulator.java | 3 - .../registry/type/BlockMappings.java | 3 - .../geysermc/connector/utils/ChunkUtils.java | 22 ++++++- 9 files changed, 105 insertions(+), 36 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/{EmptyChunkProvider.java => bitarray/SingletonBitArray.java} (52%) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkWithLightTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkWithLightTranslator.java index eded821a8..18ed24c64 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkWithLightTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/level/JavaLevelChunkWithLightTranslator.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.chunk.ChunkSection; import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.GlobalPalette; import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette; +import com.github.steveice10.mc.protocol.data.game.chunk.palette.SingletonPalette; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLevelChunkWithLightPacket; @@ -47,6 +48,7 @@ import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufOutputStream; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntLists; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -59,6 +61,7 @@ import org.geysermc.connector.network.translators.world.chunk.BlockStorage; import org.geysermc.connector.network.translators.world.chunk.GeyserChunkSection; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; +import org.geysermc.connector.network.translators.world.chunk.bitarray.SingletonBitArray; import org.geysermc.connector.registry.BlockRegistries; import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.ChunkUtils; @@ -145,6 +148,22 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator VarInts.writeInt(buffer, id)); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java index 5c278eb9c..c9621b144 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArray.java @@ -25,6 +25,9 @@ package org.geysermc.connector.network.translators.world.chunk.bitarray; +import com.nukkitx.network.VarInts; +import io.netty.buffer.ByteBuf; + public interface BitArray { void set(int index, int value); @@ -33,6 +36,13 @@ public interface BitArray { int size(); + /** + * Overridden if the bit array implementation does not require size. + */ + default void writeSizeToNetwork(ByteBuf buffer, int size) { + VarInts.writeInt(buffer, size); + } + int[] getWords(); BitArrayVersion getVersion(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java index f45e9f6b9..d39a1216f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/BitArrayVersion.java @@ -35,7 +35,8 @@ public enum BitArrayVersion { V4(4, 8, V5), V3(3, 10, V4), // 2 bit padding V2(2, 16, V3), - V1(1, 32, V2); + V1(1, 32, V2), + V0(0, 0, V1); private static final BitArrayVersion[] VALUES = values(); @@ -94,6 +95,8 @@ public enum BitArrayVersion { if (this == V3 || this == V5 || this == V6) { // Padded palettes aren't able to use bitwise operations due to their padding. return new PaddedBitArray(this, size, words); + } else if (this == V0) { + return new SingletonBitArray(); } else { return new Pow2BitArray(this, size, words); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/SingletonBitArray.java similarity index 52% rename from connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java rename to connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/SingletonBitArray.java index afef277ab..db4e05301 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/bitarray/SingletonBitArray.java @@ -23,36 +23,49 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.world.chunk; +package org.geysermc.connector.network.translators.world.chunk.bitarray; -import com.nukkitx.nbt.NBTOutputStream; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; -import lombok.Getter; +import io.netty.buffer.ByteBuf; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +public class SingletonBitArray implements BitArray { + public static final SingletonBitArray INSTANCE = new SingletonBitArray(); -public class EmptyChunkProvider { - @Getter - private final byte[] emptyLevelChunkData; - @Getter - private final GeyserChunkSection emptySection; + private static final int[] EMPTY_ARRAY = new int[0]; - public EmptyChunkProvider(int airId) { - BlockStorage emptyStorage = new BlockStorage(airId); - emptySection = new GeyserChunkSection(new BlockStorage[]{emptyStorage}); + public SingletonBitArray() { + } - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size + @Override + public void set(int index, int value) { + } - try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { - stream.writeTag(NbtMap.EMPTY); - } + @Override + public int get(int index) { + return 0; + } - emptyLevelChunkData = outputStream.toByteArray(); - } catch (IOException e) { - throw new AssertionError("Unable to generate empty level chunk data"); - } + @Override + public int size() { + return 1; + } + + @Override + public void writeSizeToNetwork(ByteBuf buffer, int size) { + // no-op - size is fixed + } + + @Override + public int[] getWords() { + return EMPTY_ARRAY; + } + + @Override + public BitArrayVersion getVersion() { + return BitArrayVersion.V0; + } + + @Override + public SingletonBitArray copy() { + return new SingletonBitArray(); } } diff --git a/connector/src/main/java/org/geysermc/connector/registry/populator/BlockRegistryPopulator.java b/connector/src/main/java/org/geysermc/connector/registry/populator/BlockRegistryPopulator.java index a4388388a..2beaeff79 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/populator/BlockRegistryPopulator.java +++ b/connector/src/main/java/org/geysermc/connector/registry/populator/BlockRegistryPopulator.java @@ -38,8 +38,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIntPair; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.chunk.BlockStorage; -import org.geysermc.connector.network.translators.world.chunk.GeyserChunkSection; import org.geysermc.connector.registry.BlockRegistries; import org.geysermc.connector.registry.type.BlockMapping; import org.geysermc.connector.registry.type.BlockMappings; @@ -200,7 +198,6 @@ public class BlockRegistryPopulator { builder.bedrockBlockStates(blocksTag); BlockRegistries.BLOCKS.register(palette.getKey().valueInt(), builder.blockStateVersion(stateVersion) - .emptyChunkSection(new GeyserChunkSection(new BlockStorage[]{new BlockStorage(airRuntimeId)})) .javaToBedrockBlocks(javaToBedrockBlocks) .javaIdentifierToBedrockTag(javaIdentifierToBedrockTag) .itemFrames(itemFrames) diff --git a/connector/src/main/java/org/geysermc/connector/registry/type/BlockMappings.java b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMappings.java index c07f2edfa..8e1cd6a1a 100644 --- a/connector/src/main/java/org/geysermc/connector/registry/type/BlockMappings.java +++ b/connector/src/main/java/org/geysermc/connector/registry/type/BlockMappings.java @@ -31,7 +31,6 @@ import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; import lombok.Builder; import lombok.Value; -import org.geysermc.connector.network.translators.world.chunk.GeyserChunkSection; import java.util.Map; @@ -44,8 +43,6 @@ public class BlockMappings { int blockStateVersion; - GeyserChunkSection emptyChunkSection; - int[] javaToBedrockBlocks; NbtList bedrockBlockStates; diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 8db82c0ae..48e1a9622 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -36,6 +36,7 @@ import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntLists; import lombok.experimental.UtilityClass; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; @@ -43,6 +44,8 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity; import org.geysermc.connector.network.translators.world.chunk.BlockStorage; +import org.geysermc.connector.network.translators.world.chunk.GeyserChunkSection; +import org.geysermc.connector.network.translators.world.chunk.bitarray.SingletonBitArray; import org.geysermc.connector.registry.BlockRegistries; import static org.geysermc.connector.network.translators.world.block.BlockStateValues.JAVA_AIR_ID; @@ -60,13 +63,30 @@ public class ChunkUtils { public static final int MAXIMUM_ACCEPTED_HEIGHT = 256; public static final int MAXIMUM_ACCEPTED_HEIGHT_OVERWORLD = 384; + /** + * An empty subchunk. + */ + public static final byte[] SERIALIZED_CHUNK_DATA; + /** + * An empty chunk that can be safely passed on to a LevelChunkPacket with subcounts set to 0. + */ public static final byte[] EMPTY_CHUNK_DATA; public static final byte[] EMPTY_BIOME_DATA; static { ByteBuf byteBuf = Unpooled.buffer(); try { - BlockStorage blockStorage = new BlockStorage(0); + new GeyserChunkSection(new BlockStorage[0]) + .writeToNetwork(byteBuf); + SERIALIZED_CHUNK_DATA = new byte[byteBuf.readableBytes()]; + byteBuf.readBytes(SERIALIZED_CHUNK_DATA); + } finally { + byteBuf.release(); + } + + byteBuf = Unpooled.buffer(); + try { + BlockStorage blockStorage = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(0)); blockStorage.writeToNetwork(byteBuf); EMPTY_BIOME_DATA = new byte[byteBuf.readableBytes()];