diff --git a/api/src/main/java/com/viaversion/viaversion/api/type/types/VarIntType.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/VarIntType.java index 56583a912..5fa3ebf76 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/type/types/VarIntType.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/VarIntType.java @@ -32,6 +32,15 @@ public class VarIntType extends Type implements TypeConverter private static final int VALUE_BITS = 0x7F; private static final int MULTI_BYTE_BITS = ~VALUE_BITS; private static final int MAX_BYTES = 5; + private static final int[] VAR_INT_LENGTHS = new int[65]; + + static { + // Copied from Velocity https://github.com/PaperMC/Velocity/blob/08a42b3723633ea5eb6b96c0bb42180f3c2b07eb/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java#L166 + for (int i = 0; i <= 32; ++i) { + VAR_INT_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d); + } + VAR_INT_LENGTHS[32] = 1; // Special case for the number 0. + } public VarIntType() { super("VarInt", Integer.class); @@ -61,15 +70,6 @@ public class VarIntType extends Type implements TypeConverter buffer.writeByte(value); } - public static int varIntLength(int value) { - int length = 1; - while ((value & MULTI_BYTE_BITS) != 0) { - length++; - value >>>= 7; - } - return length; - } - /** * @deprecated use {@link #readPrimitive(ByteBuf)} for manual reading to avoid wrapping */ @@ -97,4 +97,8 @@ public class VarIntType extends Type implements TypeConverter } throw new UnsupportedOperationException(); } + + public static int varIntLength(final int value) { + return VAR_INT_LENGTHS[Integer.numberOfLeadingZeros(value)]; + } } diff --git a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkSectionType1_18.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkSectionType1_18.java index 3de74488d..56a1a5e77 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkSectionType1_18.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkSectionType1_18.java @@ -22,6 +22,7 @@ */ package com.viaversion.viaversion.api.type.types.chunk; +import com.viaversion.viaversion.api.minecraft.chunks.Chunk; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSection; import com.viaversion.viaversion.api.minecraft.chunks.ChunkSectionImpl; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; @@ -54,4 +55,14 @@ public final class ChunkSectionType1_18 extends Type { blockPaletteType.write(buffer, section.palette(PaletteType.BLOCKS)); biomePaletteType.write(buffer, section.palette(PaletteType.BIOMES)); } + + public int serializedSize(final Chunk chunk) { + int length = 0; + for (final ChunkSection section : chunk.getSections()) { + length += Short.BYTES + + blockPaletteType.serializedSize(section.palette(PaletteType.BLOCKS)) + + biomePaletteType.serializedSize(section.palette(PaletteType.BIOMES)); + } + return length; + } } diff --git a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_18.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_18.java index 1d4a8991d..8cdec0c2b 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_18.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_18.java @@ -52,14 +52,10 @@ public final class ChunkType1_18 extends Type { final CompoundTag heightMap = Types.NAMED_COMPOUND_TAG.read(buffer); // Read sections - final ByteBuf sectionsBuf = buffer.readBytes(Types.VAR_INT.readPrimitive(buffer)); + final ByteBuf sectionsBuf = buffer.readSlice(Types.VAR_INT.readPrimitive(buffer)); final ChunkSection[] sections = new ChunkSection[ySectionCount]; - try { - for (int i = 0; i < ySectionCount; i++) { - sections[i] = sectionType.read(sectionsBuf); - } - } finally { - sectionsBuf.release(); + for (int i = 0; i < ySectionCount; i++) { + sections[i] = sectionType.read(sectionsBuf); } final int blockEntitiesLength = Types.VAR_INT.readPrimitive(buffer); @@ -78,16 +74,9 @@ public final class ChunkType1_18 extends Type { Types.NAMED_COMPOUND_TAG.write(buffer, chunk.getHeightMap()); - final ByteBuf sectionBuffer = buffer.alloc().buffer(); - try { - for (final ChunkSection section : chunk.getSections()) { - sectionType.write(sectionBuffer, section); - } - sectionBuffer.readerIndex(0); - Types.VAR_INT.writePrimitive(buffer, sectionBuffer.readableBytes()); - buffer.writeBytes(sectionBuffer); - } finally { - sectionBuffer.release(); // release buffer + Types.VAR_INT.writePrimitive(buffer, sectionType.serializedSize(chunk)); + for (final ChunkSection section : chunk.getSections()) { + sectionType.write(buffer, section); } Types.VAR_INT.writePrimitive(buffer, chunk.blockEntities().size()); diff --git a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_20_2.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_20_2.java index ffe709afd..3fb8413ce 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_20_2.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/ChunkType1_20_2.java @@ -52,14 +52,10 @@ public final class ChunkType1_20_2 extends Type { final CompoundTag heightMap = Types.COMPOUND_TAG.read(buffer); // Read sections - final ByteBuf sectionsBuf = buffer.readBytes(Types.VAR_INT.readPrimitive(buffer)); + final ByteBuf sectionsBuf = buffer.readSlice(Types.VAR_INT.readPrimitive(buffer)); final ChunkSection[] sections = new ChunkSection[ySectionCount]; - try { - for (int i = 0; i < ySectionCount; i++) { - sections[i] = sectionType.read(sectionsBuf); - } - } finally { - sectionsBuf.release(); + for (int i = 0; i < ySectionCount; i++) { + sections[i] = sectionType.read(sectionsBuf); } final int blockEntitiesLength = Types.VAR_INT.readPrimitive(buffer); @@ -78,16 +74,9 @@ public final class ChunkType1_20_2 extends Type { Types.COMPOUND_TAG.write(buffer, chunk.getHeightMap()); - final ByteBuf sectionBuffer = buffer.alloc().buffer(); - try { - for (final ChunkSection section : chunk.getSections()) { - sectionType.write(sectionBuffer, section); - } - sectionBuffer.readerIndex(0); - Types.VAR_INT.writePrimitive(buffer, sectionBuffer.readableBytes()); - buffer.writeBytes(sectionBuffer); - } finally { - sectionBuffer.release(); // release buffer + Types.VAR_INT.writePrimitive(buffer, sectionType.serializedSize(chunk)); + for (final ChunkSection section : chunk.getSections()) { + sectionType.write(buffer, section); } Types.VAR_INT.writePrimitive(buffer, chunk.blockEntities().size()); diff --git a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/PaletteType1_18.java b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/PaletteType1_18.java index 7e404fb76..6df3d9485 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/PaletteType1_18.java +++ b/api/src/main/java/com/viaversion/viaversion/api/type/types/chunk/PaletteType1_18.java @@ -27,6 +27,7 @@ import com.viaversion.viaversion.api.minecraft.chunks.DataPaletteImpl; import com.viaversion.viaversion.api.minecraft.chunks.PaletteType; import com.viaversion.viaversion.api.type.Type; import com.viaversion.viaversion.api.type.Types; +import com.viaversion.viaversion.api.type.types.VarIntType; import com.viaversion.viaversion.util.CompactArrayUtil; import com.viaversion.viaversion.util.MathUtil; import io.netty.buffer.ByteBuf; @@ -94,13 +95,7 @@ public final class PaletteType1_18 extends Type { return; } - // 1, 2, and 3 bit linear block palettes can't be read by the client - final int min = type == PaletteType.BLOCKS ? 4 : 1; - int bitsPerValue = Math.max(min, MathUtil.ceilLog2(size)); - if (bitsPerValue > type.highestBitsPerValue()) { - bitsPerValue = globalPaletteBits; - } - + final int bitsPerValue = bitsPerValue(size); buffer.writeByte(bitsPerValue); if (bitsPerValue != globalPaletteBits) { @@ -113,4 +108,39 @@ public final class PaletteType1_18 extends Type { Types.LONG_ARRAY_PRIMITIVE.write(buffer, CompactArrayUtil.createCompactArrayWithPadding(bitsPerValue, type.size(), bitsPerValue == globalPaletteBits ? palette::idAt : palette::paletteIndexAt)); } + + private int bitsPerValue(final int size) { + // 1, 2, and 3 bit linear block palettes can't be read by the client + final int min = type == PaletteType.BLOCKS ? 4 : 1; + int bitsPerValue = Math.max(min, MathUtil.ceilLog2(size)); + if (bitsPerValue > type.highestBitsPerValue()) { + bitsPerValue = globalPaletteBits; + } + return bitsPerValue; + } + + public int serializedSize(final DataPalette palette) { + // This is a bit of extra work, but worth it to avoid otherwise having to allocate and write to an extra buffer. + // On top of saving memory, it provides small but measurable speedup compared to writing to a separate buffer and then back + final int size = palette.size(); + final int bitsPerValue = bitsPerValue(size); + int serializedTypesSize = 0; + int serializedValuesSize = 1; // At least one byte for 0 length + if (size == 1) { + serializedTypesSize = VarIntType.varIntLength(palette.idByIndex(0)); + } else { + if (bitsPerValue != globalPaletteBits) { + serializedTypesSize = VarIntType.varIntLength(size); + for (int i = 0; i < size; i++) { + serializedTypesSize += VarIntType.varIntLength(palette.idByIndex(i)); + } + } + + final int valuesPerLong = 64 / bitsPerValue; + final int values = (type.size() + valuesPerLong - 1) / valuesPerLong; + serializedValuesSize = VarIntType.varIntLength(values) + (Long.BYTES * values); + } + + return Byte.BYTES + serializedTypesSize + serializedValuesSize; + } }