Skip to content

Commit e11111a

Browse files
authored
Merge pull request #17 from EngineHub/ot/fix/optimize-state-machine-allocations
Replace simple state records with enums
2 parents 2f3227d + c0e037c commit e11111a

File tree

3 files changed

+61
-46
lines changed

3 files changed

+61
-46
lines changed

format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/LinSnbtWriter.java

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,17 @@ private sealed interface WriteState {
4141
record List(int remainingValues) implements WriteState {
4242
}
4343

44-
record Compound(boolean hasPrevious) implements WriteState {
44+
enum Compound implements WriteState {
45+
DEFAULT,
46+
/**
47+
* Indicates that an entry was just finished, so when a new name is encountered, a comma should be emitted
48+
* first.
49+
*/
50+
HAS_PREVIOUS_ENTRY,
4551
}
4652

47-
record WritingArray() implements WriteState {
53+
enum WritingArray implements WriteState {
54+
INSTANCE
4855
}
4956
}
5057

@@ -72,13 +79,14 @@ public void write(Appendable output, LinStream tokens) throws IOException {
7279
}
7380
switch (token) {
7481
case LinToken.Name(String name, Optional<LinTagId> id) -> {
75-
if (!(state instanceof WriteState.Compound compound)) {
82+
if (!(state instanceof WriteState.Compound)) {
7683
throw new NbtWriteException("Names can only appear inside compounds");
7784
}
78-
if (compound.hasPrevious) {
85+
if (state == WriteState.Compound.HAS_PREVIOUS_ENTRY) {
7986
output.append(',');
8087
// Kill the previous flag
81-
replaceLast(new WriteState.Compound(false));
88+
stateStack.removeLast();
89+
stateStack.addLast(WriteState.Compound.DEFAULT);
8290
}
8391
output.append(Elusion.escapeIfNeeded(name)).append(':');
8492
}
@@ -87,7 +95,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
8795
if (state instanceof WriteState.WritingArray) {
8896
output.append(',');
8997
} else {
90-
stateStack.addLast(new WriteState.WritingArray());
98+
stateStack.addLast(WriteState.WritingArray.INSTANCE);
9199
}
92100
while (buffer.hasRemaining()) {
93101
output.append(String.valueOf(buffer.get())).append('B');
@@ -112,7 +120,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
112120
case LinToken.CompoundStart compoundStart -> {
113121
output.append('{');
114122

115-
stateStack.addLast(new WriteState.Compound(false));
123+
stateStack.addLast(WriteState.Compound.DEFAULT);
116124
}
117125
case LinToken.CompoundEnd compoundEnd -> {
118126
output.append('}');
@@ -135,7 +143,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
135143
if (state instanceof WriteState.WritingArray) {
136144
output.append(',');
137145
} else {
138-
stateStack.addLast(new WriteState.WritingArray());
146+
stateStack.addLast(WriteState.WritingArray.INSTANCE);
139147
}
140148
while (buffer.hasRemaining()) {
141149
output.append(String.valueOf(buffer.get()));
@@ -173,7 +181,7 @@ public void write(Appendable output, LinStream tokens) throws IOException {
173181
if (state instanceof WriteState.WritingArray) {
174182
output.append(',');
175183
} else {
176-
stateStack.addLast(new WriteState.WritingArray());
184+
stateStack.addLast(WriteState.WritingArray.INSTANCE);
177185
}
178186
while (buffer.hasRemaining()) {
179187
output.append(String.valueOf(buffer.get())).append('L');
@@ -221,13 +229,9 @@ private void handleValueEnd(Appendable output) throws IOException {
221229
output.append(',');
222230
}
223231
}
224-
case WriteState.Compound compound -> stateStack.addLast(new WriteState.Compound(true));
232+
case WriteState.Compound compound -> stateStack.addLast(WriteState.Compound.HAS_PREVIOUS_ENTRY);
225233
default -> throw new NbtWriteException("Unexpected state: " + state);
226234
}
227235
}
228236

229-
private void replaceLast(WriteState state) {
230-
stateStack.removeLast();
231-
stateStack.addLast(state);
232-
}
233237
}

format-snbt/src/main/java/org/enginehub/linbus/format/snbt/impl/reader/LinSnbtReader.java

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ private sealed interface State {
5050
* The cursor should either point to a comma or a closing brace.
5151
* </p>
5252
*/
53-
record InCompound() implements State {
53+
enum InCompound implements State {
54+
INSTANCE
5455
}
5556

5657
/**
@@ -60,7 +61,8 @@ record InCompound() implements State {
6061
* After this, the cursor will be one character after the colon.
6162
* </p>
6263
*/
63-
record CompoundEntryName() implements State {
64+
enum CompoundEntryName implements State {
65+
INSTANCE
6466
}
6567

6668
/**
@@ -70,7 +72,8 @@ record CompoundEntryName() implements State {
7072
* The cursor should either point to a comma or a closing bracket.
7173
* </p>
7274
*/
73-
record InList() implements State {
75+
enum InList implements State {
76+
INSTANCE
7477
}
7578

7679
/**
@@ -80,7 +83,8 @@ record InList() implements State {
8083
* The cursor should always point to the start of a value.
8184
* </p>
8285
*/
83-
record InByteArray() implements State {
86+
enum InByteArray implements State {
87+
INSTANCE
8488
}
8589

8690
/**
@@ -90,7 +94,8 @@ record InByteArray() implements State {
9094
* The cursor should always point to the start of a value.
9195
* </p>
9296
*/
93-
record InIntArray() implements State {
97+
enum InIntArray implements State {
98+
INSTANCE
9499
}
95100

96101
/**
@@ -100,14 +105,17 @@ record InIntArray() implements State {
100105
* The cursor should always point to the start of a value.
101106
* </p>
102107
*/
103-
record InLongArray() implements State {
108+
enum InLongArray implements State {
109+
INSTANCE
104110
}
105111

106112
/**
107113
* We need to read a value. Usually, we'll just return the value, and not push a new state, unless we need to
108114
* read a complex value such as a compound or list.
109115
*/
110-
record ReadValue(boolean mustBeCompound) implements State {
116+
enum ReadValue implements State {
117+
ANY,
118+
COMPOUND_ONLY,
111119
}
112120
}
113121

@@ -135,7 +143,7 @@ record ReadValue(boolean mustBeCompound) implements State {
135143
*/
136144
public LinSnbtReader(Iterator<? extends SnbtTokenWithMetadata> input) {
137145
this.input = input;
138-
this.stateStack = new ArrayDeque<>(List.of(new State.ReadValue(true)));
146+
this.stateStack = new ArrayDeque<>(List.of(State.ReadValue.COMPOUND_ONLY));
139147
this.tokenQueue = new ArrayDeque<>();
140148
this.readAgainStack = new ArrayDeque<>();
141149
}
@@ -182,7 +190,7 @@ private NbtParseException unexpectedTokenSpecificError(SnbtToken token, String e
182190

183191
private void fillTokenStack(State state) {
184192
switch (state) {
185-
case State.ReadValue(boolean mustBeCompound) -> readValue(mustBeCompound);
193+
case State.ReadValue readValue -> readValue(readValue);
186194
case State.InCompound inCompound -> advanceCompound();
187195
case State.CompoundEntryName compoundEntryName -> readName();
188196
case State.InList inList -> advanceList();
@@ -211,18 +219,18 @@ private void fillTokenStack(State state) {
211219
}
212220
}
213221

214-
private void readValue(boolean mustBeCompound) {
222+
private void readValue(State.ReadValue readValue) {
215223
// Remove the ReadValue
216224
stateStack.removeLast();
217225
var token = read().token();
218226
if (token instanceof SnbtToken.CompoundStart) {
219-
stateStack.addLast(new State.InCompound());
220-
stateStack.addLast(new State.CompoundEntryName());
227+
stateStack.addLast(State.InCompound.INSTANCE);
228+
stateStack.addLast(State.CompoundEntryName.INSTANCE);
221229
tokenQueue.addLast(new LinToken.CompoundStart());
222230
return;
223231
}
224232

225-
if (mustBeCompound) {
233+
if (readValue == State.ReadValue.COMPOUND_ONLY) {
226234
throw unexpectedTokenSpecificError(token, SnbtToken.CompoundStart.INSTANCE.toString());
227235
}
228236

@@ -243,7 +251,7 @@ private void advanceCompound() {
243251
stateStack.removeLast();
244252
tokenQueue.addLast(new LinToken.CompoundEnd());
245253
}
246-
case SnbtToken.Separator separator -> stateStack.addLast(new State.CompoundEntryName());
254+
case SnbtToken.Separator separator -> stateStack.addLast(State.CompoundEntryName.INSTANCE);
247255
default -> throw unexpectedTokenError(token);
248256
}
249257
}
@@ -259,7 +267,7 @@ private void readName() {
259267
if (!(token instanceof SnbtToken.EntrySeparator)) {
260268
throw unexpectedTokenSpecificError(token, SnbtToken.EntrySeparator.INSTANCE.toString());
261269
}
262-
stateStack.addLast(new State.ReadValue(false));
270+
stateStack.addLast(State.ReadValue.ANY);
263271
tokenQueue.addLast(new LinToken.Name(text.content()));
264272
}
265273

@@ -270,7 +278,7 @@ private void advanceList() {
270278
stateStack.removeLast();
271279
tokenQueue.addLast(new LinToken.ListEnd());
272280
}
273-
case SnbtToken.Separator separator -> stateStack.addLast(new State.ReadValue(false));
281+
case SnbtToken.Separator separator -> stateStack.addLast(State.ReadValue.ANY);
274282
default -> throw unexpectedTokenError(token);
275283
}
276284
}
@@ -324,23 +332,23 @@ private <T extends Buffer, L extends LinToken> void advanceArray(
324332
private void prepareListLike() {
325333
int initialCharIndex = charIndex;
326334
var typing = read();
327-
if (typing.token() instanceof SnbtToken.Text text && !text.quoted() && text.content().length() == 1) {
335+
if (typing.token() instanceof SnbtToken.Text(boolean quoted, String content) && !quoted && content.length() == 1) {
328336
var separatorCheck = read();
329337
if (separatorCheck.token() instanceof SnbtToken.ListTypeSeparator) {
330-
switch (text.content().charAt(0)) {
338+
switch (content.charAt(0)) {
331339
case 'B' -> {
332-
stateStack.addLast(new State.InByteArray());
340+
stateStack.addLast(State.InByteArray.INSTANCE);
333341
tokenQueue.addLast(new LinToken.ByteArrayStart());
334342
}
335343
case 'I' -> {
336-
stateStack.addLast(new State.InIntArray());
344+
stateStack.addLast(State.InIntArray.INSTANCE);
337345
tokenQueue.addLast(new LinToken.IntArrayStart());
338346
}
339347
case 'L' -> {
340-
stateStack.addLast(new State.InLongArray());
348+
stateStack.addLast(State.InLongArray.INSTANCE);
341349
tokenQueue.addLast(new LinToken.LongArrayStart());
342350
}
343-
default -> throw new NbtParseException(errorPrefix() + "Invalid array type: " + text.content());
351+
default -> throw new NbtParseException(errorPrefix() + "Invalid array type: " + content);
344352
}
345353
return;
346354
}
@@ -349,8 +357,8 @@ private void prepareListLike() {
349357
readAgainStack.addFirst(typing);
350358
charIndex = initialCharIndex;
351359

352-
stateStack.addLast(new State.InList());
353-
stateStack.addLast(new State.ReadValue(false));
360+
stateStack.addLast(State.InList.INSTANCE);
361+
stateStack.addLast(State.ReadValue.ANY);
354362
tokenQueue.addLast(new LinToken.ListStart());
355363
}
356364

stream/src/main/java/org/enginehub/linbus/stream/impl/LinNbtReader.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,22 @@ private sealed interface State {
114114
/**
115115
* We need to initialize and return the root name.
116116
*/
117-
record Initial() implements State {
117+
enum Initial implements State {
118+
INSTANCE
118119
}
119120

120121
/**
121122
* We need to return {@link LinToken.CompoundStart}.
122123
*/
123-
record CompoundStart() implements State {
124+
enum CompoundStart implements State {
125+
INSTANCE
124126
}
125127

126128
/**
127129
* We need to give the name of the next entry. We'll load the ID here too.
128130
*/
129-
record CompoundEntryName() implements State {
131+
enum CompoundEntryName implements State {
132+
INSTANCE
130133
}
131134

132135
/**
@@ -247,7 +250,7 @@ public String decode() throws CharacterCodingException {
247250
*/
248251
public LinNbtReader(DataInput input, LinReadOptions options) {
249252
this.input = input;
250-
this.stateStack = new ArrayDeque<>(List.of(new State.Initial()));
253+
this.stateStack = new ArrayDeque<>(List.of(State.Initial.INSTANCE));
251254
// We only need to check strings if we're allowing normal UTF-8 encoding.
252255
this.stringEncoding = options.allowNormalUtf8Encoding()
253256
? StringEncoding.UNKNOWN : StringEncoding.MODIFIED_UTF_8;
@@ -262,11 +265,11 @@ public LinNbtReader(DataInput input, LinReadOptions options) {
262265
if (input.readUnsignedByte() != LinTagId.COMPOUND.id()) {
263266
throw new NbtParseException("NBT stream does not start with a compound tag");
264267
}
265-
stateStack.addLast(new State.CompoundStart());
268+
stateStack.addLast(State.CompoundStart.INSTANCE);
266269
yield new LinToken.Name(readUtf(), LinTagId.COMPOUND);
267270
}
268271
case State.CompoundStart compoundStart -> {
269-
stateStack.addLast(new State.CompoundEntryName());
272+
stateStack.addLast(State.CompoundEntryName.INSTANCE);
270273
yield new LinToken.CompoundStart();
271274
}
272275
case State.CompoundEntryName compoundEntryName -> {
@@ -276,7 +279,7 @@ public LinNbtReader(DataInput input, LinReadOptions options) {
276279
}
277280

278281
// After we read the value, we'll be back at reading the name.
279-
stateStack.addLast(new State.CompoundEntryName());
282+
stateStack.addLast(State.CompoundEntryName.INSTANCE);
280283
stateStack.addLast(new State.ReadValue(id));
281284
yield new LinToken.Name(readUtf(), id);
282285
}
@@ -345,7 +348,7 @@ private LinToken handleReadValue(LinTagId id) throws IOException {
345348
yield new LinToken.ListStart(size, elementId);
346349
}
347350
case COMPOUND -> {
348-
stateStack.addLast(new State.CompoundEntryName());
351+
stateStack.addLast(State.CompoundEntryName.INSTANCE);
349352
yield new LinToken.CompoundStart();
350353
}
351354
case INT_ARRAY -> {

0 commit comments

Comments
 (0)