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 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 -> {