mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
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.
This commit is contained in:
parent
61f20217a9
commit
badee15c46
9 changed files with 105 additions and 36 deletions
|
@ -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<Clientbo
|
|||
continue;
|
||||
}
|
||||
|
||||
if (javaPalette instanceof SingletonPalette) {
|
||||
// There's only one block here. Very easy!
|
||||
int javaId = javaPalette.idToState(0);
|
||||
int bedrockId = session.getBlockMappings().getBedrockBlockId(javaId);
|
||||
BlockStorage blockStorage = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(bedrockId));
|
||||
|
||||
if (BlockRegistries.WATERLOGGED.get().contains(javaId)) {
|
||||
BlockStorage waterlogged = new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(session.getBlockMappings().getBedrockWaterId()));
|
||||
sections[bedrockSectionY] = new GeyserChunkSection(new BlockStorage[] {blockStorage, waterlogged});
|
||||
} else {
|
||||
sections[bedrockSectionY] = new GeyserChunkSection(new BlockStorage[] {blockStorage});
|
||||
}
|
||||
// If a chunk contains all of the same piston or flower pot then god help us
|
||||
continue;
|
||||
}
|
||||
|
||||
IntList bedrockPalette = new IntArrayList(javaPalette.size());
|
||||
waterloggedPaletteIds.clear();
|
||||
pistonOrFlowerPaletteIds.clear();
|
||||
|
@ -270,7 +289,11 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
int size = 0;
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
GeyserChunkSection section = sections[i];
|
||||
size += (section != null ? section : session.getBlockMappings().getEmptyChunkSection()).estimateNetworkSize();
|
||||
if (section != null) {
|
||||
size += section.estimateNetworkSize();
|
||||
} else {
|
||||
size += SERIALIZED_CHUNK_DATA.length;
|
||||
}
|
||||
}
|
||||
size += ChunkUtils.EMPTY_CHUNK_DATA.length; // Consists only of biome data
|
||||
size += 1; // Border blocks
|
||||
|
@ -281,7 +304,11 @@ public class JavaLevelChunkWithLightTranslator extends PacketTranslator<Clientbo
|
|||
byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
GeyserChunkSection section = sections[i];
|
||||
(section != null ? section : session.getBlockMappings().getEmptyChunkSection()).writeToNetwork(byteBuf);
|
||||
if (section != null) {
|
||||
section.writeToNetwork(byteBuf);
|
||||
} else {
|
||||
byteBuf.writeBytes(SERIALIZED_CHUNK_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we're dealing with Bedrock chunk sections
|
||||
|
|
|
@ -29,9 +29,11 @@ import com.github.steveice10.mc.protocol.data.game.chunk.DataPalette;
|
|||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.SingletonPalette;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntLists;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
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.Registries;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -107,7 +109,7 @@ public class BiomeTranslator {
|
|||
|
||||
if (biomeData.getPalette() instanceof SingletonPalette palette) {
|
||||
int biomeId = biomeTranslations.get(palette.idToState(0));
|
||||
return new BlockStorage(biomeId);
|
||||
return new BlockStorage(SingletonBitArray.INSTANCE, IntLists.singleton(biomeId));
|
||||
} else {
|
||||
BlockStorage storage = new BlockStorage(0);
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ public class BlockStorage {
|
|||
buffer.writeIntLE(word);
|
||||
}
|
||||
|
||||
VarInts.writeInt(buffer, palette.size());
|
||||
bitArray.writeSizeToNetwork(buffer, palette.size());
|
||||
palette.forEach((IntConsumer) id -> VarInts.writeInt(buffer, id));
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size
|
||||
|
||||
try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) {
|
||||
stream.writeTag(NbtMap.EMPTY);
|
||||
public SingletonBitArray() {
|
||||
}
|
||||
|
||||
emptyLevelChunkData = outputStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Unable to generate empty level chunk data");
|
||||
@Override
|
||||
public void set(int index, int value) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int get(int index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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<NbtMap> bedrockBlockStates;
|
||||
|
|
|
@ -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()];
|
||||
|
|
Loading…
Reference in a new issue