Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Velocity's varint reading/writing optimizations #1070

Open
wants to merge 6 commits into
base: 2.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.nio.charset.Charset;

public class ByteBufHelper {

public static int capacity(Object buffer) {
return PacketEvents.getAPI().getNettyManager().getByteBufOperator().capacity(buffer);
}
Expand Down Expand Up @@ -239,29 +240,37 @@ public static Object resetWriterIndex(Object buffer) {
return PacketEvents.getAPI().getNettyManager().getByteBufOperator().resetWriterIndex(buffer);
}

public static int readVarInt(Object buffer) {
int value = 0;
int length = 0;
byte currentByte;
do {
currentByte = readByte(buffer);
value |= (currentByte & 0x7F) << (length * 7);
length++;
if (length > 5) {
throw new RuntimeException("VarInt is too large. Must be smaller than 5 bytes.");
}
} while ((currentByte & 0x80) == 0x80);
return value;
}

public static void writeVarInt(Object buffer, int value) {
while (true) {
if ((value & ~0x7F) == 0) {
writeByte(buffer, value);
return;
}
writeByte(buffer, (value & 0x7F) | 0x80);
value >>>= 7;
public static int getIntLE(Object buffer, int readerIndex) {
return PacketEvents.getAPI().getNettyManager().getByteBufOperator().getIntLE(buffer, readerIndex);
}

public static int readVarInt(Object buf) {
return PacketEvents.getAPI().getNettyManager().getByteBufOperator().readVarInt(buf);
}

//Src: https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
public static void writeVarInt(Object buf, int i) {
if ((i & (0xFFFFFFFF << 7)) == 0) {
// 1 byte case
writeByte(buf, i);
} else if ((i & (0xFFFFFFFF << 14)) == 0) {
// 2 byte case
int w = (i & 0x7F | 0x80) << 8 | (i >>> 7);
writeShort(buf, w);
} else if ((i & (0xFFFFFFFF << 21)) == 0) {
// 3 byte case
int w = ((i & 0x7F | 0x80) << 16) | ((i >>> 7 & 0x7F | 0x80) << 8) | (i >>> 14);
writeMedium(buf, w);
} else if ((i & (0xFFFFFFFF << 28)) == 0) {
// 4 byte case
int w = ((i & 0x7F | 0x80) << 24) | ((i >>> 7 & 0x7F | 0x80) << 16) | ((i >>> 14 & 0x7F | 0x80) << 8) | (i >>> 21);
writeInt(buf, w);
} else {
// 5 byte case (the max size for a VarInt)
// Write the first 4 bytes as an int
int w = ((i & 0x7F | 0x80) << 24) | ((i >>> 7 & 0x7F | 0x80) << 16) | ((i >>> 14 & 0x7F | 0x80) << 8) | (i >>> 21 & 0x7F | 0x80);
writeInt(buf, w); // Write the first 4 bytes
writeByte(buf, i >>> 28); // Write the remaining 5th byte
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.github.retrooper.packetevents.netty.buffer;

import java.nio.charset.Charset;
import java.util.Objects;

public interface ByteBufOperator {
int capacity(Object buffer);
Expand Down Expand Up @@ -49,6 +50,9 @@ public interface ByteBufOperator {
Object getBytes(Object buffer, int index, byte[] destination);
short getUnsignedByte(Object buffer, int index);

int getIntLE(Object buffer, int readerIndex);
int readVarInt(Object buffer);

boolean isReadable(Object buffer);
Object copy(Object buffer);
Object duplicate(Object buffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,18 +387,7 @@ public void writeMedium(int value) {
}

public int readVarInt() {
int value = 0;
int length = 0;
byte currentByte;
do {
currentByte = readByte();
value |= (currentByte & 0x7F) << (length * 7);
length++;
if (length > 5) {
throw new RuntimeException("VarInt is too large. Must be smaller than 5 bytes.");
}
} while ((currentByte & 0x80) == 0x80);
return value;
return ByteBufHelper.readVarInt(this.buffer);//delegate to ByteBufHelper
}

public void writeVarInt(int value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.netty.buffer.ByteBuf;

import java.nio.charset.Charset;
import java.util.Objects;

public class ByteBufOperatorImpl implements ByteBufOperator {
@Override
Expand Down Expand Up @@ -99,6 +100,16 @@ public long readLong(Object buffer) {
return ((ByteBuf)buffer).readLong();
}

@Override
public int getIntLE(Object buffer, int readerIndex) {
return ((ByteBuf) buffer).getIntLE(readerIndex);
}

@Override
public int readVarInt(Object buffer) {
return FastNettyUtils.readVarInt((ByteBuf) buffer);
}

@Override
public void writeByte(Object buffer, int value) {
((ByteBuf)buffer).writeByte(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package io.github.retrooper.packetevents.impl.netty.buffer;

import io.netty.buffer.ByteBuf;

public final class FastNettyUtils {

//Src: https://github.com/PaperMC/Velocity/blob/9cfcfcf2ed5712e792114a3ab824670e25e23526/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftVarintFrameDecoder.java#L82
public static int readVarInt(final ByteBuf buf) {
if (buf.readableBytes() < 4)
return readVarIntSmallBuffer(buf);

// take the last three bytes and check if any of them have the high bit set
final int wholeOrMore = buf.getIntLE(buf.readerIndex());
final int atStop = ~wholeOrMore & 0x808080;
if (atStop == 0) throw new IllegalArgumentException("VarInt too big");

final int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
buf.skipBytes(bitsToKeep >> 3);

// https://github.com/netty/netty/pull/14050#issuecomment-2107750734
int preservedBytes = wholeOrMore & (atStop ^ (atStop - 1));

// https://github.com/netty/netty/pull/14050#discussion_r1597896639
preservedBytes = (preservedBytes & 0x007F007F) | ((preservedBytes & 0x00007F00) >> 1);
preservedBytes = (preservedBytes & 0x00003FFF) | ((preservedBytes & 0x3FFF0000) >> 2);
return preservedBytes;
}

private static int readVarIntSmallBuffer(ByteBuf buf) {
switch (buf.readableBytes()) {
case 3:
return readVarInt3Bytes(buf);
case 2:
return readVarInt2Bytes(buf);
case 1: {
byte val = buf.readByte();
//check if it has the continuation bit set
if ((val & -128) != 0) throw new IllegalArgumentException("VarInt too big for 1 byte");
return val;
}
case 0:
return 0;//I guess 0? Or an Exception?
default:
throw new AssertionError("how");
}
}

private static int readVarInt3Bytes(final ByteBuf buf) {
// Read 3 bytes in little-endian order
final int wholeOrMore = buf.getMediumLE(buf.readerIndex()); // Reads 3 bytes as an int
final int atStop = ~wholeOrMore & 0x808080; // Check for stop bits

// If no stop bits are found, throw an exception
if (atStop == 0) throw new IllegalArgumentException("VarInt too big for 3 bytes");

// Find the position of the first stop bit
final int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
buf.skipBytes(bitsToKeep >> 3); // Skip the processed bytes

// Extract and preserve the valid bytes
int preservedBytes = wholeOrMore & (atStop ^ (atStop - 1));

// Compact the 7-bit chunks
preservedBytes = (preservedBytes & 0x007F007F) | ((preservedBytes & 0x00007F00) >> 1);
preservedBytes = (preservedBytes & 0x00003FFF) | ((preservedBytes & 0x3FFF0000) >> 2);

return preservedBytes;
}

private static int readVarInt2Bytes(final ByteBuf buf) {
// Read 2 bytes in little-endian order
final int wholeOrMore = buf.getShortLE(buf.readerIndex()); // Reads 2 bytes as an integer
final int atStop = ~wholeOrMore & 0x8080; // Identify stop bits in the two bytes

// If no stop bits are found, the VarInt is too large
if (atStop == 0) throw new IllegalArgumentException("VarInt too big for 2 bytes");

// Find the first stop bit
final int bitsToKeep = Integer.numberOfTrailingZeros(atStop) + 1;
buf.skipBytes(bitsToKeep >> 3); // Skip the number of processed bytes

// Extract and preserve the relevant 7-bit chunks
int preservedBytes = wholeOrMore & (atStop ^ (atStop - 1));

// Compact the 7-bit chunks into a single integer
preservedBytes = (preservedBytes & 0x007F) | ((preservedBytes & 0x7F00) >> 1);

return preservedBytes;
}

}
Loading