From 177f3428977f8800ad10e1caa229491b6c25c181 Mon Sep 17 00:00:00 2001 From: sabotack Date: Thu, 18 May 2023 13:29:28 +0200 Subject: [PATCH 1/6] fix #100: print crashes if there is no delay --- src/main/interpreter/Visitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/interpreter/Visitor.java b/src/main/interpreter/Visitor.java index 9e14a11..80f4120 100644 --- a/src/main/interpreter/Visitor.java +++ b/src/main/interpreter/Visitor.java @@ -583,6 +583,7 @@ public MSType visitFuncCall(MineScriptParser.FuncCallContext ctx) { } else { TurtleCommands.print(server, expressionId + " is: " + text, messageType); } + timeout(1); }); } default -> { From b6d539198bb2e5fb5dbc4e55fec6af3a3bee68f4 Mon Sep 17 00:00:00 2001 From: sabotack Date: Thu, 18 May 2023 13:40:30 +0200 Subject: [PATCH 2/6] fix #96: 32K character limit when opening screen --- src/main/java/minescript/block/entity/TurtleBlockEntity.java | 4 +++- src/main/java/minescript/screen/TextEditorScreenHandler.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/minescript/block/entity/TurtleBlockEntity.java b/src/main/java/minescript/block/entity/TurtleBlockEntity.java index a6a7694..0072183 100644 --- a/src/main/java/minescript/block/entity/TurtleBlockEntity.java +++ b/src/main/java/minescript/block/entity/TurtleBlockEntity.java @@ -45,7 +45,9 @@ public void readNbt(NbtCompound nbt) { @Override public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) { - buf.writeString(this.input.getString()); + String text = this.input.getString(); + buf.writeInt(text.length()); + buf.writeString(text, text.length()); } @Override diff --git a/src/main/java/minescript/screen/TextEditorScreenHandler.java b/src/main/java/minescript/screen/TextEditorScreenHandler.java index c5a59d0..391efbf 100644 --- a/src/main/java/minescript/screen/TextEditorScreenHandler.java +++ b/src/main/java/minescript/screen/TextEditorScreenHandler.java @@ -23,7 +23,7 @@ public class TextEditorScreenHandler extends ScreenHandler { public TextEditorScreenHandler(int syncId, PlayerInventory inventory, PacketByteBuf buf) { this(syncId, inventory, (TurtleBlockEntity) null); - inputText = buf.readString(); + inputText = buf.readString(buf.readInt()); } public TextEditorScreenHandler(int syncId, PlayerInventory inventory, @Nullable TurtleBlockEntity entity) { From 1f9e8d0456fd13231eb668e1c6b148d263454701 Mon Sep 17 00:00:00 2001 From: sabotack Date: Thu, 18 May 2023 14:21:17 +0200 Subject: [PATCH 3/6] fix #102: Thread never stopped + unsafe stop --- src/main/interpreter/Interpreter.java | 4 +++ src/main/interpreter/Visitor.java | 3 ++- .../ThreadInterruptedException.java | 5 ++++ .../minescript/block/custom/TurtleBlock.java | 7 +++-- .../minescript/network/MineScriptPackets.java | 2 -- .../packet/StopInterpreterC2SPacket.java | 27 ------------------- 6 files changed, 14 insertions(+), 34 deletions(-) create mode 100644 src/main/interpreter/exceptions/ThreadInterruptedException.java delete mode 100644 src/main/java/minescript/network/packet/StopInterpreterC2SPacket.java diff --git a/src/main/interpreter/Interpreter.java b/src/main/interpreter/Interpreter.java index 320b2b2..5caefa6 100644 --- a/src/main/interpreter/Interpreter.java +++ b/src/main/interpreter/Interpreter.java @@ -2,6 +2,7 @@ import interpreter.antlr.MineScriptLexer; import interpreter.antlr.MineScriptParser; +import interpreter.exceptions.ThreadInterruptedException; import interpreter.types.MSMessageType; import minescript.network.TurtleCommands; import net.minecraft.server.MinecraftServer; @@ -28,6 +29,7 @@ public Interpreter(String program, MinecraftServer server, ServerWorld world, Bl @Override public void run() { try { + System.out.println("Thread started"); // Create a CharStream that reads from standard input CharStream input = CharStreams.fromString(program + System.lineSeparator()); // Create a lexer that feeds off of input CharStream @@ -43,6 +45,8 @@ public void run() { ParseTree tree = parser.program(); // Begin parsing at init rule Visitor visitor = new Visitor(server, world, turtlePos, new SymbolTable()); visitor.visit(tree); + } catch (ThreadInterruptedException e) { + System.out.println(e.getMessage()); } catch (Exception e) { TurtleCommands.print(server, e.getMessage(), MSMessageType.ERROR); } diff --git a/src/main/interpreter/Visitor.java b/src/main/interpreter/Visitor.java index 80f4120..e267e61 100644 --- a/src/main/interpreter/Visitor.java +++ b/src/main/interpreter/Visitor.java @@ -3,6 +3,7 @@ import interpreter.antlr.MineScriptBaseVisitor; import interpreter.antlr.MineScriptParser; import interpreter.exceptions.SymbolNotFoundException; +import interpreter.exceptions.ThreadInterruptedException; import interpreter.types.*; import minescript.network.TurtleCommands; import net.minecraft.block.Block; @@ -764,7 +765,7 @@ private void timeout(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { - throw new RuntimeException(e); + throw new ThreadInterruptedException("Thread was interrupted while waiting"); } } } \ No newline at end of file diff --git a/src/main/interpreter/exceptions/ThreadInterruptedException.java b/src/main/interpreter/exceptions/ThreadInterruptedException.java new file mode 100644 index 0000000..f1cea4e --- /dev/null +++ b/src/main/interpreter/exceptions/ThreadInterruptedException.java @@ -0,0 +1,5 @@ +package interpreter.exceptions; + +public class ThreadInterruptedException extends RuntimeException { + public ThreadInterruptedException(String message) { super(message); } +} diff --git a/src/main/java/minescript/block/custom/TurtleBlock.java b/src/main/java/minescript/block/custom/TurtleBlock.java index 2794356..cd77004 100644 --- a/src/main/java/minescript/block/custom/TurtleBlock.java +++ b/src/main/java/minescript/block/custom/TurtleBlock.java @@ -69,10 +69,9 @@ public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockSt BlockEntity blockEntity = world.getBlockEntity(pos); if (blockEntity instanceof TurtleBlockEntity entity) { - PacketByteBuf buf = PacketByteBufs.create(); - buf.writeBlockPos(entity.getPos()); - - ClientPlayNetworking.send(MineScriptPackets.STOP_INTERPRETER_ID, buf); + if (entity.interpreterThread != null && entity.interpreterThread.isAlive()) { + entity.interpreterThread.interrupt(); + } } } super.onStateReplaced(state, world, pos, newState, moved); diff --git a/src/main/java/minescript/network/MineScriptPackets.java b/src/main/java/minescript/network/MineScriptPackets.java index 6e74104..087a6bd 100644 --- a/src/main/java/minescript/network/MineScriptPackets.java +++ b/src/main/java/minescript/network/MineScriptPackets.java @@ -7,10 +7,8 @@ public class MineScriptPackets { public static final Identifier START_INTERPRETER_ID = new Identifier("minescript", "start_interpreter"); - public static final Identifier STOP_INTERPRETER_ID = new Identifier("minescript", "stop_interpreter"); public static void registerC2SPackets() { ServerPlayNetworking.registerGlobalReceiver(START_INTERPRETER_ID, StartInterpreterC2SPacket::receive); - ServerPlayNetworking.registerGlobalReceiver(STOP_INTERPRETER_ID, StopInterpreterC2SPacket::receive); } } diff --git a/src/main/java/minescript/network/packet/StopInterpreterC2SPacket.java b/src/main/java/minescript/network/packet/StopInterpreterC2SPacket.java deleted file mode 100644 index 0f56a77..0000000 --- a/src/main/java/minescript/network/packet/StopInterpreterC2SPacket.java +++ /dev/null @@ -1,27 +0,0 @@ -package minescript.network.packet; - -import interpreter.Interpreter; -import minescript.block.entity.TurtleBlockEntity; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerPlayNetworkHandler; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.math.BlockPos; - -public class StopInterpreterC2SPacket { - public static void receive(MinecraftServer server, ServerPlayerEntity player, ServerPlayNetworkHandler handler, PacketByteBuf buf, PacketSender responseSender) { - BlockPos pos = buf.readBlockPos(); - - server.executeSync(() -> { - ServerWorld world = player.getWorld(); - TurtleBlockEntity entity = (TurtleBlockEntity) world.getBlockEntity(pos); - - assert entity != null; - if (entity.interpreterThread != null && entity.interpreterThread.isAlive()) { - entity.interpreterThread.stop(); - } - }); - } -} From 0acfb2ef7a03d0d9f2f26bf89b9e6578d29158bd Mon Sep 17 00:00:00 2001 From: sabotack Date: Thu, 18 May 2023 15:17:01 +0200 Subject: [PATCH 4/6] fix #101: function scoping inconsistencies --- src/main/interpreter/Interpreter.java | 3 +- src/main/interpreter/SymbolTable.java | 41 +++++++++++++++------------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/main/interpreter/Interpreter.java b/src/main/interpreter/Interpreter.java index 5caefa6..fae6143 100644 --- a/src/main/interpreter/Interpreter.java +++ b/src/main/interpreter/Interpreter.java @@ -28,8 +28,8 @@ public Interpreter(String program, MinecraftServer server, ServerWorld world, Bl @Override public void run() { + System.out.println("Thread started"); try { - System.out.println("Thread started"); // Create a CharStream that reads from standard input CharStream input = CharStreams.fromString(program + System.lineSeparator()); // Create a lexer that feeds off of input CharStream @@ -50,5 +50,6 @@ public void run() { } catch (Exception e) { TurtleCommands.print(server, e.getMessage(), MSMessageType.ERROR); } + System.out.println("Thread finished"); } } \ No newline at end of file diff --git a/src/main/interpreter/SymbolTable.java b/src/main/interpreter/SymbolTable.java index ec506a9..99244e3 100644 --- a/src/main/interpreter/SymbolTable.java +++ b/src/main/interpreter/SymbolTable.java @@ -35,12 +35,13 @@ public void enterSymbol(String name, MSType value) { Symbol newSymbol = new Symbol(name, value); checkRestrictedKeyWords(newSymbol); - /*If the variable is already in the current scope, update it*/ - if (isVarInNewScope(name)) { - Symbol oldSymbol = hashMap.get(getPrefixName(name)); + /*If a prefixed version of the variable already exists, update it*/ + String shadowedSymbolName = getShadowedSymbolName(name); + if (shadowedSymbolName != null) { + Symbol oldSymbol = hashMap.get(shadowedSymbolName); delete(oldSymbol.name); - Symbol prefixSymbol = new Symbol(oldSymbol.name, value); - add(prefixSymbol); + Symbol newShadowedSymbol = new Symbol(oldSymbol.name, value); + add(newShadowedSymbol); return; } @@ -77,8 +78,9 @@ else if (symbol.name.equals(funcName.name())){ * @return symbol from the hash table */ public Symbol retrieveSymbol(String name) { - if (isVarInNewScope(name)) { - return hashMap.get(getPrefixName(name)); + String shadowedSymbolName = getShadowedSymbolName(name); + if (shadowedSymbolName != null) { + return hashMap.get(shadowedSymbolName); } else if (hashMap.containsKey(name)) { return hashMap.get(name); } else { @@ -108,20 +110,23 @@ private void add(Symbol symbol) { hashMap.put(symbol.name, symbol); } - /** - * @param name name of the variable - * @return true if the variable is in the current scope - */ - private boolean isVarInNewScope(String name) { - return scopeStack.peek().stream().anyMatch(s -> s.endsWith("." + name)); - } - /** * @param name id of the variable - * @return prefix of the variable + * @return name of the symbol if a prefixed version of it exists in any of the scopes, otherwise null */ - private String getPrefixName(String name) { - return scopeStack.peek().stream().filter(s -> s.contains("." + name)).findFirst().orElseThrow(); + private String getShadowedSymbolName(String name) { + if (scopeStack.empty()) return null; + + ArrayList currentScope = scopeStack.peek(); + if (currentScope.stream().anyMatch(s -> s.endsWith("." + name))) { + return currentScope.stream().filter(s -> s.endsWith("." + name)).findFirst().orElseThrow(); + } + else { + scopeStack.pop(); + String res = getShadowedSymbolName(name); + scopeStack.push(currentScope); + return res; + } } private record Symbol(String name, MSType value) { From 0fc6c7414a52049d7664ab7b0cada284ba268cc8 Mon Sep 17 00:00:00 2001 From: sabotack Date: Thu, 18 May 2023 16:32:07 +0200 Subject: [PATCH 5/6] fix #97: writeNbt and readNbt crash --- .../block/entity/TurtleBlockEntity.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/minescript/block/entity/TurtleBlockEntity.java b/src/main/java/minescript/block/entity/TurtleBlockEntity.java index 0072183..d6888cc 100644 --- a/src/main/java/minescript/block/entity/TurtleBlockEntity.java +++ b/src/main/java/minescript/block/entity/TurtleBlockEntity.java @@ -33,14 +33,30 @@ public void setTurtleInput(String input) { @Override public void writeNbt(NbtCompound nbt) { - nbt.putString("input", this.input.getString()); + String text = input.getString(); + int length = text.length(); + + int splits = (int) Math.ceil((double) length / 50000); + for (int i = 0; i < splits; i++) { + if (i == splits - 1) + nbt.putString("input" + i, text.substring(i * 50000)); + else + nbt.putString("input" + i, text.substring(i * 50000, (i + 1) * 50000)); + } + super.writeNbt(nbt); } @Override public void readNbt(NbtCompound nbt) { + StringBuilder text = new StringBuilder(); + int split = 0; + while (nbt.contains("input" + split)) { + text.append(nbt.getString("input" + split)); + split++; + } + this.input = Text.of(text.toString()); super.readNbt(nbt); - this.input = Text.of(nbt.getString("input")); } @Override From 93b2219ddff244e439c82375d857d0dfc7699331 Mon Sep 17 00:00:00 2001 From: sabotack Date: Tue, 23 May 2023 08:34:51 +0200 Subject: [PATCH 6/6] fix: 1MB payload limit --- .../block/entity/TurtleBlockEntity.java | 10 +++- .../minescript/screen/TextEditorScreen.java | 29 +++++++--- .../screen/TextEditorScreenHandler.java | 58 ++++++++++++++++--- 3 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/main/java/minescript/block/entity/TurtleBlockEntity.java b/src/main/java/minescript/block/entity/TurtleBlockEntity.java index d6888cc..a08740a 100644 --- a/src/main/java/minescript/block/entity/TurtleBlockEntity.java +++ b/src/main/java/minescript/block/entity/TurtleBlockEntity.java @@ -31,6 +31,10 @@ public void setTurtleInput(String input) { this.markDirty(); } + public String getTurtleInput () { + return this.input.getString(); + } + @Override public void writeNbt(NbtCompound nbt) { String text = input.getString(); @@ -61,9 +65,9 @@ public void readNbt(NbtCompound nbt) { @Override public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) { - String text = this.input.getString(); - buf.writeInt(text.length()); - buf.writeString(text, text.length()); +// String text = this.input.getString(); +// buf.writeInt(text.length()); +// buf.writeString(text, text.length()); } @Override diff --git a/src/main/java/minescript/screen/TextEditorScreen.java b/src/main/java/minescript/screen/TextEditorScreen.java index 96a519d..35aa758 100644 --- a/src/main/java/minescript/screen/TextEditorScreen.java +++ b/src/main/java/minescript/screen/TextEditorScreen.java @@ -1,25 +1,23 @@ package minescript.screen; -import io.netty.buffer.ByteBuf; -import io.wispforest.owo.client.screens.ScreenInternals; import io.wispforest.owo.ui.base.BaseUIModelHandledScreen; import io.wispforest.owo.ui.base.BaseUIModelScreen; import io.wispforest.owo.ui.component.ButtonComponent; import io.wispforest.owo.ui.container.FlowLayout; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.minecraft.client.gui.widget.EditBoxWidget; +import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; public class TextEditorScreen extends BaseUIModelHandledScreen { + private EditBoxWidget editor; + private boolean isTriggerSent = false; + private boolean isTextSet = false; public TextEditorScreen(TextEditorScreenHandler handler, PlayerInventory inventory, Text title) { // super(handler, inventory, title, FlowLayout.class, BaseUIModelScreen.DataSource.file("../src/main/resources/assets/minescript/owo_ui/editor.xml")); // For development @@ -29,11 +27,11 @@ public TextEditorScreen(TextEditorScreenHandler handler, PlayerInventory invento @Override protected void build(FlowLayout rootComponent) { - var editor = rootComponent.childById(EditBoxWidget.class, "edit-box"); + editor = rootComponent.childById(EditBoxWidget.class, "edit-box"); if (editor == null) throw new NullPointerException("editor is null"); editor.active = true; - editor.setText(handler.getInputText()); + editor.mouseDown().subscribe((mouseX, mouseY, button) -> { editor.setFocused(true); @@ -69,6 +67,21 @@ protected void build(FlowLayout rootComponent) { handler.sendMessage(new TextEditorScreenHandler.StartInterpreterMessage(text.length(), buf)); this.close(); }); + + } + + @Override + public void render (MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (!isTextSet && handler.isClientTextUpdated()) { + editor.setText(handler.getInputText()); + isTextSet = true; + } + + if (!isTriggerSent) { + handler.sendMessage(new TextEditorScreenHandler.TriggerSendInputMessage()); + isTriggerSent = true; + } + super.render(matrices, mouseX, mouseY, delta); } @Override diff --git a/src/main/java/minescript/screen/TextEditorScreenHandler.java b/src/main/java/minescript/screen/TextEditorScreenHandler.java index 391efbf..8cff18d 100644 --- a/src/main/java/minescript/screen/TextEditorScreenHandler.java +++ b/src/main/java/minescript/screen/TextEditorScreenHandler.java @@ -1,6 +1,5 @@ package minescript.screen; -import io.netty.buffer.ByteBuf; import minescript.MineScript; import minescript.block.entity.TurtleBlockEntity; import minescript.network.MineScriptPackets; @@ -9,21 +8,18 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.NbtCompound; import net.minecraft.network.PacketByteBuf; import net.minecraft.screen.ScreenHandler; -import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; - public class TextEditorScreenHandler extends ScreenHandler { private String inputText; + private boolean isClientTextUpdated; private final @Nullable TurtleBlockEntity entity; public TextEditorScreenHandler(int syncId, PlayerInventory inventory, PacketByteBuf buf) { this(syncId, inventory, (TurtleBlockEntity) null); - inputText = buf.readString(buf.readInt()); + isClientTextUpdated = false; } public TextEditorScreenHandler(int syncId, PlayerInventory inventory, @Nullable TurtleBlockEntity entity) { @@ -32,11 +28,50 @@ public TextEditorScreenHandler(int syncId, PlayerInventory inventory, @Nullable inputText = "placeholder"; this.entity = entity; + this.addClientboundMessage(SendInputToClientMessage.class, this::handleSendInputToClient); this.addServerboundMessage(StartInterpreterMessage.class, this::handleSetInputText); + this.addServerboundMessage(TriggerSendInputMessage.class, this::handleTriggerSendInput); } - public String getInputText() { - return inputText; + private void handleTriggerSendInput(TriggerSendInputMessage message) { + assert entity != null; + String text = entity.getTurtleInput(); + int length = text.length(); + + int splits = (int) Math.ceil((double) length / 50000); + PacketByteBuf buf = PacketByteBufs.create(); + + int splitLength; + + for (int i = 0; i < splits; i++) { + if (i == splits - 1) { + splitLength = text.substring(i * 50000).length(); + buf.writeString(text.substring(i * 50000), splitLength); + this.sendMessage(new SendInputToClientMessage(i, splits, splitLength, buf)); + } + else { + String substring = text.substring(i * 50000, (i + 1) * 50000); + buf.writeString(substring, substring.length()); + splitLength = substring.length(); + this.sendMessage(new SendInputToClientMessage(i, splits, splitLength, buf)); + } + } + } + + private void handleSendInputToClient(SendInputToClientMessage message) { + int splits = message.totalSplits; + int currentSplit = message.current; + int splitLength = message.splitLength; + PacketByteBuf buf = message.buf; + + if (currentSplit == 0) + this.inputText = buf.readString(splitLength); + else + this.inputText += buf.readString(splitLength); + + // if last split then set clientTextUpdated to true + if (currentSplit == splits - 1) + isClientTextUpdated = true; } private void handleSetInputText(StartInterpreterMessage message) { @@ -54,6 +89,13 @@ private void handleSetInputText(StartInterpreterMessage message) { ClientPlayNetworking.send(MineScriptPackets.START_INTERPRETER_ID, buf); } + public String getInputText() { + return inputText; + } + public boolean isClientTextUpdated() { return isClientTextUpdated; } + + public record TriggerSendInputMessage() {} + public record SendInputToClientMessage(int current, int totalSplits, int splitLength, PacketByteBuf buf) {} public record StartInterpreterMessage(int length, PacketByteBuf buf) {} @Override