Skip to content

Commit b7b0d19

Browse files
committed
Varints
1 parent d7964a3 commit b7b0d19

3 files changed

Lines changed: 117 additions & 15 deletions

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
group=team.unnamed
2-
version=1.1.0
2+
version=1.1.1
33
description=A lightweight, fast and efficient Molang lexer, parser, interpreter and compiler for Java 8+

src/main/java/team/unnamed/mocha/util/ExprBytesUtils.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package team.unnamed.mocha.util;
2525

2626
import io.netty.buffer.ByteBuf;
27+
import io.netty.buffer.ByteBufUtil;
2728
import org.jetbrains.annotations.NotNull;
2829
import team.unnamed.mocha.parser.ast.*;
2930

@@ -68,7 +69,7 @@ public static void writeExpression(Expression expression, ByteBuf buf) {
6869
}
6970

7071
public static <T> List<T> readList(ByteBuf buf, Function<ByteBuf, T> reader) {
71-
int count = buf.readInt();
72+
int count = VarIntUtils.readVarInt(buf);
7273
List<T> list = new ArrayList<>(count);
7374
for (int i = 0; i < count; i++) {
7475
list.add(reader.apply(buf));
@@ -77,7 +78,7 @@ public static <T> List<T> readList(ByteBuf buf, Function<ByteBuf, T> reader) {
7778
}
7879

7980
public static <T> void writeList(ByteBuf buf, List<T> list, BiConsumer<T, ByteBuf> writer) {
80-
buf.writeInt(list.size());
81+
VarIntUtils.writeVarInt(buf, list.size());
8182
for (T entry : list) {
8283
writer.accept(entry, buf);
8384
}
@@ -92,20 +93,16 @@ public static <T> T getEnum(T[] values, ByteBuf buf) {
9293
}
9394

9495
public static String readString(ByteBuf buf) {
95-
int len = buf.readInt();
96-
if (len <= 0) return null;
97-
byte[] b = new byte[len];
98-
buf.readBytes(b); //that is safe to use.
99-
return new String(b, StandardCharsets.UTF_8);
96+
int length = VarIntUtils.readVarInt(buf);
97+
if (length <= 0) return null;
98+
String str = buf.toString(buf.readerIndex(), length, StandardCharsets.UTF_8);
99+
buf.skipBytes(length);
100+
return str;
100101
}
101102

102103
public static void writeString(ByteBuf buf, String str) {
103-
if (str == null || str.isBlank()) { // Minor optimization to avoid writing empty lines
104-
buf.writeInt(0);
105-
return;
106-
}
107-
byte[] b = str.getBytes(StandardCharsets.UTF_8);
108-
buf.writeInt(b.length);
109-
buf.writeBytes(b);
104+
int size = ByteBufUtil.utf8Bytes(str);
105+
VarIntUtils.writeVarInt(buf, size);
106+
buf.writeCharSequence(str, StandardCharsets.UTF_8);
110107
}
111108
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* This file is part of mocha, licensed under the MIT license
3+
*
4+
* Copyright (c) 2021-2025 Unnamed Team
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package team.unnamed.mocha.util;
25+
26+
import io.netty.buffer.ByteBuf;
27+
28+
public class VarIntUtils {
29+
private static final int MAXIMUM_VARINT_SIZE = 5;
30+
31+
/**
32+
* Reads a Minecraft-style VarInt from the specified {@code buf}.
33+
*
34+
* @param buf the buffer to read from
35+
* @return the decoded VarInt
36+
*/
37+
public static int readVarInt(ByteBuf buf) {
38+
int readable = buf.readableBytes();
39+
if (readable == 0) {
40+
// special case for empty buffer
41+
throw new IllegalStateException("Bad VarInt decoded");
42+
}
43+
44+
// we can read at least one byte, and this should be a common case
45+
int k = buf.readByte();
46+
if ((k & 0x80) != 128) {
47+
return k;
48+
}
49+
50+
// in case decoding one byte was not enough, use a loop to decode up to the next 4 bytes
51+
int maxRead = Math.min(MAXIMUM_VARINT_SIZE, readable);
52+
int i = k & 0x7F;
53+
for (int j = 1; j < maxRead; j++) {
54+
k = buf.readByte();
55+
i |= (k & 0x7F) << j * 7;
56+
if ((k & 0x80) != 128) {
57+
return i;
58+
}
59+
}
60+
throw new IllegalStateException("Bad VarInt decoded");
61+
}
62+
63+
/**
64+
* Writes a Minecraft-style VarInt to the specified {@code buf}.
65+
*
66+
* @param buf the buffer to read from
67+
* @param value the integer to write
68+
*/
69+
public static void writeVarInt(ByteBuf buf, int value) {
70+
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
71+
// that the proxy will write, to improve inlining.
72+
if ((value & (0xFFFFFFFF << 7)) == 0) {
73+
buf.writeByte(value);
74+
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
75+
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
76+
buf.writeShort(w);
77+
} else {
78+
writeVarIntFull(buf, value);
79+
}
80+
}
81+
82+
private static void writeVarIntFull(ByteBuf buf, int value) {
83+
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
84+
85+
// This essentially is an unrolled version of the "traditional" VarInt encoding.
86+
if ((value & (0xFFFFFFFF << 7)) == 0) {
87+
buf.writeByte(value);
88+
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
89+
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
90+
buf.writeShort(w);
91+
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
92+
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
93+
buf.writeMedium(w);
94+
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
95+
int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
96+
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
97+
buf.writeInt(w);
98+
} else {
99+
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
100+
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
101+
buf.writeInt(w);
102+
buf.writeByte(value >>> 28);
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)