From c0e037ce8637ceafb954062012a90047bbd63a4c Mon Sep 17 00:00:00 2001
From: Octavia Togami
Date: Sun, 22 Feb 2026 18:52:14 -0800
Subject: [PATCH] Replace simple state records with enums
This reduces allocations, since we can't make them values right now.
Valhalla wen?
---
.../format/snbt/impl/LinSnbtWriter.java | 32 ++++++-----
.../snbt/impl/reader/LinSnbtReader.java | 56 +++++++++++--------
.../linbus/stream/impl/LinNbtReader.java | 19 ++++---
3 files changed, 61 insertions(+), 46 deletions(-)
diff --git a/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/LinSnbtWriter.java b/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/LinSnbtWriter.java
index 335d453..ad9d76e 100644
--- a/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/LinSnbtWriter.java
+++ b/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/LinSnbtWriter.java
@@ -41,10 +41,17 @@ private sealed interface WriteState {
record List(int remainingValues) implements WriteState {
}
- record Compound(boolean hasPrevious) implements WriteState {
+ enum Compound implements WriteState {
+ DEFAULT,
+ /**
+ * Indicates that an entry was just finished, so when a new name is encountered, a comma should be emitted
+ * first.
+ */
+ HAS_PREVIOUS_ENTRY,
}
- record WritingArray() implements WriteState {
+ enum WritingArray implements WriteState {
+ INSTANCE
}
}
@@ -72,13 +79,14 @@ public void write(Appendable output, LinStream tokens) throws IOException {
}
switch (token) {
case LinToken.Name(String name, Optional id) -> {
- if (!(state instanceof WriteState.Compound compound)) {
+ if (!(state instanceof WriteState.Compound)) {
throw new NbtWriteException("Names can only appear inside compounds");
}
- if (compound.hasPrevious) {
+ if (state == WriteState.Compound.HAS_PREVIOUS_ENTRY) {
output.append(',');
// Kill the previous flag
- replaceLast(new WriteState.Compound(false));
+ stateStack.removeLast();
+ stateStack.addLast(WriteState.Compound.DEFAULT);
}
output.append(Elusion.escapeIfNeeded(name)).append(':');
}
@@ -87,7 +95,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
if (state instanceof WriteState.WritingArray) {
output.append(',');
} else {
- stateStack.addLast(new WriteState.WritingArray());
+ stateStack.addLast(WriteState.WritingArray.INSTANCE);
}
while (buffer.hasRemaining()) {
output.append(String.valueOf(buffer.get())).append('B');
@@ -112,7 +120,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
case LinToken.CompoundStart compoundStart -> {
output.append('{');
- stateStack.addLast(new WriteState.Compound(false));
+ stateStack.addLast(WriteState.Compound.DEFAULT);
}
case LinToken.CompoundEnd compoundEnd -> {
output.append('}');
@@ -135,7 +143,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
if (state instanceof WriteState.WritingArray) {
output.append(',');
} else {
- stateStack.addLast(new WriteState.WritingArray());
+ stateStack.addLast(WriteState.WritingArray.INSTANCE);
}
while (buffer.hasRemaining()) {
output.append(String.valueOf(buffer.get()));
@@ -173,7 +181,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
if (state instanceof WriteState.WritingArray) {
output.append(',');
} else {
- stateStack.addLast(new WriteState.WritingArray());
+ stateStack.addLast(WriteState.WritingArray.INSTANCE);
}
while (buffer.hasRemaining()) {
output.append(String.valueOf(buffer.get())).append('L');
@@ -221,13 +229,9 @@ private void handleValueEnd(Appendable output) throws IOException {
output.append(',');
}
}
- case WriteState.Compound compound -> stateStack.addLast(new WriteState.Compound(true));
+ case WriteState.Compound compound -> stateStack.addLast(WriteState.Compound.HAS_PREVIOUS_ENTRY);
default -> throw new NbtWriteException("Unexpected state: " + state);
}
}
- private void replaceLast(WriteState state) {
- stateStack.removeLast();
- stateStack.addLast(state);
- }
}
diff --git a/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/reader/LinSnbtReader.java b/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/reader/LinSnbtReader.java
index da9dd93..8c07394 100644
--- a/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/reader/LinSnbtReader.java
+++ b/format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/reader/LinSnbtReader.java
@@ -50,7 +50,8 @@ private sealed interface State {
* The cursor should either point to a comma or a closing brace.
*
*/
- record InCompound() implements State {
+ enum InCompound implements State {
+ INSTANCE
}
/**
@@ -60,7 +61,8 @@ record InCompound() implements State {
* After this, the cursor will be one character after the colon.
*
*/
- record CompoundEntryName() implements State {
+ enum CompoundEntryName implements State {
+ INSTANCE
}
/**
@@ -70,7 +72,8 @@ record CompoundEntryName() implements State {
* The cursor should either point to a comma or a closing bracket.
*
*/
- record InList() implements State {
+ enum InList implements State {
+ INSTANCE
}
/**
@@ -80,7 +83,8 @@ record InList() implements State {
* The cursor should always point to the start of a value.
*
*/
- record InByteArray() implements State {
+ enum InByteArray implements State {
+ INSTANCE
}
/**
@@ -90,7 +94,8 @@ record InByteArray() implements State {
* The cursor should always point to the start of a value.
*
*/
- record InIntArray() implements State {
+ enum InIntArray implements State {
+ INSTANCE
}
/**
@@ -100,14 +105,17 @@ record InIntArray() implements State {
* The cursor should always point to the start of a value.
*
*/
- record InLongArray() implements State {
+ enum InLongArray implements State {
+ INSTANCE
}
/**
* We need to read a value. Usually, we'll just return the value, and not push a new state, unless we need to
* read a complex value such as a compound or list.
*/
- record ReadValue(boolean mustBeCompound) implements State {
+ enum ReadValue implements State {
+ ANY,
+ COMPOUND_ONLY,
}
}
@@ -135,7 +143,7 @@ record ReadValue(boolean mustBeCompound) implements State {
*/
public LinSnbtReader(Iterator extends SnbtTokenWithMetadata> input) {
this.input = input;
- this.stateStack = new ArrayDeque<>(List.of(new State.ReadValue(true)));
+ this.stateStack = new ArrayDeque<>(List.of(State.ReadValue.COMPOUND_ONLY));
this.tokenQueue = new ArrayDeque<>();
this.readAgainStack = new ArrayDeque<>();
}
@@ -182,7 +190,7 @@ private NbtParseException unexpectedTokenSpecificError(SnbtToken token, String e
private void fillTokenStack(State state) {
switch (state) {
- case State.ReadValue(boolean mustBeCompound) -> readValue(mustBeCompound);
+ case State.ReadValue readValue -> readValue(readValue);
case State.InCompound inCompound -> advanceCompound();
case State.CompoundEntryName compoundEntryName -> readName();
case State.InList inList -> advanceList();
@@ -211,18 +219,18 @@ private void fillTokenStack(State state) {
}
}
- private void readValue(boolean mustBeCompound) {
+ private void readValue(State.ReadValue readValue) {
// Remove the ReadValue
stateStack.removeLast();
var token = read().token();
if (token instanceof SnbtToken.CompoundStart) {
- stateStack.addLast(new State.InCompound());
- stateStack.addLast(new State.CompoundEntryName());
+ stateStack.addLast(State.InCompound.INSTANCE);
+ stateStack.addLast(State.CompoundEntryName.INSTANCE);
tokenQueue.addLast(new LinToken.CompoundStart());
return;
}
- if (mustBeCompound) {
+ if (readValue == State.ReadValue.COMPOUND_ONLY) {
throw unexpectedTokenSpecificError(token, SnbtToken.CompoundStart.INSTANCE.toString());
}
@@ -243,7 +251,7 @@ private void advanceCompound() {
stateStack.removeLast();
tokenQueue.addLast(new LinToken.CompoundEnd());
}
- case SnbtToken.Separator separator -> stateStack.addLast(new State.CompoundEntryName());
+ case SnbtToken.Separator separator -> stateStack.addLast(State.CompoundEntryName.INSTANCE);
default -> throw unexpectedTokenError(token);
}
}
@@ -259,7 +267,7 @@ private void readName() {
if (!(token instanceof SnbtToken.EntrySeparator)) {
throw unexpectedTokenSpecificError(token, SnbtToken.EntrySeparator.INSTANCE.toString());
}
- stateStack.addLast(new State.ReadValue(false));
+ stateStack.addLast(State.ReadValue.ANY);
tokenQueue.addLast(new LinToken.Name(text.content()));
}
@@ -270,7 +278,7 @@ private void advanceList() {
stateStack.removeLast();
tokenQueue.addLast(new LinToken.ListEnd());
}
- case SnbtToken.Separator separator -> stateStack.addLast(new State.ReadValue(false));
+ case SnbtToken.Separator separator -> stateStack.addLast(State.ReadValue.ANY);
default -> throw unexpectedTokenError(token);
}
}
@@ -324,23 +332,23 @@ private void advanceArray(
private void prepareListLike() {
int initialCharIndex = charIndex;
var typing = read();
- if (typing.token() instanceof SnbtToken.Text text && !text.quoted() && text.content().length() == 1) {
+ if (typing.token() instanceof SnbtToken.Text(boolean quoted, String content) && !quoted && content.length() == 1) {
var separatorCheck = read();
if (separatorCheck.token() instanceof SnbtToken.ListTypeSeparator) {
- switch (text.content().charAt(0)) {
+ switch (content.charAt(0)) {
case 'B' -> {
- stateStack.addLast(new State.InByteArray());
+ stateStack.addLast(State.InByteArray.INSTANCE);
tokenQueue.addLast(new LinToken.ByteArrayStart());
}
case 'I' -> {
- stateStack.addLast(new State.InIntArray());
+ stateStack.addLast(State.InIntArray.INSTANCE);
tokenQueue.addLast(new LinToken.IntArrayStart());
}
case 'L' -> {
- stateStack.addLast(new State.InLongArray());
+ stateStack.addLast(State.InLongArray.INSTANCE);
tokenQueue.addLast(new LinToken.LongArrayStart());
}
- default -> throw new NbtParseException(errorPrefix() + "Invalid array type: " + text.content());
+ default -> throw new NbtParseException(errorPrefix() + "Invalid array type: " + content);
}
return;
}
@@ -349,8 +357,8 @@ private void prepareListLike() {
readAgainStack.addFirst(typing);
charIndex = initialCharIndex;
- stateStack.addLast(new State.InList());
- stateStack.addLast(new State.ReadValue(false));
+ stateStack.addLast(State.InList.INSTANCE);
+ stateStack.addLast(State.ReadValue.ANY);
tokenQueue.addLast(new LinToken.ListStart());
}
diff --git a/stream/src/main/java/org/enginehub/linbus/stream/impl/LinNbtReader.java b/stream/src/main/java/org/enginehub/linbus/stream/impl/LinNbtReader.java
index 451bf6f..8436673 100644
--- a/stream/src/main/java/org/enginehub/linbus/stream/impl/LinNbtReader.java
+++ b/stream/src/main/java/org/enginehub/linbus/stream/impl/LinNbtReader.java
@@ -114,19 +114,22 @@ private sealed interface State {
/**
* We need to initialize and return the root name.
*/
- record Initial() implements State {
+ enum Initial implements State {
+ INSTANCE
}
/**
* We need to return {@link LinToken.CompoundStart}.
*/
- record CompoundStart() implements State {
+ enum CompoundStart implements State {
+ INSTANCE
}
/**
* We need to give the name of the next entry. We'll load the ID here too.
*/
- record CompoundEntryName() implements State {
+ enum CompoundEntryName implements State {
+ INSTANCE
}
/**
@@ -247,7 +250,7 @@ public String decode() throws CharacterCodingException {
*/
public LinNbtReader(DataInput input, LinReadOptions options) {
this.input = input;
- this.stateStack = new ArrayDeque<>(List.of(new State.Initial()));
+ this.stateStack = new ArrayDeque<>(List.of(State.Initial.INSTANCE));
// We only need to check strings if we're allowing normal UTF-8 encoding.
this.stringEncoding = options.allowNormalUtf8Encoding()
? StringEncoding.UNKNOWN : StringEncoding.MODIFIED_UTF_8;
@@ -262,11 +265,11 @@ public LinNbtReader(DataInput input, LinReadOptions options) {
if (input.readUnsignedByte() != LinTagId.COMPOUND.id()) {
throw new NbtParseException("NBT stream does not start with a compound tag");
}
- stateStack.addLast(new State.CompoundStart());
+ stateStack.addLast(State.CompoundStart.INSTANCE);
yield new LinToken.Name(readUtf(), LinTagId.COMPOUND);
}
case State.CompoundStart compoundStart -> {
- stateStack.addLast(new State.CompoundEntryName());
+ stateStack.addLast(State.CompoundEntryName.INSTANCE);
yield new LinToken.CompoundStart();
}
case State.CompoundEntryName compoundEntryName -> {
@@ -276,7 +279,7 @@ public LinNbtReader(DataInput input, LinReadOptions options) {
}
// After we read the value, we'll be back at reading the name.
- stateStack.addLast(new State.CompoundEntryName());
+ stateStack.addLast(State.CompoundEntryName.INSTANCE);
stateStack.addLast(new State.ReadValue(id));
yield new LinToken.Name(readUtf(), id);
}
@@ -345,7 +348,7 @@ private LinToken handleReadValue(LinTagId id) throws IOException {
yield new LinToken.ListStart(size, elementId);
}
case COMPOUND -> {
- stateStack.addLast(new State.CompoundEntryName());
+ stateStack.addLast(State.CompoundEntryName.INSTANCE);
yield new LinToken.CompoundStart();
}
case INT_ARRAY -> {