From deab6a0caca366c447be35a25f2b1b84703593d1 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Tue, 5 Nov 2024 12:03:16 +0100 Subject: [PATCH] Don't allocate bufs in 1.18/1.20 chunk types Unlike Mojang, we don't sneak a byte array of data outside of the actual packet reading, so there's no reason to not just use the original buffer. Slicing doesn't allocate anything while also setting the reader index correctly, as also used in the previous types --- .../viaversion/api/type/types/VarIntType.java | 22 ++++++---- .../types/chunk/ChunkSectionType1_18.java | 11 +++++ .../api/type/types/chunk/ChunkType1_18.java | 23 +++------- .../api/type/types/chunk/ChunkType1_20_2.java | 23 +++------- .../api/type/types/chunk/PaletteType1_18.java | 44 ++++++++++++++++--- 5 files changed, 73 insertions(+), 50 deletions(-) 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; + } }