From 44ef4c07af8095faad8e71be09290e1906653604 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 7 Jul 2024 23:10:34 -0400 Subject: [PATCH 01/49] fix reset button not purging labels --- .../teamdman/sfm/common/blockentity/ManagerBlockEntity.java | 2 ++ .../ca/teamdman/sfm/common/program/LabelPositionHolder.java | 5 +++++ .../resources/assets/sfm/template_programs/changelog.sfml | 1 + 3 files changed, 8 insertions(+) diff --git a/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java b/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java index 4baddc8df..53fe6a7d4 100644 --- a/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java +++ b/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java @@ -8,6 +8,7 @@ import ca.teamdman.sfm.common.net.ClientboundManagerGuiUpdatePacket; import ca.teamdman.sfm.common.net.ClientboundManagerLogLevelUpdatedPacket; import ca.teamdman.sfm.common.net.ClientboundManagerLogsPacket; +import ca.teamdman.sfm.common.program.LabelPositionHolder; import ca.teamdman.sfm.common.registry.SFMBlockEntities; import ca.teamdman.sfm.common.registry.SFMPackets; import ca.teamdman.sfm.common.util.OpenContainerTracker; @@ -238,6 +239,7 @@ public void clearContent() { public void reset() { getDisk().ifPresent(disk -> { + LabelPositionHolder.purge(disk); disk.setTag(null); setItem(0, disk); setChanged(); diff --git a/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java b/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java index 3af09148a..e2085bdcb 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java @@ -60,6 +60,11 @@ public void save(ItemStack stack) { CACHE.put(stack, new LabelPositionHolder(this)); } + public static void purge(ItemStack stack) { + stack.getOrCreateTag().remove("sfm:labels"); + CACHE.remove(stack); + } + public CompoundTag serialize() { var tag = new CompoundTag(); for (var label : get().keySet()) { diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 739802d33..1268e3c69 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -8,6 +8,7 @@ NAME "Changelog" -- fix label guns sharing internal objects with each other and disks ---- this caused disks in managers to get updated without pushing ---- sometimes manager disk didn't have labels you thought you pushed +-- fix reset button not purging labels ---- 4.16.0 ---- -- fix crashes using ctrl+space From cb810aca15d06636cd2dafbba5c5aeef348d9dcf Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 7 Jul 2024 23:14:20 -0400 Subject: [PATCH 02/49] manager button handler method naming clarity --- .../sfm/client/gui/screen/ManagerScreen.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java b/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java index f7806ab5f..75ad00df0 100644 --- a/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java +++ b/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java @@ -112,7 +112,7 @@ protected void init() { buttonWidth, 16, MANAGER_GUI_PASTE_FROM_CLIPBOARD_BUTTON.getComponent(), - button -> this.onLoadClipboard(), + button -> this.onClipboardPasteButtonClicked(), buildTooltip(MANAGER_GUI_PASTE_FROM_CLIPBOARD_BUTTON_TOOLTIP) )); editButton = this.addRenderableWidget(new ExtendedButtonWithTooltip( @@ -121,7 +121,7 @@ protected void init() { buttonWidth, 16, MANAGER_GUI_EDIT_BUTTON.getComponent(), - button -> onEdit(), + button -> onEditButtonClicked(), buildTooltip(MANAGER_GUI_EDIT_BUTTON_TOOLTIP) )); examplesButton = this.addRenderableWidget(new ExtendedButtonWithTooltip( @@ -130,7 +130,7 @@ protected void init() { buttonWidth, 16, MANAGER_GUI_VIEW_EXAMPLES_BUTTON.getComponent(), - button -> onShowExamples(), + button -> onExamplesButtonClicked(), buildTooltip(MANAGER_GUI_VIEW_EXAMPLES_BUTTON_TOOLTIP) )); clipboardCopyButton = this.addRenderableWidget(new ExtendedButton( @@ -139,7 +139,7 @@ protected void init() { buttonWidth, 16, MANAGER_GUI_COPY_TO_CLIPBOARD_BUTTON.getComponent(), - button -> this.onSaveClipboard() + button -> this.onClipboardCopyButtonClicked() )); logsButton = this.addRenderableWidget(new ExtendedButton( (this.width - this.imageWidth) / 2 - buttonWidth, @@ -147,7 +147,7 @@ protected void init() { buttonWidth, 16, MANAGER_GUI_VIEW_LOGS_BUTTON.getComponent(), - button -> onShowLogs() + button -> onLogsButtonClicked() )); rebuildButton = this.addRenderableWidget(new ExtendedButton( (this.width - this.imageWidth) / 2 - buttonWidth, @@ -155,7 +155,7 @@ protected void init() { buttonWidth, 16, MANAGER_GUI_REBUILD_BUTTON.getComponent(), - button -> this.onSendRebuild() + button -> this.onRebuildButtonClicked() )); resetButton = this.addRenderableWidget(new ExtendedButtonWithTooltip( (this.width - this.imageWidth) / 2 + 120, @@ -163,7 +163,7 @@ protected void init() { 50, 12, MANAGER_GUI_RESET_BUTTON.getComponent(), - button -> sendReset(), + button -> onResetButtonClicked(), buildTooltip(MANAGER_GUI_RESET_BUTTON_TOOLTIP) )); diagButton = this.addRenderableWidget(new ExtendedButtonWithTooltip( @@ -172,13 +172,7 @@ protected void init() { 12, 14, Component.literal("!"), - button -> { - if (Screen.hasShiftDown() && !isReadOnly()) { - sendAttemptFix(); - } else { - this.onSaveDiagClipboard(); - } - }, + button -> onDiagButtonClicked(), buildTooltip(isReadOnly() ? MANAGER_GUI_WARNING_BUTTON_TOOLTIP_READ_ONLY : MANAGER_GUI_WARNING_BUTTON_TOOLTIP) @@ -186,19 +180,27 @@ protected void init() { updateVisibilities(); } - private void onEdit() { + private void onDiagButtonClicked() { + if (Screen.hasShiftDown() && !isReadOnly()) { + sendAttemptFix(); + } else { + this.onSaveDiagClipboard(); + } + } + + private void onEditButtonClicked() { ClientStuff.showProgramEditScreen(DiskItem.getProgram(menu.getDisk()), this::sendProgram); } - private void onShowExamples() { + private void onExamplesButtonClicked() { ClientStuff.showExampleListScreen(DiskItem.getProgram(menu.getDisk()), this::sendProgram); } - private void onShowLogs() { + private void onLogsButtonClicked() { ClientStuff.showLogsScreen(menu); } - private void sendReset() { + private void onResetButtonClicked() { SFMPackets.MANAGER_CHANNEL.sendToServer(new ServerboundManagerResetPacket( menu.containerId, menu.MANAGER_POSITION @@ -207,7 +209,7 @@ private void sendReset() { statusCountdown = STATUS_DURATION; } - private void onSendRebuild() { + private void onRebuildButtonClicked() { SFMPackets.MANAGER_CHANNEL.sendToServer(new ServerboundManagerRebuildPacket( menu.containerId, menu.MANAGER_POSITION @@ -236,7 +238,7 @@ private void sendProgram(String program) { statusCountdown = STATUS_DURATION; } - private void onSaveClipboard() { + private void onClipboardCopyButtonClicked() { try { Minecraft.getInstance().keyboardHandler.setClipboard(menu.program); status = MANAGER_GUI_STATUS_SAVED_CLIPBOARD.getComponent(); @@ -269,7 +271,7 @@ private void onSaveDiagClipboard() { } } - private void onLoadClipboard() { + private void onClipboardPasteButtonClicked() { try { String contents = Minecraft.getInstance().keyboardHandler.getClipboard(); sendProgram(contents); @@ -281,19 +283,19 @@ private void onLoadClipboard() { @Override public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { if (Screen.isPaste(pKeyCode) && clipboardPasteButton.visible) { - onLoadClipboard(); + onClipboardPasteButtonClicked(); return true; } else if (Screen.isCopy(pKeyCode) && clipboardCopyButton.visible) { - onSaveClipboard(); + onClipboardCopyButtonClicked(); return true; } else if (pKeyCode == GLFW.GLFW_KEY_E && Screen.hasControlDown() && Screen.hasShiftDown() && examplesButton.visible) { - onShowExamples(); + onExamplesButtonClicked(); return true; } else if (pKeyCode == GLFW.GLFW_KEY_E && Screen.hasControlDown() && editButton.visible) { - onEdit(); + onEditButtonClicked(); return true; } return super.keyPressed(pKeyCode, pScanCode, pModifiers); From 67ee785ada76cecd0d56a1f819372cbb5ef1c4ec Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 7 Jul 2024 23:23:19 -0400 Subject: [PATCH 03/49] add confirmation screen to reset button --- .../c622617f6fabf890a00b9275cd5f643584a8a2c8 | 4 +-- .../resources/assets/sfm/lang/en_us.json | 4 +++ .../sfm/client/gui/screen/ManagerScreen.java | 30 +++++++++++++++---- .../ca/teamdman/sfm/common/Constants.java | 16 ++++++++++ .../sfm/template_programs/changelog.sfml | 1 + 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 195eb56a0..5e214808b 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -1,2 +1,2 @@ -// 1.19.2 2024-07-06T17:27:07.0138117 Languages: en_us -4be3cb294c2eb6e344a5f4a20f9ebed5bdf7ccea assets/sfm/lang/en_us.json +// 1.19.2 2024-07-07T23:20:52.3280082 Languages: en_us +bbbe40f5ae2c61748dfb4371b4053d8073f673b3 assets/sfm/lang/en_us.json diff --git a/src/generated/resources/assets/sfm/lang/en_us.json b/src/generated/resources/assets/sfm/lang/en_us.json index 24acc6e91..099b75a53 100644 --- a/src/generated/resources/assets/sfm/lang/en_us.json +++ b/src/generated/resources/assets/sfm/lang/en_us.json @@ -50,6 +50,10 @@ "gui.sfm.manager.edit_button.tooltip": "Press Ctrl+E to edit.", "gui.sfm.manager.hovered_tick_time": "Hovered tick time: %s ms", "gui.sfm.manager.peak_tick_time": "Peak tick time: %s ms", + "gui.sfm.manager.reset_confirm_screen.message": "Are you sure you want to reset this disk?", + "gui.sfm.manager.reset_confirm_screen.no_button": "Never mind, make no changes", + "gui.sfm.manager.reset_confirm_screen.title": "Reset disk?", + "gui.sfm.manager.reset_confirm_screen.yes_button": "Wipe program and labels", "gui.sfm.manager.state": "State: %s", "gui.sfm.manager.state.invalid_program": "invalid program", "gui.sfm.manager.state.no_disk": "missing disk", diff --git a/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java b/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java index 75ad00df0..0d3eaef81 100644 --- a/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java +++ b/src/main/java/ca/teamdman/sfm/client/gui/screen/ManagerScreen.java @@ -3,6 +3,7 @@ import ca.teamdman.sfm.SFM; import ca.teamdman.sfm.client.ClientDiagnosticInfo; import ca.teamdman.sfm.client.ClientStuff; +import ca.teamdman.sfm.common.Constants; import ca.teamdman.sfm.common.containermenu.ManagerContainerMenu; import ca.teamdman.sfm.common.item.DiskItem; import ca.teamdman.sfm.common.net.*; @@ -13,6 +14,7 @@ import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.screens.ConfirmScreen; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.player.LocalPlayer; @@ -201,12 +203,28 @@ private void onLogsButtonClicked() { } private void onResetButtonClicked() { - SFMPackets.MANAGER_CHANNEL.sendToServer(new ServerboundManagerResetPacket( - menu.containerId, - menu.MANAGER_POSITION - )); - status = MANAGER_GUI_STATUS_RESET.getComponent(); - statusCountdown = STATUS_DURATION; + ConfirmScreen confirmScreen = new ConfirmScreen( + proceed -> { + assert this.minecraft != null; + this.minecraft.popGuiLayer(); // Close confirm screen + + if (proceed) { + SFMPackets.MANAGER_CHANNEL.sendToServer(new ServerboundManagerResetPacket( + menu.containerId, + menu.MANAGER_POSITION + )); + status = MANAGER_GUI_STATUS_RESET.getComponent(); + statusCountdown = STATUS_DURATION; + } + }, + Constants.LocalizationKeys.MANAGER_RESET_CONFIRM_SCREEN_TITLE.getComponent(), + Constants.LocalizationKeys.MANAGER_RESET_CONFIRM_SCREEN_MESSAGE.getComponent(), + Constants.LocalizationKeys.MANAGER_RESET_CONFIRM_SCREEN_YES_BUTTON.getComponent(), + Constants.LocalizationKeys.MANAGER_RESET_CONFIRM_SCREEN_NO_BUTTON.getComponent() + ); + assert this.minecraft != null; + this.minecraft.pushGuiLayer(confirmScreen); + confirmScreen.setDelay(20); } private void onRebuildButtonClicked() { diff --git a/src/main/java/ca/teamdman/sfm/common/Constants.java b/src/main/java/ca/teamdman/sfm/common/Constants.java index a086f61cf..d51c22bc3 100644 --- a/src/main/java/ca/teamdman/sfm/common/Constants.java +++ b/src/main/java/ca/teamdman/sfm/common/Constants.java @@ -44,6 +44,22 @@ public static final class LocalizationKeys { "gui.sfm.save_changes_confirm.no_button", "Continue editing" ); + public static final LocalizationEntry MANAGER_RESET_CONFIRM_SCREEN_TITLE = new LocalizationEntry( + "gui.sfm.manager.reset_confirm_screen.title", + "Reset disk?" + ); + public static final LocalizationEntry MANAGER_RESET_CONFIRM_SCREEN_MESSAGE = new LocalizationEntry( + "gui.sfm.manager.reset_confirm_screen.message", + "Are you sure you want to reset this disk?" + ); + public static final LocalizationEntry MANAGER_RESET_CONFIRM_SCREEN_YES_BUTTON = new LocalizationEntry( + "gui.sfm.manager.reset_confirm_screen.yes_button", + "Wipe program and labels" + ); + public static final LocalizationEntry MANAGER_RESET_CONFIRM_SCREEN_NO_BUTTON = new LocalizationEntry( + "gui.sfm.manager.reset_confirm_screen.no_button", + "Never mind, make no changes" + ); public static final LocalizationEntry EXIT_WITHOUT_SAVING_CONFIRM_SCREEN_TITLE = new LocalizationEntry( "gui.sfm.exit_without_saving_confirm.title", "Exit without saving?" diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 1268e3c69..09858016b 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -9,6 +9,7 @@ NAME "Changelog" ---- this caused disks in managers to get updated without pushing ---- sometimes manager disk didn't have labels you thought you pushed -- fix reset button not purging labels +-- add confirmation screen to reset button ---- 4.16.0 ---- -- fix crashes using ctrl+space From 9ec16f79682827707f2f5c04fe022479a7985fba Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 7 Jul 2024 23:29:40 -0400 Subject: [PATCH 04/49] remove confirmation screen when overwriting empty program --- .../ca/teamdman/sfm/client/gui/screen/ExampleEditScreen.java | 2 +- src/main/resources/assets/sfm/template_programs/changelog.sfml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ca/teamdman/sfm/client/gui/screen/ExampleEditScreen.java b/src/main/java/ca/teamdman/sfm/client/gui/screen/ExampleEditScreen.java index 897821959..6858c5ebb 100644 --- a/src/main/java/ca/teamdman/sfm/client/gui/screen/ExampleEditScreen.java +++ b/src/main/java/ca/teamdman/sfm/client/gui/screen/ExampleEditScreen.java @@ -27,7 +27,7 @@ public boolean equalsAnyTemplate(String content) { @Override public void saveAndClose() { // The user is attempting to apply a code change to the disk - if (equalsAnyTemplate(program)) { + if (program.isBlank() || equalsAnyTemplate(program)) { // The disk contains template code, safe to overwrite super.saveAndClose(); } else { diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 09858016b..733f18e7c 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -10,6 +10,7 @@ NAME "Changelog" ---- sometimes manager disk didn't have labels you thought you pushed -- fix reset button not purging labels -- add confirmation screen to reset button +-- remove confirmation screen when overwriting empty program ---- 4.16.0 ---- -- fix crashes using ctrl+space From 423fe4392d2d5eea947ddeffd66ce06c8424081b Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 7 Jul 2024 23:35:40 -0400 Subject: [PATCH 05/49] add client config for showing line numbers --- .../client/gui/screen/ProgramEditScreen.java | 49 ++++++++++++------- .../ca/teamdman/sfm/common/SFMConfig.java | 14 ++++++ .../sfm/template_programs/changelog.sfml | 1 + 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java b/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java index be31a45a1..8605542a8 100644 --- a/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java +++ b/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java @@ -5,6 +5,7 @@ import ca.teamdman.sfm.client.ProgramTokenContextActions; import ca.teamdman.sfm.client.gui.EditorUtils; import ca.teamdman.sfm.common.Constants; +import ca.teamdman.sfm.common.SFMConfig; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.math.Matrix4f; @@ -287,12 +288,24 @@ public void setCursorPosition(int cursor) { this.textField.cursor = cursor; } + public int getLineNumberWidth() { + if (shouldShowLineNumbers()) { + return this.font.width("000"); + } else { + return 0; + } + } + + private static boolean shouldShowLineNumbers() { + return SFMConfig.getOrDefault(SFMConfig.CLIENT.showLineNumbers); + } + @Override public boolean mouseClicked(double mx, double my, int button) { try { // if mouse in bounds, translate to accommodate line numbers if (mx >= this.x + 1 && mx <= this.x + this.width - 1) { - mx -= 1 + this.font.width("000"); + mx -= 1 + getLineNumberWidth(); } return super.mouseClicked(mx , my, button); } catch (Exception e) { @@ -311,7 +324,7 @@ public boolean mouseDragged( ) { // if mouse in bounds, translate to accommodate line numbers if (mx >= this.x + 1 && mx <= this.x + this.width - 1) { - mx -= 1 + this.font.width("000"); + mx -= 1 + getLineNumberWidth(); } return super.mouseDragged(mx, my, button, dx, dy); } @@ -342,7 +355,7 @@ protected void renderContents(PoseStack poseStack, int mx, int my, float partial boolean isCursorVisible = this.isFocused() && this.frame / 6 % 2 == 0; boolean isCursorAtEndOfLine = false; int cursorIndex = textField.cursor(); - int lineX = this.x + this.innerPadding() + this.font.width("000"); + int lineX = this.x + this.innerPadding() + getLineNumberWidth(); int lineY = this.y + this.innerPadding(); int charCount = 0; int cursorX = 0; @@ -360,20 +373,22 @@ protected void renderContents(PoseStack poseStack, int mx, int my, float partial && cursorIndex <= charCount + lineLength; var buffer = MultiBufferSource.immediate(Tesselator.getInstance().getBuilder()); - // Draw line number - String lineNumber = String.valueOf(line + 1); - this.font.drawInBatch( - lineNumber, - lineX - 2 - this.font.width(lineNumber), - lineY, - -1, - true, - matrix4f, - buffer, - false, - 0, - LightTexture.FULL_BRIGHT - ); + if (shouldShowLineNumbers()) { + // Draw line number + String lineNumber = String.valueOf(line + 1); + this.font.drawInBatch( + lineNumber, + lineX - 2 - this.font.width(lineNumber), + lineY, + -1, + true, + matrix4f, + buffer, + false, + 0, + LightTexture.FULL_BRIGHT + ); + } if (cursorOnThisLine) { isCursorAtEndOfLine = cursorIndex == charCount + lineLength; diff --git a/src/main/java/ca/teamdman/sfm/common/SFMConfig.java b/src/main/java/ca/teamdman/sfm/common/SFMConfig.java index 314d36737..71b71eb14 100644 --- a/src/main/java/ca/teamdman/sfm/common/SFMConfig.java +++ b/src/main/java/ca/teamdman/sfm/common/SFMConfig.java @@ -7,11 +7,16 @@ public class SFMConfig { public static final ForgeConfigSpec COMMON_SPEC; + public static final ForgeConfigSpec CLIENT_SPEC; public static final SFMConfig.Common COMMON; + public static final SFMConfig.Client CLIENT; static { final Pair commonSpecPair = new ForgeConfigSpec.Builder().configure(SFMConfig.Common::new); COMMON_SPEC = commonSpecPair.getRight(); COMMON = commonSpecPair.getLeft(); + final Pair clientSpecPair = new ForgeConfigSpec.Builder().configure(SFMConfig.Client::new); + CLIENT_SPEC = clientSpecPair.getRight(); + CLIENT = clientSpecPair.getLeft(); } public static class Common { @@ -25,6 +30,14 @@ public static class Common { .defineInRange("timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIOStatementsPresent", 1, 1, Integer.MAX_VALUE); } } + public static class Client { + public final ForgeConfigSpec.BooleanValue showLineNumbers; + + Client(ForgeConfigSpec.Builder builder) { + showLineNumbers = builder + .define("showLineNumbers", true); + } + } /** * Get a config value in a way that doesn't fail when running tests @@ -39,5 +52,6 @@ public static T getOrDefault(ForgeConfigSpec.ConfigValue configValue) { public static void register(ModLoadingContext context) { context.registerConfig(ModConfig.Type.COMMON, SFMConfig.COMMON_SPEC); + context.registerConfig(ModConfig.Type.CLIENT, SFMConfig.CLIENT_SPEC); } } diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 733f18e7c..424fd0ed6 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -11,6 +11,7 @@ NAME "Changelog" -- fix reset button not purging labels -- add confirmation screen to reset button -- remove confirmation screen when overwriting empty program +-- add client config for showing line numbers ---- 4.16.0 ---- -- fix crashes using ctrl+space From 5fe46ed6900603b5e78d0871e1178f048dc6e3bc Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 7 Jul 2024 23:58:10 -0400 Subject: [PATCH 06/49] add toggle button in program edit screen --- .../c622617f6fabf890a00b9275cd5f643584a8a2c8 | 4 +- .../resources/assets/sfm/lang/en_us.json | 1 + .../client/gui/screen/ProgramEditScreen.java | 58 ++++++++++++------- .../ca/teamdman/sfm/common/Constants.java | 4 ++ .../ca/teamdman/sfm/common/SFMConfig.java | 2 +- .../sfm/template_programs/changelog.sfml | 1 + 6 files changed, 47 insertions(+), 23 deletions(-) diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 5e214808b..5e1f1fbec 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -1,2 +1,2 @@ -// 1.19.2 2024-07-07T23:20:52.3280082 Languages: en_us -bbbe40f5ae2c61748dfb4371b4053d8073f673b3 assets/sfm/lang/en_us.json +// 1.19.2 2024-07-07T23:46:45.730411 Languages: en_us +e429e836b67ddbf103765ecb344770ad3dd0a76c assets/sfm/lang/en_us.json diff --git a/src/generated/resources/assets/sfm/lang/en_us.json b/src/generated/resources/assets/sfm/lang/en_us.json index 099b75a53..b49a0a022 100644 --- a/src/generated/resources/assets/sfm/lang/en_us.json +++ b/src/generated/resources/assets/sfm/lang/en_us.json @@ -74,6 +74,7 @@ "gui.sfm.save_changes_confirm.yes_button": "Overwrite disk", "gui.sfm.text_editor.done_button.tooltip": "Shift+Enter to submit", "gui.sfm.text_editor.title": "Text Editor", + "gui.sfm.text_editor.toggle_line_numbers_button.tooltip": "Toggle line numbers", "gui.sfm.title.labelgun": "Label Gun", "gui.sfm.title.program_template_picker": "Program Template Picker", "item.sfm.disk": "Factory Manager Program Disk", diff --git a/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java b/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java index 8605542a8..8945df0fb 100644 --- a/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java +++ b/src/main/java/ca/teamdman/sfm/client/gui/screen/ProgramEditScreen.java @@ -32,6 +32,7 @@ import java.util.function.Consumer; import static ca.teamdman.sfm.common.Constants.LocalizationKeys.PROGRAM_EDIT_SCREEN_DONE_BUTTON_TOOLTIP; +import static ca.teamdman.sfm.common.Constants.LocalizationKeys.PROGRAM_EDIT_SCREEN_TOGGLE_LINE_NUMBERS_BUTTON_TOOLTIP; public class ProgramEditScreen extends Screen { protected final String INITIAL_CONTENT; @@ -77,6 +78,16 @@ protected void init() { textarea.setValue(INITIAL_CONTENT); this.setInitialFocus(textarea); + + this.addRenderableWidget(new Button( + this.width / 2 - 200, + this.height / 2 - 100 + 195, + 16, + 20, + Component.literal("#"), + (button) -> this.onToggleLineNumbersButtonClicked(), + buildTooltip(PROGRAM_EDIT_SCREEN_TOGGLE_LINE_NUMBERS_BUTTON_TOOLTIP) + )); this.addRenderableWidget(new Button( this.width / 2 - 2 - 150, this.height / 2 - 100 + 195, @@ -84,20 +95,7 @@ protected void init() { 20, CommonComponents.GUI_DONE, (button) -> this.saveAndClose(), - (button, pose, mx, my) -> renderTooltip( - pose, - font.split( - PROGRAM_EDIT_SCREEN_DONE_BUTTON_TOOLTIP.getComponent(), - Math.max( - width - / 2 - - 43, - 170 - ) - ), - mx, - my - ) + buildTooltip(PROGRAM_EDIT_SCREEN_DONE_BUTTON_TOOLTIP) )); this.addRenderableWidget(new Button( this.width / 2 - 2 + 100, @@ -109,6 +107,30 @@ protected void init() { )); } + private Button.OnTooltip buildTooltip(Constants.LocalizationKeys.LocalizationEntry entry) { + return (btn, pose, mx, my) -> renderTooltip( + pose, + font.split( + entry.getComponent(), + Math.max( + width + / 2 + - 43, + 170 + ) + ), + mx, + my + ); + } + + private static boolean shouldShowLineNumbers() { + return SFMConfig.getOrDefault(SFMConfig.CLIENT.showLineNumbers); + } + private void onToggleLineNumbersButtonClicked() { + SFMConfig.CLIENT.showLineNumbers.set(!shouldShowLineNumbers()); + } + public void saveAndClose() { SAVE_CALLBACK.accept(textarea.getValue()); @@ -296,16 +318,12 @@ public int getLineNumberWidth() { } } - private static boolean shouldShowLineNumbers() { - return SFMConfig.getOrDefault(SFMConfig.CLIENT.showLineNumbers); - } - @Override public boolean mouseClicked(double mx, double my, int button) { try { // if mouse in bounds, translate to accommodate line numbers if (mx >= this.x + 1 && mx <= this.x + this.width - 1) { - mx -= 1 + getLineNumberWidth(); + mx -= getLineNumberWidth(); } return super.mouseClicked(mx , my, button); } catch (Exception e) { @@ -324,7 +342,7 @@ public boolean mouseDragged( ) { // if mouse in bounds, translate to accommodate line numbers if (mx >= this.x + 1 && mx <= this.x + this.width - 1) { - mx -= 1 + getLineNumberWidth(); + mx -= getLineNumberWidth(); } return super.mouseDragged(mx, my, button, dx, dy); } diff --git a/src/main/java/ca/teamdman/sfm/common/Constants.java b/src/main/java/ca/teamdman/sfm/common/Constants.java index d51c22bc3..4e407ed55 100644 --- a/src/main/java/ca/teamdman/sfm/common/Constants.java +++ b/src/main/java/ca/teamdman/sfm/common/Constants.java @@ -28,6 +28,10 @@ public static final class LocalizationKeys { "gui.sfm.text_editor.done_button.tooltip", "Shift+Enter to submit" ); + public static final LocalizationEntry PROGRAM_EDIT_SCREEN_TOGGLE_LINE_NUMBERS_BUTTON_TOOLTIP = new LocalizationEntry( + "gui.sfm.text_editor.toggle_line_numbers_button.tooltip", + "Toggle line numbers" + ); public static final LocalizationEntry SAVE_CHANGES_CONFIRM_SCREEN_TITLE = new LocalizationEntry( "gui.sfm.save_changes_confirm.title", "Save changes" diff --git a/src/main/java/ca/teamdman/sfm/common/SFMConfig.java b/src/main/java/ca/teamdman/sfm/common/SFMConfig.java index 71b71eb14..945e9be85 100644 --- a/src/main/java/ca/teamdman/sfm/common/SFMConfig.java +++ b/src/main/java/ca/teamdman/sfm/common/SFMConfig.java @@ -35,7 +35,7 @@ public static class Client { Client(ForgeConfigSpec.Builder builder) { showLineNumbers = builder - .define("showLineNumbers", true); + .define("showLineNumbers", false); } } diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 424fd0ed6..4a8991d8e 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -12,6 +12,7 @@ NAME "Changelog" -- add confirmation screen to reset button -- remove confirmation screen when overwriting empty program -- add client config for showing line numbers +---- add toggle button in program edit screen ---- 4.16.0 ---- -- fix crashes using ctrl+space From f2651dcdba3930e543cffce8642302e837df113d Mon Sep 17 00:00:00 2001 From: TeamDman Date: Mon, 8 Jul 2024 00:02:24 -0400 Subject: [PATCH 07/49] add short circuit in program build when parser errors present, fixes #138 also add the program content to the logs --- .../java/ca/teamdman/sfml/ast/Program.java | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index 9a6ae1386..88c72585e 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -57,38 +57,45 @@ public static void compile( SFMLParser.ProgramContext context = parser.program(); buildErrors.stream().map(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL::get).forEach(errors::add); + // build AST Program program = null; - try { - program = builder.visitProgram(context); - // make sure all referenced resources exist now during compilation instead of waiting for the program to tick - - for (ResourceIdentifier referencedResource : program.referencedResources) { - try { - ResourceType resourceType = referencedResource.getResourceType(); - if (resourceType == null) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_UNKNOWN_RESOURCE_TYPE.get( - referencedResource)); + if (errors.isEmpty()) { + try { + program = builder.visitProgram(context); + // make sure all referenced resources exist now during compilation instead of waiting for the program to tick + + for (ResourceIdentifier referencedResource : program.referencedResources) { + try { + ResourceType resourceType = referencedResource.getResourceType(); + if (resourceType == null) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_UNKNOWN_RESOURCE_TYPE.get( + referencedResource)); + } + } catch (ResourceLocationException e) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_MALFORMED_RESOURCE_TYPE.get(referencedResource)); } - } catch (ResourceLocationException e) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_MALFORMED_RESOURCE_TYPE.get(referencedResource)); } - } - } catch (ResourceLocationException | IllegalArgumentException | AssertionError e) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL.get(e.getMessage())); - } catch (Throwable t) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); - SFM.LOGGER.error("Encountered unhandled error while compiling program", t); - if (!FMLEnvironment.production) { - var message = t.getMessage(); - if (message != null) { - errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName() + ": " + message)); - } else { - errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName())); + } catch (ResourceLocationException | IllegalArgumentException | AssertionError e) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL.get(e.getMessage())); + } catch (Throwable t) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); + SFM.LOGGER.error("Encountered unhandled error \"{}\" while compiling program\n```\n{}\n```", t, programString); + if (!FMLEnvironment.production) { + var message = t.getMessage(); + if (message != null) { + errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName() + ": " + message)); + } else { + errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName())); + } } } } + if (program == null && errors.isEmpty()) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); + SFM.LOGGER.error("Program was somehow null after a successful compile. I have no idea how this could happen, but it definitely shouldn't.\n```\n{}\n```", programString); + } if (errors.isEmpty()) { onSuccess.accept(program, builder); From 516d08dbd853b91da49de2c541ebb36ea3a648a9 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Mon, 8 Jul 2024 00:10:37 -0400 Subject: [PATCH 08/49] changelog - reduce log spam from syntax errors --- src/main/resources/assets/sfm/template_programs/changelog.sfml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 4a8991d8e..14e494e57 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -13,6 +13,8 @@ NAME "Changelog" -- remove confirmation screen when overwriting empty program -- add client config for showing line numbers ---- add toggle button in program edit screen +-- reduce log spam from syntax errors +---- build process now properly stops when parsing fails ---- 4.16.0 ---- -- fix crashes using ctrl+space From b22210e7dbe6455470b404ddefabfe0ab552f7f6 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Mon, 8 Jul 2024 00:11:12 -0400 Subject: [PATCH 09/49] bump minor version --- gradle.properties | 2 +- src/main/resources/assets/sfm/template_programs/changelog.sfml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index d6e8df96d..dfb9dde71 100644 --- a/gradle.properties +++ b/gradle.properties @@ -42,7 +42,7 @@ mod_name=Super Factory Manager # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=Mozilla Public License Version 2.0 # The mod version. See https://semver.org/ -mod_version=4.16.1 +mod_version=4.17.0 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 14e494e57..92d04ca15 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -2,7 +2,7 @@ NAME "Changelog" -- Official SFM Discord: -- https://discord.gg/5mbUY3mu6m ----- 4.16.1 ---- +---- 4.17.0 ---- -- fix cache not being invalidated when chunk unloaded ---- this is caused by other mods not invalidating capabilities -- fix label guns sharing internal objects with each other and disks From a682abddcbdfe8d34780c33c70484ed224204891 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Mon, 8 Jul 2024 00:14:32 -0400 Subject: [PATCH 10/49] changelog clarity --- src/main/resources/assets/sfm/template_programs/changelog.sfml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/sfm/template_programs/changelog.sfml b/src/main/resources/assets/sfm/template_programs/changelog.sfml index 92d04ca15..d78cc5da1 100644 --- a/src/main/resources/assets/sfm/template_programs/changelog.sfml +++ b/src/main/resources/assets/sfm/template_programs/changelog.sfml @@ -4,7 +4,8 @@ NAME "Changelog" ---- 4.17.0 ---- -- fix cache not being invalidated when chunk unloaded ----- this is caused by other mods not invalidating capabilities +---- this was caused by other mods not invalidating capabilities +---- this caused managers to stop working (rejoining a server, etc) -- fix label guns sharing internal objects with each other and disks ---- this caused disks in managers to get updated without pushing ---- sometimes manager disk didn't have labels you thought you pushed From f22273258f6482a20c0e09011bacb9ebd03a414d Mon Sep 17 00:00:00 2001 From: TeamDman Date: Thu, 11 Jul 2024 23:06:56 -0400 Subject: [PATCH 11/49] update localizations --- .../.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 | 4 ++-- src/generated/resources/assets/sfm/lang/en_us.json | 6 ++++-- src/main/java/ca/teamdman/sfm/common/Constants.java | 12 ++++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 index 5e1f1fbec..211d6919b 100644 --- a/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 +++ b/src/generated/resources/.cache/c622617f6fabf890a00b9275cd5f643584a8a2c8 @@ -1,2 +1,2 @@ -// 1.19.2 2024-07-07T23:46:45.730411 Languages: en_us -e429e836b67ddbf103765ecb344770ad3dd0a76c assets/sfm/lang/en_us.json +// 1.19.2 2024-07-11T19:19:37.273704 Languages: en_us +bc64fe70fb894ae1fd9ae7d30e9215e646ee6ad8 assets/sfm/lang/en_us.json diff --git a/src/generated/resources/assets/sfm/lang/en_us.json b/src/generated/resources/assets/sfm/lang/en_us.json index b49a0a022..0ba39d14f 100644 --- a/src/generated/resources/assets/sfm/lang/en_us.json +++ b/src/generated/resources/assets/sfm/lang/en_us.json @@ -115,7 +115,7 @@ "log.sfm.program.tick": "PROGRAM TICK BEGIN", "log.sfm.program.tick.redstone_count": "Program ticking with %d unprocessed redstone pulses.", "log.sfm.program.voided_resources": "!!!RESOURCE LOSS HAS OCCURRED!!! Failed to move all promised items, found %s %s:%s, took %d but had %d left over after insertion", - "log.sfm.resource_type.get_capabilities.begin": "Gathering capabilities of type %s against labels %s", + "log.sfm.resource_type.get_capabilities.begin": "Gathering capabilities of type %s (%s) against labels %s", "log.sfm.resource_type.get_capabilities.not_present": "Capability %s %s direction=%s not present", "log.sfm.resource_type.get_capabilities.present": "Capability %s %s direction=%s present", "log.sfm.statement.tick.forget": "FORGET %s", @@ -130,7 +130,7 @@ "log.sfm.statement.tick.io.gather_slots.not_each": "EACH keyword not used - trackers will be shared between blocks", "log.sfm.statement.tick.io.gather_slots.not_in_range": "Slot %d - not in range", "log.sfm.statement.tick.io.gather_slots.range": "Gathering slots in range set: %s", - "log.sfm.statement.tick.io.gather_slots.resource_types": "Gathering for: %s", + "log.sfm.statement.tick.io.gather_slots.resource_types": "Gathering for: %s (%s)", "log.sfm.statement.tick.io.gather_slots.should_not_create": "Slot %d - skipping - %s", "log.sfm.statement.tick.io.move_to.begin": "Begin moving %s into %s", "log.sfm.statement.tick.io.move_to.destination_tracker_reject": "Destination tracker rejected the transfer, skipping", @@ -162,9 +162,11 @@ "program.sfm.warnings.adjacent_but_disconnected_label": "Label \"%s\" is assigned in the world at %s and is connected by cables but is not detected as a valid inventory.", "program.sfm.warnings.disconnected_label": "Label \"%s\" is assigned in the world at %s but not connected by cables.", "program.sfm.warnings.each_without_pattern": "EACH used without a pattern, statement %s", + "program.sfm.warnings.output_label_not_found_in_inputs": "Statement \"%s\" uses resource type \"%s\" which has no matching input statement.", "program.sfm.warnings.round_robin_smelly_count": "Round robin by label should be used with more than one label, statement %s", "program.sfm.warnings.round_robin_smelly_each": "Round robin by block shouldn't be used with EACH, statement %s", "program.sfm.warnings.undefined_label": "Label \"%s\" is assigned in the world but not defined in code.", "program.sfm.warnings.unknown_resource_id": "Resource \"%s\" was not found.", + "program.sfm.warnings.unused_input_label": "Statement \"%s\" inputs \"%s\" from \"%s\" but no future output statement consume \"%s\".", "program.sfm.warnings.unused_label": "Label \"%s\" is used in code but not assigned in the world." } \ No newline at end of file diff --git a/src/main/java/ca/teamdman/sfm/common/Constants.java b/src/main/java/ca/teamdman/sfm/common/Constants.java index 4e407ed55..d4fa5d984 100644 --- a/src/main/java/ca/teamdman/sfm/common/Constants.java +++ b/src/main/java/ca/teamdman/sfm/common/Constants.java @@ -305,6 +305,14 @@ public static final class LocalizationKeys { "program.sfm.warnings.unused_label", "Label \"%s\" is used in code but not assigned in the world." ); + public static final LocalizationEntry PROGRAM_WARNING_OUTPUT_RESOURCE_TYPE_NOT_FOUND_IN_INPUTS = new LocalizationEntry( + "program.sfm.warnings.output_label_not_found_in_inputs", + "Statement \"%s\" uses resource type \"%s\" which has no matching input statement." + ); + public static final LocalizationEntry PROGRAM_WARNING_UNUSED_INPUT_LABEL = new LocalizationEntry( + "program.sfm.warnings.unused_input_label", + "Statement \"%s\" inputs \"%s\" from \"%s\" but no future output statement consume \"%s\"." + ); public static final LocalizationEntry PROGRAM_WARNING_UNKNOWN_RESOURCE_ID = new LocalizationEntry( "program.sfm.warnings.unknown_resource_id", "Resource \"%s\" was not found." @@ -367,7 +375,7 @@ public static final class LocalizationKeys { public static final LocalizationEntry LOG_RESOURCE_TYPE_GET_CAPABILITIES_BEGIN = new LocalizationEntry( "log.sfm.resource_type.get_capabilities.begin", - "Gathering capabilities of type %s against labels %s" + "Gathering capabilities of type %s (%s) against labels %s" ); public static final LocalizationEntry LOG_RESOURCE_TYPE_GET_CAPABILITIES_CAP_NOT_PRESENT = new LocalizationEntry( "log.sfm.resource_type.get_capabilities.not_present", @@ -520,7 +528,7 @@ public static final class LocalizationKeys { ); public static final LocalizationEntry LOG_PROGRAM_TICK_IO_STATEMENT_GATHER_SLOTS_FOR_RESOURCE_TYPE = new LocalizationEntry( "log.sfm.statement.tick.io.gather_slots.resource_types", - "Gathering for: %s" + "Gathering for: %s (%s)" ); public static final LocalizationEntry LOG_PROGRAM_TICK_OUTPUT_STATEMENT = new LocalizationEntry( "log.sfm.statement.tick.output", From e815e71b33ff8743c0cfdafa8fecf3eb6385ec4f Mon Sep 17 00:00:00 2001 From: TeamDman Date: Thu, 11 Jul 2024 23:08:11 -0400 Subject: [PATCH 12/49] compileAndUpdateErrorsAndWarnings name clarity --- .../teamdman/sfm/common/blockentity/ManagerBlockEntity.java | 2 +- src/main/java/ca/teamdman/sfm/common/item/DiskItem.java | 5 +++-- .../sfm/common/net/ServerboundDiskItemSetProgramPacket.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java b/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java index 53fe6a7d4..3ad87773f 100644 --- a/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java +++ b/src/main/java/ca/teamdman/sfm/common/blockentity/ManagerBlockEntity.java @@ -165,7 +165,7 @@ public Optional getDisk() { public void rebuildProgramAndUpdateDisk() { if (level != null && level.isClientSide()) return; this.program = getDisk() - .flatMap(itemStack -> DiskItem.compileAndUpdateAttributes(itemStack, this)) + .flatMap(itemStack -> DiskItem.compileAndUpdateErrorsAndWarnings(itemStack, this)) .orElse(null); sendUpdatePacket(); } diff --git a/src/main/java/ca/teamdman/sfm/common/item/DiskItem.java b/src/main/java/ca/teamdman/sfm/common/item/DiskItem.java index b6de4ea0a..0bf655a14 100644 --- a/src/main/java/ca/teamdman/sfm/common/item/DiskItem.java +++ b/src/main/java/ca/teamdman/sfm/common/item/DiskItem.java @@ -7,6 +7,7 @@ import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; import ca.teamdman.sfm.common.net.ServerboundDiskItemSetProgramPacket; import ca.teamdman.sfm.common.program.LabelPositionHolder; +import ca.teamdman.sfm.common.program.ProgramLinter; import ca.teamdman.sfm.common.registry.SFMItems; import ca.teamdman.sfm.common.registry.SFMPackets; import ca.teamdman.sfm.common.util.SFMUtils; @@ -56,7 +57,7 @@ public static void setProgram(ItemStack stack, String program) { } - public static Optional compileAndUpdateAttributes(ItemStack stack, @Nullable ManagerBlockEntity manager) { + public static Optional compileAndUpdateErrorsAndWarnings(ItemStack stack, @Nullable ManagerBlockEntity manager) { if (manager != null) { manager.logger.info(x -> x.accept(Constants.LocalizationKeys.PROGRAM_COMPILE_FROM_DISK_BEGIN.get())); } @@ -79,7 +80,7 @@ public static Optional compileAndUpdateAttributes(ItemStack stack, @Nul // Update disk properties setProgramName(stack, successProgram.name()); setWarnings(stack, warnings); - setErrors(stack, errors); + setErrors(stack, Collections.emptyList()); // Track result rtn.set(successProgram); diff --git a/src/main/java/ca/teamdman/sfm/common/net/ServerboundDiskItemSetProgramPacket.java b/src/main/java/ca/teamdman/sfm/common/net/ServerboundDiskItemSetProgramPacket.java index c629eb49b..9ef6b6a52 100644 --- a/src/main/java/ca/teamdman/sfm/common/net/ServerboundDiskItemSetProgramPacket.java +++ b/src/main/java/ca/teamdman/sfm/common/net/ServerboundDiskItemSetProgramPacket.java @@ -38,7 +38,7 @@ public static void handle( var stack = sender.getItemInHand(msg.hand); if (stack.getItem() instanceof DiskItem) { DiskItem.setProgram(stack, msg.programString); - DiskItem.compileAndUpdateAttributes(stack, null); + DiskItem.compileAndUpdateErrorsAndWarnings(stack, null); } }); From fd6d2b8627500db6c92670abb2254f0aac102519 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Thu, 11 Jul 2024 23:09:00 -0400 Subject: [PATCH 13/49] move getConditionIndex to Trigger --- .../java/ca/teamdman/sfml/ast/Program.java | 828 +++++++++--------- .../java/ca/teamdman/sfml/ast/Trigger.java | 33 + 2 files changed, 440 insertions(+), 421 deletions(-) diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index 88c72585e..25a6d2461 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -1,421 +1,407 @@ -package ca.teamdman.sfml.ast; - -import ca.teamdman.sfm.SFM; -import ca.teamdman.sfm.common.Constants; -import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; -import ca.teamdman.sfm.common.cablenetwork.CableNetworkManager; -import ca.teamdman.sfm.common.item.DiskItem; -import ca.teamdman.sfm.common.program.LabelPositionHolder; -import ca.teamdman.sfm.common.program.ProgramContext; -import ca.teamdman.sfm.common.resourcetype.ResourceType; -import ca.teamdman.sfm.common.util.SFMUtils; -import ca.teamdman.sfml.SFMLLexer; -import ca.teamdman.sfml.SFMLParser; -import net.minecraft.ResourceLocationException; -import net.minecraft.network.chat.contents.TranslatableContents; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraftforge.fml.loading.FMLEnvironment; -import org.antlr.v4.runtime.*; -import org.jetbrains.annotations.NotNull; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -public record Program( - String name, - List triggers, - Set referencedLabels, - Set> referencedResources -) implements Statement { - public static final int MAX_PROGRAM_LENGTH = 80960; - public static final int MAX_LABEL_LENGTH = 256; - - public static void compile( - String programString, - BiConsumer onSuccess, - Consumer> onFailure - ) { - SFMLLexer lexer = new SFMLLexer(CharStreams.fromString(programString)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - SFMLParser parser = new SFMLParser(tokens); - ASTBuilder builder = new ASTBuilder(); - - // set up error capturing - lexer.removeErrorListeners(); - parser.removeErrorListeners(); - List errors = new ArrayList<>(); - List buildErrors = new ArrayList<>(); - ListErrorListener listener = new ListErrorListener(buildErrors); - lexer.addErrorListener(listener); - parser.addErrorListener(listener); - - // initial parse - SFMLParser.ProgramContext context = parser.program(); - buildErrors.stream().map(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL::get).forEach(errors::add); - - - // build AST - Program program = null; - if (errors.isEmpty()) { - try { - program = builder.visitProgram(context); - // make sure all referenced resources exist now during compilation instead of waiting for the program to tick - - for (ResourceIdentifier referencedResource : program.referencedResources) { - try { - ResourceType resourceType = referencedResource.getResourceType(); - if (resourceType == null) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_UNKNOWN_RESOURCE_TYPE.get( - referencedResource)); - } - } catch (ResourceLocationException e) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_MALFORMED_RESOURCE_TYPE.get(referencedResource)); - } - } - } catch (ResourceLocationException | IllegalArgumentException | AssertionError e) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL.get(e.getMessage())); - } catch (Throwable t) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); - SFM.LOGGER.error("Encountered unhandled error \"{}\" while compiling program\n```\n{}\n```", t, programString); - if (!FMLEnvironment.production) { - var message = t.getMessage(); - if (message != null) { - errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName() + ": " + message)); - } else { - errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName())); - } - } - } - } - - if (program == null && errors.isEmpty()) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); - SFM.LOGGER.error("Program was somehow null after a successful compile. I have no idea how this could happen, but it definitely shouldn't.\n```\n{}\n```", programString); - } - - if (errors.isEmpty()) { - onSuccess.accept(program, builder); - } else { - onFailure.accept(errors); - } - } - - public ArrayList gatherWarnings(ItemStack disk, @Nullable ManagerBlockEntity manager) { - var warnings = new ArrayList(); - var labels = LabelPositionHolder.from(disk); - // labels in code but not in world - for (String label : referencedLabels) { - var isUsed = !labels.getPositions(label).isEmpty(); - if (!isUsed) { - warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_UNUSED_LABEL.get(label)); - } - } - - // labels used in world but not defined in code - labels.get().keySet() - .stream() - .filter(x -> !referencedLabels.contains(x)) - .forEach(label -> warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_UNDEFINED_LABEL.get(label))); - - var level = manager != null ? manager.getLevel() : null; - if (level != null) { - // labels in world but not connected via cables - CableNetworkManager - .getOrRegisterNetworkFromManagerPosition(manager) - .ifPresent(network -> labels.forEach((label, pos) -> { - var adjacent = network.isAdjacentToCable(pos); - if (!adjacent) { - warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_DISCONNECTED_LABEL.get( - label, - String.format( - "[%d,%d,%d]", - pos.getX(), - pos.getY(), - pos.getZ() - ) - )); - } - var viable = SFMUtils.discoverCapabilityProvider(level, pos).isPresent(); - if (!viable && adjacent) { - warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_CONNECTED_BUT_NOT_VIABLE_LABEL.get( - label, - String.format( - "[%d,%d,%d]", - pos.getX(), - pos.getY(), - pos.getZ() - ) - )); - } - })); - } - - // try and validate that references resources exist - for (var resource : referencedResources) { - // skip regex resources - Optional loc = resource.getLocation(); - if (loc.isEmpty()) continue; - - // make sure resource type is registered - var type = resource.getResourceType(); - if (type == null) { - SFM.LOGGER.error("Resource type not found for resource: {}, should have been validated at program compile", resource); - continue; - } - - // make sure resource exists in the registry - if (!type.registryKeyExists(loc.get())) { - warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_UNKNOWN_RESOURCE_ID.get(resource)); - } - } - - // check for poor round-robin usage - getDescendantStatements() - .filter(IOStatement.class::isInstance) - .map(IOStatement.class::cast) - .forEach(statement -> { - { // round robin smells - var smell = statement - .labelAccess() - .roundRobin() - .getSmell(statement.labelAccess(), statement.each()); - if (smell != null) { - warnings.add(smell.get(statement.toStringPretty())); - } - } - { // resource each without pattern - boolean smells = statement - .resourceLimits() - .resourceLimits() - .stream() - .anyMatch(rl -> rl.limit().quantity().idExpansionBehaviour() - == ResourceQuantity.IdExpansionBehaviour.EXPAND && !rl - .resourceId() - .usesRegex()); - if (smells) { - warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_RESOURCE_EACH_WITHOUT_PATTERN.get( - statement.toStringPretty() - )); - } - } - }); - return warnings; - } - - public void fixWarnings(ItemStack disk, ManagerBlockEntity manager) { - var labels = LabelPositionHolder.from(disk); - // remove labels not defined in code - labels.removeIf(label -> !referencedLabels.contains(label)); - - // remove labels not connected via cables - CableNetworkManager - .getOrRegisterNetworkFromManagerPosition(manager) - .ifPresent(network -> labels.removeIf((label, pos) -> !network.isAdjacentToCable(pos))); - labels.save(disk); - - // update warnings - DiskItem.setWarnings(disk, gatherWarnings(disk, manager)); - } - - /** - * Create a context and tick the program. - * - * @return {@code true} if a trigger entered its body - */ - public boolean tick(ManagerBlockEntity manager) { - var context = new ProgramContext(this, manager, ProgramContext.ExecutionPolicy.UNRESTRICTED); - - // log if there are unprocessed redstone pulses - int unprocessedRedstonePulseCount = manager.getUnprocessedRedstonePulseCount(); - if (unprocessedRedstonePulseCount > 0) { - manager.logger.debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_WITH_REDSTONE_COUNT.get( - unprocessedRedstonePulseCount))); - } - - - tick(context); - - manager.clearRedstonePulseQueue(); - - return context.didSomething(); - } - - @Override - public List getStatements() { - return triggers.stream().map(x -> (Statement) x).toList(); - } - - @Override - public void tick(ProgramContext context) { - for (Trigger t : triggers) { - // Only process triggers that should tick - if (!t.shouldTick(context)) { - continue; - } - - // Set flag and log on first trigger - if (!context.didSomething()) { - context.setDidSomething(true); - context.getLogger().trace(getTraceLogWriter(context)); - context.getLogger().debug(debug -> debug.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK.get())); - } - - // Log pretty triggers - if (triggers instanceof ShortStatement ss) { - context.getLogger().debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_TRIGGER_STATEMENT.get( - ss.toStringShort()))); - } - - // Perform and measure tick - long start = System.nanoTime(); - ProgramContext forkedContext = context.copy(); - t.tick(forkedContext); - forkedContext.free(); - long nanoTimePassed = System.nanoTime() - start; - - // Log trigger time - context.getLogger().info(x -> x.accept(Constants.LocalizationKeys.PROGRAM_TICK_TRIGGER_TIME_MS.get( - nanoTimePassed / 1_000_000.0, - t.toString() - ))); - - } - } - - private static @NotNull Consumer> getTraceLogWriter(ProgramContext context) { - return trace -> { - trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_1.get()); - trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_2.get()); - Level level = context - .getManager() - .getLevel(); - //noinspection DataFlowIssue - context - .getNetwork() - .getCablePositions() - .map(pos -> "- " - + pos.toString() - + " " - + level - .getBlockState( - pos)) - .forEach(body -> trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_BODY.get( - body))); - trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_3.get()); - //noinspection DataFlowIssue - context - .getNetwork() - .getCapabilityProviderPositions() - .map(pos -> "- " + pos.toString() + " " + level - .getBlockState(pos)) - .forEach(body -> trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_BODY.get( - body))); - trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_FOOTER.get()); - - trace.accept(Constants.LocalizationKeys.LOG_LABEL_POSITION_HOLDER_DETAILS_HEADER.get()); - //noinspection DataFlowIssue - context - .getlabelPositions() - .get() - .forEach((label, positions) -> positions - .stream() - .map( - pos -> "- " - + label - + ": " - + pos.toString() - + " " - + level - .getBlockState( - pos) - - ) - .forEach(body -> trace.accept(Constants.LocalizationKeys.LOG_LABEL_POSITION_HOLDER_DETAILS_BODY.get( - body)))); - trace.accept(Constants.LocalizationKeys.LOG_LABEL_POSITION_HOLDER_DETAILS_FOOTER.get()); - trace.accept(Constants.LocalizationKeys.LOG_PROGRAM_CONTEXT.get(context)); - }; - } - - @Override - public String toString() { - var rtn = new StringBuilder(); - rtn.append("NAME \"").append(name).append("\"\n"); - for (Trigger trigger : triggers) { - rtn.append(trigger).append("\n"); - } - return rtn.toString(); - } - - public void replaceOutputStatement(OutputStatement oldStatement, OutputStatement newStatement) { - Deque toPatch = new ArrayDeque<>(); - toPatch.add(this); - while (!toPatch.isEmpty()) { - Statement statement = toPatch.pollFirst(); - List children = statement.getStatements(); - for (int i = 0; i < children.size(); i++) { - Statement child = children.get(i); - if (child == oldStatement) { - children.set(i, newStatement); - } else { - toPatch.add(child); - } - } - } - } - - public int getConditionIndex(IfStatement statement) { - Deque toVisit = new ArrayDeque<>(); - toVisit.add(this); - int seen = 0; - while (!toVisit.isEmpty()) { - Statement current = toVisit.pollFirst(); - if (current instanceof IfStatement ifStatement) { - if (ifStatement == statement) { - return seen; - } - seen++; - } - toVisit.addAll(current.getStatements()); - } - return -1; - } - - public int getConditionCount() { - Deque toVisit = new ArrayDeque<>(); - toVisit.add(this); - int seen = 0; - while (!toVisit.isEmpty()) { - Statement current = toVisit.pollFirst(); - if (current instanceof IfStatement) { - seen++; - } - toVisit.addAll(current.getStatements()); - } - return seen; - } - - public static class ListErrorListener extends BaseErrorListener { - private final List errors; - - public ListErrorListener(List errors) { - this.errors = errors; - } - - @Override - public void syntaxError( - Recognizer recognizer, - Object offendingSymbol, - int line, - int charPositionInLine, - String msg, - RecognitionException e - ) { - errors.add("line " + line + ":" + charPositionInLine + " " + msg); - } - } -} +package ca.teamdman.sfml.ast; + +import ca.teamdman.sfm.SFM; +import ca.teamdman.sfm.common.Constants; +import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; +import ca.teamdman.sfm.common.cablenetwork.CableNetworkManager; +import ca.teamdman.sfm.common.item.DiskItem; +import ca.teamdman.sfm.common.program.LabelPositionHolder; +import ca.teamdman.sfm.common.program.ProgramContext; +import ca.teamdman.sfm.common.resourcetype.ResourceType; +import ca.teamdman.sfm.common.util.SFMUtils; +import ca.teamdman.sfml.SFMLLexer; +import ca.teamdman.sfml.SFMLParser; +import net.minecraft.ResourceLocationException; +import net.minecraft.network.chat.contents.TranslatableContents; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraftforge.fml.loading.FMLEnvironment; +import org.antlr.v4.runtime.*; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public record Program( + String name, + List triggers, + Set referencedLabels, + Set> referencedResources +) implements Statement { + public static final int MAX_PROGRAM_LENGTH = 80960; + public static final int MAX_LABEL_LENGTH = 256; + + public static void compile( + String programString, + BiConsumer onSuccess, + Consumer> onFailure + ) { + SFMLLexer lexer = new SFMLLexer(CharStreams.fromString(programString)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + SFMLParser parser = new SFMLParser(tokens); + ASTBuilder builder = new ASTBuilder(); + + // set up error capturing + lexer.removeErrorListeners(); + parser.removeErrorListeners(); + List errors = new ArrayList<>(); + List buildErrors = new ArrayList<>(); + ListErrorListener listener = new ListErrorListener(buildErrors); + lexer.addErrorListener(listener); + parser.addErrorListener(listener); + + // initial parse + SFMLParser.ProgramContext context = parser.program(); + buildErrors.stream().map(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL::get).forEach(errors::add); + + + // build AST + Program program = null; + if (errors.isEmpty()) { + try { + program = builder.visitProgram(context); + // make sure all referenced resources exist now during compilation instead of waiting for the program to tick + + for (ResourceIdentifier referencedResource : program.referencedResources) { + try { + ResourceType resourceType = referencedResource.getResourceType(); + if (resourceType == null) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_UNKNOWN_RESOURCE_TYPE.get( + referencedResource)); + } + } catch (ResourceLocationException e) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_MALFORMED_RESOURCE_TYPE.get(referencedResource)); + } + } + } catch (ResourceLocationException | IllegalArgumentException | AssertionError e) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL.get(e.getMessage())); + } catch (Throwable t) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); + SFM.LOGGER.error("Encountered unhandled error \"{}\" while compiling program\n```\n{}\n```", t, programString); + if (!FMLEnvironment.production) { + var message = t.getMessage(); + if (message != null) { + errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName() + ": " + message)); + } else { + errors.add(SFMUtils.getTranslatableContents(t.getClass().getSimpleName())); + } + } + } + } + + if (program == null && errors.isEmpty()) { + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); + SFM.LOGGER.error("Program was somehow null after a successful compile. I have no idea how this could happen, but it definitely shouldn't.\n```\n{}\n```", programString); + } + + if (errors.isEmpty()) { + onSuccess.accept(program, builder); + } else { + onFailure.accept(errors); + } + } + + public ArrayList gatherWarnings(ItemStack disk, @Nullable ManagerBlockEntity manager) { + var warnings = new ArrayList(); + var labels = LabelPositionHolder.from(disk); + // labels in code but not in world + for (String label : referencedLabels) { + var isUsed = !labels.getPositions(label).isEmpty(); + if (!isUsed) { + warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_UNUSED_LABEL.get(label)); + } + } + + // labels used in world but not defined in code + labels.get().keySet() + .stream() + .filter(x -> !referencedLabels.contains(x)) + .forEach(label -> warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_UNDEFINED_LABEL.get(label))); + + var level = manager != null ? manager.getLevel() : null; + if (level != null) { + // labels in world but not connected via cables + CableNetworkManager + .getOrRegisterNetworkFromManagerPosition(manager) + .ifPresent(network -> labels.forEach((label, pos) -> { + var adjacent = network.isAdjacentToCable(pos); + if (!adjacent) { + warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_DISCONNECTED_LABEL.get( + label, + String.format( + "[%d,%d,%d]", + pos.getX(), + pos.getY(), + pos.getZ() + ) + )); + } + var viable = SFMUtils.discoverCapabilityProvider(level, pos).isPresent(); + if (!viable && adjacent) { + warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_CONNECTED_BUT_NOT_VIABLE_LABEL.get( + label, + String.format( + "[%d,%d,%d]", + pos.getX(), + pos.getY(), + pos.getZ() + ) + )); + } + })); + } + + // try and validate that references resources exist + for (var resource : referencedResources) { + // skip regex resources + Optional loc = resource.getLocation(); + if (loc.isEmpty()) continue; + + // make sure resource type is registered + var type = resource.getResourceType(); + if (type == null) { + SFM.LOGGER.error("Resource type not found for resource: {}, should have been validated at program compile", resource); + continue; + } + + // make sure resource exists in the registry + if (!type.registryKeyExists(loc.get())) { + warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_UNKNOWN_RESOURCE_ID.get(resource)); + } + } + + // check for poor round-robin usage + getDescendantStatements() + .filter(IOStatement.class::isInstance) + .map(IOStatement.class::cast) + .forEach(statement -> { + { // round robin smells + var smell = statement + .labelAccess() + .roundRobin() + .getSmell(statement.labelAccess(), statement.each()); + if (smell != null) { + warnings.add(smell.get(statement.toStringPretty())); + } + } + { // resource each without pattern + boolean smells = statement + .resourceLimits() + .resourceLimits() + .stream() + .anyMatch(rl -> rl.limit().quantity().idExpansionBehaviour() + == ResourceQuantity.IdExpansionBehaviour.EXPAND && !rl + .resourceId() + .usesRegex()); + if (smells) { + warnings.add(Constants.LocalizationKeys.PROGRAM_WARNING_RESOURCE_EACH_WITHOUT_PATTERN.get( + statement.toStringPretty() + )); + } + } + }); + return warnings; + } + + public void fixWarnings(ItemStack disk, ManagerBlockEntity manager) { + var labels = LabelPositionHolder.from(disk); + // remove labels not defined in code + labels.removeIf(label -> !referencedLabels.contains(label)); + + // remove labels not connected via cables + CableNetworkManager + .getOrRegisterNetworkFromManagerPosition(manager) + .ifPresent(network -> labels.removeIf((label, pos) -> !network.isAdjacentToCable(pos))); + labels.save(disk); + + // update warnings + DiskItem.setWarnings(disk, gatherWarnings(disk, manager)); + } + + /** + * Create a context and tick the program. + * + * @return {@code true} if a trigger entered its body + */ + public boolean tick(ManagerBlockEntity manager) { + var context = new ProgramContext(this, manager, ProgramContext.ExecutionPolicy.UNRESTRICTED); + + // log if there are unprocessed redstone pulses + int unprocessedRedstonePulseCount = manager.getUnprocessedRedstonePulseCount(); + if (unprocessedRedstonePulseCount > 0) { + manager.logger.debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_WITH_REDSTONE_COUNT.get( + unprocessedRedstonePulseCount))); + } + + + tick(context); + + manager.clearRedstonePulseQueue(); + + return context.didSomething(); + } + + @Override + public List getStatements() { + return triggers.stream().map(x -> (Statement) x).toList(); + } + + @Override + public void tick(ProgramContext context) { + for (Trigger t : triggers) { + // Only process triggers that should tick + if (!t.shouldTick(context)) { + continue; + } + + // Set flag and log on first trigger + if (!context.didSomething()) { + context.setDidSomething(true); + context.getLogger().trace(getTraceLogWriter(context)); + context.getLogger().debug(debug -> debug.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK.get())); + } + + // Log pretty triggers + if (triggers instanceof ShortStatement ss) { + context.getLogger().debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_TRIGGER_STATEMENT.get( + ss.toStringShort()))); + } + + // Perform and measure tick + long start = System.nanoTime(); + ProgramContext forkedContext = context.copy(); + t.tick(forkedContext); + forkedContext.free(); + long nanoTimePassed = System.nanoTime() - start; + + // Log trigger time + context.getLogger().info(x -> x.accept(Constants.LocalizationKeys.PROGRAM_TICK_TRIGGER_TIME_MS.get( + nanoTimePassed / 1_000_000.0, + t.toString() + ))); + + } + } + + private static @NotNull Consumer> getTraceLogWriter(ProgramContext context) { + return trace -> { + trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_1.get()); + trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_2.get()); + Level level = context + .getManager() + .getLevel(); + //noinspection DataFlowIssue + context + .getNetwork() + .getCablePositions() + .map(pos -> "- " + + pos.toString() + + " " + + level + .getBlockState( + pos)) + .forEach(body -> trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_BODY.get( + body))); + trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_3.get()); + //noinspection DataFlowIssue + context + .getNetwork() + .getCapabilityProviderPositions() + .map(pos -> "- " + pos.toString() + " " + level + .getBlockState(pos)) + .forEach(body -> trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_BODY.get( + body))); + trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_FOOTER.get()); + + trace.accept(Constants.LocalizationKeys.LOG_LABEL_POSITION_HOLDER_DETAILS_HEADER.get()); + //noinspection DataFlowIssue + context + .getlabelPositions() + .get() + .forEach((label, positions) -> positions + .stream() + .map( + pos -> "- " + + label + + ": " + + pos.toString() + + " " + + level + .getBlockState( + pos) + + ) + .forEach(body -> trace.accept(Constants.LocalizationKeys.LOG_LABEL_POSITION_HOLDER_DETAILS_BODY.get( + body)))); + trace.accept(Constants.LocalizationKeys.LOG_LABEL_POSITION_HOLDER_DETAILS_FOOTER.get()); + trace.accept(Constants.LocalizationKeys.LOG_PROGRAM_CONTEXT.get(context)); + }; + } + + @Override + public String toString() { + var rtn = new StringBuilder(); + rtn.append("NAME \"").append(name).append("\"\n"); + for (Trigger trigger : triggers) { + rtn.append(trigger).append("\n"); + } + return rtn.toString(); + } + + public void replaceOutputStatement(OutputStatement oldStatement, OutputStatement newStatement) { + Deque toPatch = new ArrayDeque<>(); + toPatch.add(this); + while (!toPatch.isEmpty()) { + Statement statement = toPatch.pollFirst(); + List children = statement.getStatements(); + for (int i = 0; i < children.size(); i++) { + Statement child = children.get(i); + if (child == oldStatement) { + children.set(i, newStatement); + } else { + toPatch.add(child); + } + } + } + } + + public void replaceAllOutputStatements(Function mapper) { + Deque toPatch = new ArrayDeque<>(); + toPatch.add(this); + while (!toPatch.isEmpty()) { + Statement statement = toPatch.pollFirst(); + List children = statement.getStatements(); + for (int i = 0; i < children.size(); i++) { + Statement child = children.get(i); + if (child instanceof OutputStatement outputStatement) { + children.set(i, mapper.apply(outputStatement)); + } else { + toPatch.add(child); + } + } + } + } + + public static class ListErrorListener extends BaseErrorListener { + private final List errors; + + public ListErrorListener(List errors) { + this.errors = errors; + } + + @Override + public void syntaxError( + Recognizer recognizer, + Object offendingSymbol, + int line, + int charPositionInLine, + String msg, + RecognitionException e + ) { + errors.add("line " + line + ":" + charPositionInLine + " " + msg); + } + } +} diff --git a/src/main/java/ca/teamdman/sfml/ast/Trigger.java b/src/main/java/ca/teamdman/sfml/ast/Trigger.java index 072316dce..f42168d3f 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Trigger.java +++ b/src/main/java/ca/teamdman/sfml/ast/Trigger.java @@ -2,6 +2,8 @@ import ca.teamdman.sfm.common.program.ProgramContext; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; public interface Trigger extends Statement { @@ -13,4 +15,35 @@ public interface Trigger extends Statement { default List getStatements() { return List.of(getBlock()); } + + default int getConditionIndex(IfStatement statement) { + Deque toVisit = new ArrayDeque<>(); + toVisit.add(this); + int seen = 0; + while (!toVisit.isEmpty()) { + Statement current = toVisit.pollFirst(); + if (current instanceof IfStatement ifStatement) { + if (ifStatement == statement) { + return seen; + } + seen++; + } + toVisit.addAll(current.getStatements()); + } + return -1; + } + + default int getConditionCount() { + Deque toVisit = new ArrayDeque<>(); + toVisit.add(this); + int seen = 0; + while (!toVisit.isEmpty()) { + Statement current = toVisit.pollFirst(); + if (current instanceof IfStatement) { + seen++; + } + toVisit.addAll(current.getStatements()); + } + return seen; + } } From 65b6c110b2188cd068a762b69960e48dabc1e184 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Thu, 11 Jul 2024 23:09:33 -0400 Subject: [PATCH 14/49] LabelPositionHolder doc comment and chaining return type --- .../sfm/common/program/LabelPositionHolder.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java b/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java index e2085bdcb..30484032d 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LabelPositionHolder.java @@ -26,6 +26,13 @@ private LabelPositionHolder(LabelPositionHolder other) { } + /** + * Get the label position holder for this disk. + *

+ * Saves it in the cache for faster future lookups. + *

+ * This mutably borrows the cache entry. + */ public static LabelPositionHolder from(ItemStack stack) { return CACHE.computeIfAbsent(stack, s -> { var tag = stack.getOrCreateTag().getCompound("sfm:labels"); @@ -55,9 +62,10 @@ public static LabelPositionHolder deserialize(CompoundTag tag) { return labels; } - public void save(ItemStack stack) { + public LabelPositionHolder save(ItemStack stack) { stack.getOrCreateTag().put("sfm:labels", serialize()); CACHE.put(stack, new LabelPositionHolder(this)); + return this; } public static void purge(ItemStack stack) { From 9bcaa98d76f074c2b155dcd8cd4b8fb4596fccec Mon Sep 17 00:00:00 2001 From: TeamDman Date: Thu, 11 Jul 2024 23:12:38 -0400 Subject: [PATCH 15/49] new simulation behaviour --- ...lExprStatementInspectionRequestPacket.java | 3 +- ...undIfStatementInspectionRequestPacket.java | 3 +- ...rverboundInputInspectionRequestPacket.java | 3 +- ...mulateExploreAllPathsProgramBehaviour.java | 168 ++++++++++++++++++ .../ca/teamdman/sfml/ast/TimerTrigger.java | 6 +- 5 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java diff --git a/src/main/java/ca/teamdman/sfm/common/net/ServerboundBoolExprStatementInspectionRequestPacket.java b/src/main/java/ca/teamdman/sfm/common/net/ServerboundBoolExprStatementInspectionRequestPacket.java index baf63b86f..e0ebc646e 100644 --- a/src/main/java/ca/teamdman/sfm/common/net/ServerboundBoolExprStatementInspectionRequestPacket.java +++ b/src/main/java/ca/teamdman/sfm/common/net/ServerboundBoolExprStatementInspectionRequestPacket.java @@ -3,6 +3,7 @@ import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; import ca.teamdman.sfm.common.containermenu.ManagerContainerMenu; import ca.teamdman.sfm.common.program.ProgramContext; +import ca.teamdman.sfm.common.program.SimulateExploreAllPathsProgramBehaviour; import ca.teamdman.sfm.common.registry.SFMPackets; import ca.teamdman.sfm.common.util.SFMUtils; import ca.teamdman.sfml.ast.BoolExpr; @@ -68,7 +69,7 @@ public static void handle( ProgramContext programContext = new ProgramContext( successProgram, manager, - ProgramContext.ExecutionPolicy.EXPLORE_BRANCHES + new SimulateExploreAllPathsProgramBehaviour() ); boolean result = expr.test(programContext); payload.append(result ? "TRUE" : "FALSE"); diff --git a/src/main/java/ca/teamdman/sfm/common/net/ServerboundIfStatementInspectionRequestPacket.java b/src/main/java/ca/teamdman/sfm/common/net/ServerboundIfStatementInspectionRequestPacket.java index eb29cefdb..1bd62cafc 100644 --- a/src/main/java/ca/teamdman/sfm/common/net/ServerboundIfStatementInspectionRequestPacket.java +++ b/src/main/java/ca/teamdman/sfm/common/net/ServerboundIfStatementInspectionRequestPacket.java @@ -3,6 +3,7 @@ import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; import ca.teamdman.sfm.common.containermenu.ManagerContainerMenu; import ca.teamdman.sfm.common.program.ProgramContext; +import ca.teamdman.sfm.common.program.SimulateExploreAllPathsProgramBehaviour; import ca.teamdman.sfm.common.registry.SFMPackets; import ca.teamdman.sfm.common.util.SFMUtils; import ca.teamdman.sfml.ast.IfStatement; @@ -68,7 +69,7 @@ public static void handle( ProgramContext programContext = new ProgramContext( successProgram, manager, - ProgramContext.ExecutionPolicy.EXPLORE_BRANCHES + new SimulateExploreAllPathsProgramBehaviour() ); boolean result = ifStatement.condition().test(programContext); payload.append(result ? "TRUE" : "FALSE"); diff --git a/src/main/java/ca/teamdman/sfm/common/net/ServerboundInputInspectionRequestPacket.java b/src/main/java/ca/teamdman/sfm/common/net/ServerboundInputInspectionRequestPacket.java index bd8f77aa4..43fe693ab 100644 --- a/src/main/java/ca/teamdman/sfm/common/net/ServerboundInputInspectionRequestPacket.java +++ b/src/main/java/ca/teamdman/sfm/common/net/ServerboundInputInspectionRequestPacket.java @@ -3,6 +3,7 @@ import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; import ca.teamdman.sfm.common.containermenu.ManagerContainerMenu; import ca.teamdman.sfm.common.program.ProgramContext; +import ca.teamdman.sfm.common.program.SimulateExploreAllPathsProgramBehaviour; import ca.teamdman.sfm.common.registry.SFMPackets; import ca.teamdman.sfm.common.util.SFMUtils; import ca.teamdman.sfml.ast.InputStatement; @@ -69,7 +70,7 @@ public static void handle( ProgramContext context = new ProgramContext( successProgram, manager, - ProgramContext.ExecutionPolicy.EXPLORE_BRANCHES + new SimulateExploreAllPathsProgramBehaviour() ); int preLen = payload.length(); inputStatement.gatherSlots( diff --git a/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java b/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java new file mode 100644 index 000000000..4631307e4 --- /dev/null +++ b/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java @@ -0,0 +1,168 @@ +package ca.teamdman.sfm.common.program; + +import ca.teamdman.sfm.common.resourcetype.ResourceType; +import ca.teamdman.sfml.ast.*; +import net.minecraft.network.chat.contents.TranslatableContents; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SimulateExploreAllPathsProgramBehaviour implements ProgramBehaviour { + protected List seenPaths = new ArrayList<>(); + protected ExecutionPath currentPath = new ExecutionPath(); + protected BigInteger triggerPathCount = BigInteger.ZERO; + + public SimulateExploreAllPathsProgramBehaviour() { + } + + public void terminatePathAndBeginAnew() { + seenPaths.add(currentPath); + currentPath = new ExecutionPath(); + triggerPathCount = triggerPathCount.add(BigInteger.ONE); + } + + public BigInteger getTriggerPathCount() { + return triggerPathCount; + } + + public void prepareNextTrigger() { + triggerPathCount = BigInteger.ZERO; + } + + public void pushPathElement(ExecutionPathElement statement) { + currentPath.history.add(statement); + } + + public void onOutputStatementExecution(OutputStatement outputStatement) { + pushPathElement(new SimulateExploreAllPathsProgramBehaviour.IO(outputStatement)); + } + + public void onInputStatementExecution(InputStatement inputStatement) { + pushPathElement(new SimulateExploreAllPathsProgramBehaviour.IO(inputStatement)); + } + + public void onInputStatementForgetTransform(InputStatement old, InputStatement next) { + } + + public void onInputStatementDropped(InputStatement inputStatement) { + } + + + public void onTriggerDropped(ProgramContext context) { + context.getInputs().forEach(this::onInputStatementDropped); + } + + @Override + public ProgramBehaviour fork() { + var copy = new SimulateExploreAllPathsProgramBehaviour(); + copy.seenPaths = this.seenPaths; // share the reference + copy.currentPath = this.currentPath.fork(); + return this; + } + + @Override + public void free() { + + } + + public ExecutionPath getCurrentPath() { + return currentPath; + } + + public List getSeenPaths() { + return seenPaths; + } + + public int[] getSeenIOStatementCountForEachPath() { + return seenPaths + .stream() + .mapToInt(path -> (int) path.history.stream().filter(IO.class::isInstance).count()) + .toArray(); + } + + public void onProgramFinished(Program program) { + + } + + + public enum IOKind { + INPUT, + OUTPUT + } + + public interface ExecutionPathElement { + } + + public record ExecutionPath( + List history + ) { + public ExecutionPath() { + this(new ArrayList<>()); + } + + public ExecutionPath fork() { + return new ExecutionPath(new ArrayList<>(history)); + } + + public Stream stream() { + return history.stream(); + } + + public Stream streamBranches() { + return history.stream().filter(Branch.class::isInstance).map(Branch.class::cast); + } + + public Stream streamInputs() { + return history + .stream() + .filter(IO.class::isInstance) + .map(IO.class::cast) + .filter(io -> io.kind == IOKind.INPUT); + } + + public Stream streamOutputs() { + return history + .stream() + .filter(IO.class::isInstance) + .map(IO.class::cast) + .filter(io -> io.kind == IOKind.OUTPUT); + } + } + + public record Branch( + IfStatement ifStatement, + boolean wasTrue + ) implements ExecutionPathElement { + } + + @SuppressWarnings("rawtypes") + public record IO( + IOKind kind, + Set usedResourceTypes, + Set

+ * This does not fork input statement state. + * @return shallow copy of this context + */ + public ProgramContext fork() { return new ProgramContext(this); } @@ -118,19 +132,16 @@ public void free() { for (int i = INPUTS.size() - 1; i >= 0; i--) { INPUTS.get(i).freeSlots(); } + getBehaviour().free(); } - public enum ExecutionPolicy { - EXPLORE_BRANCHES, - UNRESTRICTED - } public ManagerBlockEntity getManager() { return MANAGER; } public TranslatableLogger getLogger() { - return MANAGER.logger; + return LOGGER; } public void addInput(InputStatement input) { @@ -146,12 +157,6 @@ public CableNetwork getNetwork() { return NETWORK; } - public record Branch( - IfStatement ifStatement, - boolean wasTrue - ) { - } - @Override public String toString() { return "ProgramContext{" + @@ -160,11 +165,8 @@ public String toString() { ", NETWORK=" + NETWORK + ", INPUTS=" + INPUTS + ", LEVEL=" + LEVEL + - ", EXECUTION_POLICY=" + EXECUTION_POLICY + - ", PATH_TAKEN=" + PATH_TAKEN + - ", EXPLORATION_BRANCH_INDEX=" + EXPLORATION_BRANCH_INDEX + + ", EXECUTION_POLICY=" + BEHAVIOUR + ", REDSTONE_PULSES=" + REDSTONE_PULSES + - ", DISK_STACK=" + DISK_STACK + ", LABEL_POSITIONS=" + LABEL_POSITIONS + ", did_something=" + did_something + '}'; From 83f347469b53c464b2c094f9794f2f70ce1fefd6 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Thu, 11 Jul 2024 23:54:54 -0400 Subject: [PATCH 35/49] investigating forget statement --- .../ca/teamdman/sfml/ast/ForgetStatement.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java b/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java index 6de7cab7d..39634ddd5 100644 --- a/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java +++ b/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java @@ -2,7 +2,9 @@ import ca.teamdman.sfm.common.Constants; import ca.teamdman.sfm.common.program.ProgramContext; +import ca.teamdman.sfm.common.program.SimulateExploreAllPathsProgramBehaviour; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -13,6 +15,33 @@ public record ForgetStatement( ) implements Statement { @Override public void tick(ProgramContext context) { +// List newInputs = new ArrayList<>(); +// for (InputStatement oldInputStatement : context.getInputs()) { +// var newLabels = oldInputStatement.labelAccess().labels().stream() +// .filter(label -> !this.labels.contains(label)) +// .toList(); +// +// // always fire event from old to new, even if new has no labels +// InputStatement newInputStatement = new InputStatement( +// new LabelAccess( +// newLabels, +// oldInputStatement.labelAccess().directions(), +// oldInputStatement.labelAccess().slots(), +// oldInputStatement.labelAccess().roundRobin() +// ), +// oldInputStatement.resourceLimits(), +// oldInputStatement.each() +// ); +// if (context.getBehaviour() instanceof SimulateExploreAllPathsProgramBehaviour simulation) { +// simulation.onInputStatementForgetTransform(oldInputStatement, newInputStatement); +// oldInputStatement.transferSlotsTo(newInputStatement); +// } +// +// // include the empty inputs, ensuring they get properly freed when the input statement is dropped +// newInputs.add(newInputStatement); +// } + + // map-replace existing inputs with ones that exclude the union of the label access context.free(); var newInputs = context.getInputs() @@ -31,6 +60,7 @@ public void tick(ProgramContext context) { )) .filter(input -> !input.labelAccess().labels().isEmpty()) .toList(); + context.getInputs().clear(); context.getInputs().addAll(newInputs); context.getLogger().debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_FORGET_STATEMENT.get( From 9c8f259ee8b3b9162e364aa1cc47d9467ee3dbe9 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Fri, 12 Jul 2024 01:02:14 -0400 Subject: [PATCH 36/49] fix shared references, tests mostly passing --- .../teamdman/sfm/SFMCorrectnessGameTests.java | 31 +++++++++---- .../program/DefaultProgramBehaviour.java | 4 -- .../GatherWarningsProgramBehaviour.java | 43 +++++++++++++------ .../sfm/common/program/ProgramBehaviour.java | 1 - .../sfm/common/program/ProgramContext.java | 1 - ...mulateExploreAllPathsProgramBehaviour.java | 29 +++++++------ .../java/ca/teamdman/sfml/ast/Program.java | 2 +- 7 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java index 4ca1b600b..86c23178e 100644 --- a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java +++ b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java @@ -3124,7 +3124,7 @@ public static void count_execution_paths_conditional_1(GameTestHelper helper) { simulation )); - List expectedPathSizes = new ArrayList<>(List.of(2,3)); + List expectedPathSizes = new ArrayList<>(List.of(1,2)); assertTrue(simulation.getSeenPaths().size() == expectedPathSizes.size(), "expected " + expectedPathSizes.size() + " execution paths, got " + simulation.getSeenPaths().size()); int[] actualPathIOSizes = simulation.getSeenIOStatementCountForEachPath(); // don't assume the order, just that each path size has occurred the specified number of times @@ -3183,7 +3183,7 @@ public static void count_execution_paths_conditional_2(GameTestHelper helper) { 0, simulation )); - List expectedPathSizes = new ArrayList<>(List.of(3,4,4,5)); + List expectedPathSizes = new ArrayList<>(List.of(1,2,2,3)); assertTrue(simulation.getSeenPaths().size() == expectedPathSizes.size(), "expected " + expectedPathSizes.size() + " execution paths, got " + simulation.getSeenPaths().size()); int[] actualPathIOSizes = simulation.getSeenIOStatementCountForEachPath(); // don't assume the order, just that each path size has occurred the specified number of times @@ -3259,7 +3259,7 @@ public static void unused_io_warning_input_label_not_present_in_output(GameTestH assertTrue(warnings .get(0) .getKey() - .equals(Constants.LocalizationKeys.PROGRAM_WARNING_OUTPUT_RESOURCE_TYPE_NOT_FOUND_IN_INPUTS // should be unused input + .equals(Constants.LocalizationKeys.PROGRAM_WARNING_UNUSED_INPUT_LABEL // should be unused input .key() .get()), "expected output without matching input warning"); helper.succeed(); @@ -3292,8 +3292,8 @@ public static void conditional_output_inspection(GameTestHelper helper) { // set the program String code = """ EVERY 20 TICKS DO - IF a HAS > 0 dirt THEN - INPUT FROM a + IF a HAS = 64 dirt THEN + INPUT RETAIN 32 FROM a END OUTPUT TO b END @@ -3320,7 +3320,22 @@ public static void conditional_output_inspection(GameTestHelper helper) { //noinspection TrailingWhitespacesInTextBlock String expected = """ - todo + OUTPUT TO b + -- predictions may differ from actual execution results + -- POSSIBILITY 0 -- all false + OVERALL a HAS = 64 dirt -- false + + -- predicted inputs: + none + -- predicted outputs: + none + -- POSSIBILITY 1 -- all true + OVERALL a HAS = 64 dirt -- true + + -- predicted inputs: + INPUT 32 minecraft:dirt FROM a SLOTS 0 + -- predicted outputs: + OUTPUT 32 minecraft:dirt TO b """.stripLeading().stripIndent().stripTrailing(); if (!inspectionResults.equals(expected)) { System.out.println("Received results:"); @@ -3340,8 +3355,8 @@ public static void conditional_output_inspection(GameTestHelper helper) { } succeedIfManagerDidThingWithoutLagging(helper, manager, () -> { - assertTrue(leftChest.getStackInSlot(0).isEmpty(), "Dirt did not move"); - assertTrue(rightChest.getStackInSlot(0).getCount() == 64, "Dirt did not move"); + assertTrue(leftChest.getStackInSlot(0).getCount() == 32, "Dirt did not depart"); + assertTrue(rightChest.getStackInSlot(0).getCount() == 32, "Dirt did not arrive"); }); } } diff --git a/src/main/java/ca/teamdman/sfm/common/program/DefaultProgramBehaviour.java b/src/main/java/ca/teamdman/sfm/common/program/DefaultProgramBehaviour.java index 5a2873c87..ba590df0d 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/DefaultProgramBehaviour.java +++ b/src/main/java/ca/teamdman/sfm/common/program/DefaultProgramBehaviour.java @@ -6,8 +6,4 @@ public ProgramBehaviour fork() { return this; // this is stateless so this should be fine } - @Override - public void free() { - - } } diff --git a/src/main/java/ca/teamdman/sfm/common/program/GatherWarningsProgramBehaviour.java b/src/main/java/ca/teamdman/sfm/common/program/GatherWarningsProgramBehaviour.java index 323402452..2bd5aec6f 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/GatherWarningsProgramBehaviour.java +++ b/src/main/java/ca/teamdman/sfm/common/program/GatherWarningsProgramBehaviour.java @@ -7,7 +7,9 @@ import com.mojang.datafixers.util.Pair; import net.minecraft.network.chat.contents.TranslatableContents; +import java.math.BigInteger; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -26,19 +28,29 @@ public GatherWarningsProgramBehaviour(Consumer> this.sharedMultiverseWarningDisplay = sharedMultiverseWarningDisplay; this.sharedMultiverseWarningsByPath = new ArrayList<>(); } - public GatherWarningsProgramBehaviour(Consumer> sharedMultiverseWarningDisplay, List>> sharedMultiverseWarningsByPath) { + + public GatherWarningsProgramBehaviour( + List seenPaths, + ExecutionPath currentPath, + AtomicReference triggerPathCount, + Consumer> sharedMultiverseWarningDisplay, + List>> sharedMultiverseWarningsByPath, + List warnings + ) { + super(seenPaths, currentPath, triggerPathCount); + this.warnings.addAll(warnings); this.sharedMultiverseWarningDisplay = sharedMultiverseWarningDisplay; this.sharedMultiverseWarningsByPath = sharedMultiverseWarningsByPath; } + @Override public ProgramBehaviour fork() { - var copy = new GatherWarningsProgramBehaviour(sharedMultiverseWarningDisplay, sharedMultiverseWarningsByPath); - copy.warnings.addAll(this.warnings); - copy.seenPaths = this.seenPaths; // share the reference - copy.currentPath = this.currentPath.fork(); - copy.triggerPathCount = this.triggerPathCount; - return copy; + return new GatherWarningsProgramBehaviour( + this.seenPaths, this.currentPath, this.triggerPathCount, this.sharedMultiverseWarningDisplay, + this.sharedMultiverseWarningsByPath, + this.warnings + ); } @Override @@ -103,7 +115,7 @@ public void onInputStatementForgetTransform(InputStatement old, InputStatement n // if the label was never used, warn if (!resourceTypesOutputted.contains(resourceType)) { warnings.add(PROGRAM_WARNING_UNUSED_INPUT_LABEL.get( - old, resourceType.displayAsCode(), label,resourceType.displayAsCode())); + old, resourceType.displayAsCode(), label, resourceType.displayAsCode())); } // mark as no longer active resourceTypesInputted.remove(resourceType, label); @@ -112,9 +124,14 @@ public void onInputStatementForgetTransform(InputStatement old, InputStatement n } @Override - public void free() { + public void terminatePathAndBeginAnew() { // save the path and its warnings sharedMultiverseWarningsByPath.add(Pair.of(currentPath, new ArrayList<>(warnings))); + + // default path push and clear + super.terminatePathAndBeginAnew(); + + // clear warnings to start fresh on this new path warnings.clear(); } @@ -141,7 +158,7 @@ public void onInputStatementDropped(InputStatement inputStatement) { // if the label was never used, warn if (!resourceTypesOutputted.contains(resourceType)) { warnings.add(PROGRAM_WARNING_UNUSED_INPUT_LABEL.get( - inputStatement, resourceType.displayAsCode(), label,resourceType.displayAsCode())); + inputStatement, resourceType.displayAsCode(), label, resourceType.displayAsCode())); } // mark as no longer active resourceTypesInputted.remove(resourceType, label); @@ -165,9 +182,9 @@ public void onProgramFinished(Program program) { } // second pass - remove warning on miss -// for (var path: warningsByPath) { -// seen.retainAll(path.getSecond()); -// } + for (var path : sharedMultiverseWarningsByPath) { + seen.retainAll(path.getSecond()); + } // return true warnings sharedMultiverseWarningDisplay.accept(seen); diff --git a/src/main/java/ca/teamdman/sfm/common/program/ProgramBehaviour.java b/src/main/java/ca/teamdman/sfm/common/program/ProgramBehaviour.java index 7f6435647..417520476 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/ProgramBehaviour.java +++ b/src/main/java/ca/teamdman/sfm/common/program/ProgramBehaviour.java @@ -3,5 +3,4 @@ public interface ProgramBehaviour { ProgramBehaviour fork(); - void free(); } diff --git a/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java b/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java index cd127bfd0..750bcc65d 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java +++ b/src/main/java/ca/teamdman/sfm/common/program/ProgramContext.java @@ -132,7 +132,6 @@ public void free() { for (int i = INPUTS.size() - 1; i >= 0; i--) { INPUTS.get(i).freeSlots(); } - getBehaviour().free(); } diff --git a/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java b/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java index 4631307e4..bd2a4115f 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java +++ b/src/main/java/ca/teamdman/sfm/common/program/SimulateExploreAllPathsProgramBehaviour.java @@ -9,29 +9,40 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import java.util.stream.Stream; public class SimulateExploreAllPathsProgramBehaviour implements ProgramBehaviour { protected List seenPaths = new ArrayList<>(); protected ExecutionPath currentPath = new ExecutionPath(); - protected BigInteger triggerPathCount = BigInteger.ZERO; + protected AtomicReference triggerPathCount = new AtomicReference<>(BigInteger.ZERO); public SimulateExploreAllPathsProgramBehaviour() { } + public SimulateExploreAllPathsProgramBehaviour( + List seenPaths, + ExecutionPath currentPath, + AtomicReference triggerPathCount + ) { + this.seenPaths = seenPaths; + this.currentPath = currentPath.fork(); + this.triggerPathCount = triggerPathCount; + } + public void terminatePathAndBeginAnew() { seenPaths.add(currentPath); currentPath = new ExecutionPath(); - triggerPathCount = triggerPathCount.add(BigInteger.ONE); + triggerPathCount.set(triggerPathCount.get().add(BigInteger.ONE)); } public BigInteger getTriggerPathCount() { - return triggerPathCount; + return triggerPathCount.get(); } public void prepareNextTrigger() { - triggerPathCount = BigInteger.ZERO; + triggerPathCount.set(BigInteger.ZERO); } public void pushPathElement(ExecutionPathElement statement) { @@ -59,15 +70,7 @@ public void onTriggerDropped(ProgramContext context) { @Override public ProgramBehaviour fork() { - var copy = new SimulateExploreAllPathsProgramBehaviour(); - copy.seenPaths = this.seenPaths; // share the reference - copy.currentPath = this.currentPath.fork(); - return this; - } - - @Override - public void free() { - + return new SimulateExploreAllPathsProgramBehaviour(this.seenPaths, this.currentPath, this.triggerPathCount); } public ExecutionPath getCurrentPath() { diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index a79214e25..5f15016c5 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -160,7 +160,7 @@ public void tick(ProgramContext context) { ProgramContext forkedContext = context.fork(); trigger.tick(forkedContext); forkedContext.free(); - simulation.terminatePathAndBeginAnew(); + ((SimulateExploreAllPathsProgramBehaviour) forkedContext.getBehaviour()).terminatePathAndBeginAnew(); } simulation.prepareNextTrigger(); } else { From 8af797d79a25b639aae25fba50bd3de5bb342cc5 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Fri, 12 Jul 2024 01:03:47 -0400 Subject: [PATCH 37/49] add todo note --- src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java b/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java index 39634ddd5..0e5bd875b 100644 --- a/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java +++ b/src/main/java/ca/teamdman/sfml/ast/ForgetStatement.java @@ -3,6 +3,7 @@ import ca.teamdman.sfm.common.Constants; import ca.teamdman.sfm.common.program.ProgramContext; import ca.teamdman.sfm.common.program.SimulateExploreAllPathsProgramBehaviour; +import org.apache.commons.lang3.NotImplementedException; import java.util.ArrayList; import java.util.List; @@ -41,6 +42,13 @@ public void tick(ProgramContext context) { // newInputs.add(newInputStatement); // } + throw new NotImplementedException("todo"); + // add test for the free below causing problems + // INPUT 32 FROM a, b + // OUTPUT 1 TO c + // FORGET a + // OUTPUT TO d -- should only move 31, I predict will move 32 + // solve problems by transferring slots before destroying old // map-replace existing inputs with ones that exclude the union of the label access context.free(); From 10c2c4367609fd2fc25782fffb17b9ede4a63a2a Mon Sep 17 00:00:00 2001 From: TeamDman Date: Fri, 12 Jul 2024 01:05:05 -0400 Subject: [PATCH 38/49] add todo note for max state enumeration --- src/main/java/ca/teamdman/sfml/ast/Program.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index 5f15016c5..956c036e2 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -15,6 +15,7 @@ import net.minecraft.world.level.Level; import net.minecraftforge.fml.loading.FMLEnvironment; import org.antlr.v4.runtime.*; +import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -156,7 +157,9 @@ public void tick(ProgramContext context) { // Perform tick if (context.getBehaviour() instanceof SimulateExploreAllPathsProgramBehaviour simulation) { - for (int i = 0; i < Math.max(1, Math.pow(2,trigger.getConditionCount())); i++) { + int numPossibleStates = (int) Math.max(1, Math.pow(2, trigger.getConditionCount())); + throw new NotImplementedException("add common config for max limit before linting turns off"); + for (int i = 0; i < numPossibleStates; i++) { ProgramContext forkedContext = context.fork(); trigger.tick(forkedContext); forkedContext.free(); From f323a098a3abf2618ff4d962fad5317e7bf05e9c Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 15:23:07 -0400 Subject: [PATCH 39/49] add batch annotation for tests --- .../teamdman/sfm/SFMCorrectnessGameTests.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java index 86c23178e..980da7d9d 100644 --- a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java +++ b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java @@ -2871,7 +2871,7 @@ public static void multi_io_limits(GameTestHelper helper) { }); } - @GameTest(template = "3x4x3", batch = "laggy") + @GameTest(template = "3x4x3") public static void move_on_pulse(GameTestHelper helper) { var managerPos = new BlockPos(1, 2, 1); var buttonPos = managerPos.offset(Direction.NORTH.getNormal()); @@ -2922,7 +2922,7 @@ public static void move_on_pulse(GameTestHelper helper) { helper.pressButton(buttonPos); } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void count_execution_paths_1(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -2968,7 +2968,7 @@ public static void count_execution_paths_1(GameTestHelper helper) { helper.succeed(); } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void count_execution_paths_2(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -3020,7 +3020,7 @@ public static void count_execution_paths_2(GameTestHelper helper) { helper.succeed(); } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void count_execution_paths_3(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -3080,7 +3080,7 @@ public static void count_execution_paths_3(GameTestHelper helper) { } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void count_execution_paths_conditional_1(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -3136,7 +3136,7 @@ public static void count_execution_paths_conditional_1(GameTestHelper helper) { } helper.succeed(); } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void count_execution_paths_conditional_2(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -3196,7 +3196,7 @@ public static void count_execution_paths_conditional_2(GameTestHelper helper) { helper.succeed(); } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void unused_io_warning_output_label_not_presnet_in_input(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -3231,7 +3231,7 @@ public static void unused_io_warning_output_label_not_presnet_in_input(GameTestH } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void unused_io_warning_input_label_not_present_in_output(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); @@ -3266,7 +3266,7 @@ public static void unused_io_warning_input_label_not_present_in_output(GameTestH } - @GameTest(template = "3x2x1") + @GameTest(template = "3x2x1", batch="linting") public static void conditional_output_inspection(GameTestHelper helper) { helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); BlockPos rightPos = new BlockPos(0, 2, 0); From c6ad91184b75be32ba06dec3b061044193003511 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 15:23:53 -0400 Subject: [PATCH 40/49] add max depth config for simulation --- .../ca/teamdman/sfm/common/SFMConfig.java | 46 ++++--- .../java/ca/teamdman/sfml/ast/Program.java | 115 ++++++++++-------- 2 files changed, 92 insertions(+), 69 deletions(-) diff --git a/src/main/java/ca/teamdman/sfm/common/SFMConfig.java b/src/main/java/ca/teamdman/sfm/common/SFMConfig.java index 945e9be85..8ad33171f 100644 --- a/src/main/java/ca/teamdman/sfm/common/SFMConfig.java +++ b/src/main/java/ca/teamdman/sfm/common/SFMConfig.java @@ -10,6 +10,7 @@ public class SFMConfig { public static final ForgeConfigSpec CLIENT_SPEC; public static final SFMConfig.Common COMMON; public static final SFMConfig.Client CLIENT; + static { final Pair commonSpecPair = new ForgeConfigSpec.Builder().configure(SFMConfig.Common::new); COMMON_SPEC = commonSpecPair.getRight(); @@ -19,17 +20,44 @@ public class SFMConfig { CLIENT = clientSpecPair.getLeft(); } + /** + * Get a config value in a way that doesn't fail when running tests + */ + public static T getOrDefault(ForgeConfigSpec.ConfigValue configValue) { + try { + return configValue.get(); + } catch (Exception e) { + return configValue.getDefault(); + } + } + + public static void register(ModLoadingContext context) { + context.registerConfig(ModConfig.Type.COMMON, SFMConfig.COMMON_SPEC); + context.registerConfig(ModConfig.Type.CLIENT, SFMConfig.CLIENT_SPEC); + } + public static class Common { public final ForgeConfigSpec.IntValue timerTriggerMinimumIntervalInTicks; public final ForgeConfigSpec.IntValue timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIO; + public final ForgeConfigSpec.IntValue maxIfStatementsInTriggerBeforeSimulationIsntAllowed; Common(ForgeConfigSpec.Builder builder) { timerTriggerMinimumIntervalInTicks = builder .defineInRange("timerTriggerMinimumIntervalInTicks", 20, 1, Integer.MAX_VALUE); timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIO = builder - .defineInRange("timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIOStatementsPresent", 1, 1, Integer.MAX_VALUE); + .defineInRange( + "timerTriggerMinimumIntervalInTicksWhenOnlyForgeEnergyIOStatementsPresent", + 1, + 1, + Integer.MAX_VALUE + ); + maxIfStatementsInTriggerBeforeSimulationIsntAllowed = builder + .comment( + "The number of scenarios to check is 2^n where n is the number of if statements in a trigger") + .defineInRange("maxIfStatementsInTriggerBeforeSimulationIsntAllowed", 10, 0, Integer.MAX_VALUE); } } + public static class Client { public final ForgeConfigSpec.BooleanValue showLineNumbers; @@ -38,20 +66,4 @@ public static class Client { .define("showLineNumbers", false); } } - - /** - * Get a config value in a way that doesn't fail when running tests - */ - public static T getOrDefault(ForgeConfigSpec.ConfigValue configValue) { - try { - return configValue.get(); - } catch (Exception e) { - return configValue.getDefault(); - } - } - - public static void register(ModLoadingContext context) { - context.registerConfig(ModConfig.Type.COMMON, SFMConfig.COMMON_SPEC); - context.registerConfig(ModConfig.Type.CLIENT, SFMConfig.CLIENT_SPEC); - } } diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index 956c036e2..2ee576da8 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -2,6 +2,7 @@ import ca.teamdman.sfm.SFM; import ca.teamdman.sfm.common.Constants; +import ca.teamdman.sfm.common.SFMConfig; import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; import ca.teamdman.sfm.common.program.DefaultProgramBehaviour; import ca.teamdman.sfm.common.program.ProgramContext; @@ -15,7 +16,6 @@ import net.minecraft.world.level.Level; import net.minecraftforge.fml.loading.FMLEnvironment; import org.antlr.v4.runtime.*; -import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -71,14 +71,19 @@ public static void compile( referencedResource)); } } catch (ResourceLocationException e) { - errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_MALFORMED_RESOURCE_TYPE.get(referencedResource)); + errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_MALFORMED_RESOURCE_TYPE.get( + referencedResource)); } } } catch (ResourceLocationException | IllegalArgumentException | AssertionError e) { errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_LITERAL.get(e.getMessage())); } catch (Throwable t) { errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); - SFM.LOGGER.error("Encountered unhandled error \"{}\" while compiling program\n```\n{}\n```", t, programString); + SFM.LOGGER.error( + "Encountered unhandled error \"{}\" while compiling program\n```\n{}\n```", + t, + programString + ); if (!FMLEnvironment.production) { var message = t.getMessage(); if (message != null) { @@ -92,7 +97,10 @@ public static void compile( if (program == null && errors.isEmpty()) { errors.add(Constants.LocalizationKeys.PROGRAM_ERROR_COMPILE_FAILED.get()); - SFM.LOGGER.error("Program was somehow null after a successful compile. I have no idea how this could happen, but it definitely shouldn't.\n```\n{}\n```", programString); + SFM.LOGGER.error( + "Program was somehow null after a successful compile. I have no idea how this could happen, but it definitely shouldn't.\n```\n{}\n```", + programString + ); } if (errors.isEmpty()) { @@ -148,8 +156,10 @@ public void tick(ProgramContext context) { // Log pretty triggers if (triggers instanceof ShortStatement ss) { - context.getLogger().debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_TRIGGER_STATEMENT.get( - ss.toStringShort()))); + context + .getLogger() + .debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_TRIGGER_STATEMENT.get( + ss.toStringShort()))); } // Start stopwatch @@ -157,8 +167,9 @@ public void tick(ProgramContext context) { // Perform tick if (context.getBehaviour() instanceof SimulateExploreAllPathsProgramBehaviour simulation) { - int numPossibleStates = (int) Math.max(1, Math.pow(2, trigger.getConditionCount())); - throw new NotImplementedException("add common config for max limit before linting turns off"); + int maxConditionCount = SFMConfig.getOrDefault(SFMConfig.COMMON.maxIfStatementsInTriggerBeforeSimulationIsntAllowed); + int conditionCount = Math.min(trigger.getConditionCount(), maxConditionCount); + int numPossibleStates = (int) Math.max(1, Math.pow(2, conditionCount)); for (int i = 0; i < numPossibleStates; i++) { ProgramContext forkedContext = context.fork(); trigger.tick(forkedContext); @@ -197,6 +208,50 @@ public int getConditionIndex(IfStatement ifStatement) { return -1; } + @Override + public String toString() { + var rtn = new StringBuilder(); + rtn.append("NAME \"").append(name).append("\"\n"); + for (Trigger trigger : triggers) { + rtn.append(trigger).append("\n"); + } + return rtn.toString(); + } + + public void replaceOutputStatement(OutputStatement oldStatement, OutputStatement newStatement) { + Deque toPatch = new ArrayDeque<>(); + toPatch.add(this); + while (!toPatch.isEmpty()) { + Statement statement = toPatch.pollFirst(); + List children = statement.getStatements(); + for (int i = 0; i < children.size(); i++) { + Statement child = children.get(i); + if (child == oldStatement) { + children.set(i, newStatement); + } else { + toPatch.add(child); + } + } + } + } + + public void replaceAllOutputStatements(Function mapper) { + Deque toPatch = new ArrayDeque<>(); + toPatch.add(this); + while (!toPatch.isEmpty()) { + Statement statement = toPatch.pollFirst(); + List children = statement.getStatements(); + for (int i = 0; i < children.size(); i++) { + Statement child = children.get(i); + if (child instanceof OutputStatement outputStatement) { + children.set(i, mapper.apply(outputStatement)); + } else { + toPatch.add(child); + } + } + } + } + private static @NotNull Consumer> getTraceLogWriter(ProgramContext context) { return trace -> { trace.accept(Constants.LocalizationKeys.LOG_CABLE_NETWORK_DETAILS_HEADER_1.get()); @@ -252,50 +307,6 @@ public int getConditionIndex(IfStatement ifStatement) { }; } - @Override - public String toString() { - var rtn = new StringBuilder(); - rtn.append("NAME \"").append(name).append("\"\n"); - for (Trigger trigger : triggers) { - rtn.append(trigger).append("\n"); - } - return rtn.toString(); - } - - public void replaceOutputStatement(OutputStatement oldStatement, OutputStatement newStatement) { - Deque toPatch = new ArrayDeque<>(); - toPatch.add(this); - while (!toPatch.isEmpty()) { - Statement statement = toPatch.pollFirst(); - List children = statement.getStatements(); - for (int i = 0; i < children.size(); i++) { - Statement child = children.get(i); - if (child == oldStatement) { - children.set(i, newStatement); - } else { - toPatch.add(child); - } - } - } - } - - public void replaceAllOutputStatements(Function mapper) { - Deque toPatch = new ArrayDeque<>(); - toPatch.add(this); - while (!toPatch.isEmpty()) { - Statement statement = toPatch.pollFirst(); - List children = statement.getStatements(); - for (int i = 0; i < children.size(); i++) { - Statement child = children.get(i); - if (child instanceof OutputStatement outputStatement) { - children.set(i, mapper.apply(outputStatement)); - } else { - toPatch.add(child); - } - } - } - } - public static class ListErrorListener extends BaseErrorListener { private final List errors; From 80efc30e0aebe64eb3001225c87b3ad68afe672a Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 15:42:24 -0400 Subject: [PATCH 41/49] add failing test for FORGET count state resetting --- .../teamdman/sfm/SFMCorrectnessGameTests.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java index 980da7d9d..ab86c9d7e 100644 --- a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java +++ b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java @@ -2468,6 +2468,44 @@ public static void forget_slot(GameTestHelper helper) { } + @GameTest(template = "3x2x1") + public static void forget_input_count_state(GameTestHelper helper) { + helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); + BlockPos rightPos = new BlockPos(0, 2, 0); + helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + BlockPos leftPos = new BlockPos(2, 2, 0); + helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + + var rightChest = getItemHandler(helper, rightPos); + var leftChest = getItemHandler(helper, leftPos); + + leftChest.insertItem(0, new ItemStack(Blocks.DIRT, 64), false); + + ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0)); + manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get())); + manager.setProgram(""" + EVERY 20 TICKS DO + INPUT 10 FROM a,b + OUTPUT 1 to z + FORGET b + OUTPUT to z + END + """.stripTrailing().stripIndent()); + + // set the labels + LabelPositionHolder.empty() + .add("a", helper.absolutePos(leftPos)) + .add("b", helper.absolutePos(leftPos)) + .add("z", helper.absolutePos(rightPos)) + .save(manager.getDisk().get()); + + succeedIfManagerDidThingWithoutLagging(helper, manager, () -> { + assertTrue(leftChest.getStackInSlot(0).getCount() == 64-10, "did not remain"); + assertTrue(rightChest.getStackInSlot(0).getCount() == 10, "did not arrive"); + helper.succeed(); + }); + } + @GameTest(template = "3x2x1") public static void reorder_1(GameTestHelper helper) { helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); From f255fe2fa4ca6aa0c00f8ad5a70d3b7c448aee9a Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 17:30:19 -0400 Subject: [PATCH 42/49] add probably failing test for conditional unused IO --- .../teamdman/sfm/SFMCorrectnessGameTests.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java index ab86c9d7e..e2422e11c 100644 --- a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java +++ b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java @@ -3175,6 +3175,47 @@ public static void count_execution_paths_conditional_1(GameTestHelper helper) { helper.succeed(); } @GameTest(template = "3x2x1", batch="linting") + public static void count_execution_paths_conditional_1b(GameTestHelper helper) { + // place inventories + helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); + BlockPos rightPos = new BlockPos(0, 2, 0); + helper.setBlock(rightPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + BlockPos leftPos = new BlockPos(2, 2, 0); + helper.setBlock(leftPos, SFMBlocks.TEST_BARREL_BLOCK.get()); + + // place manager + ManagerBlockEntity manager = (ManagerBlockEntity) helper.getBlockEntity(new BlockPos(1, 2, 0)); + manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get())); + + // set the labels + LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty() + .add("left", helper.absolutePos(leftPos)) + .add("right", helper.absolutePos(rightPos)) + .save(manager.getDisk().get()); + + // load the program + manager.setProgram(""" + EVERY 20 TICKS DO + IF left HAS gt 0 stone THEN + INPUT FROM left + END + END + """.stripTrailing().stripIndent()); + assertManagerRunning(manager); + var program = manager.getProgram().get(); + + // ensure no warnings + var warnings = DiskItem.getWarnings(manager.getDisk().get()); + assertTrue(warnings.size() == 1, "expected 1 warning, got " + warnings.size()); + assertTrue(warnings + .get(0) + .getKey() + .equals(Constants.LocalizationKeys.PROGRAM_WARNING_UNUSED_INPUT_LABEL // should be unused input + .key() + .get()), "expected output without matching input warning"); + helper.succeed(); + } + @GameTest(template = "3x2x1", batch="linting") public static void count_execution_paths_conditional_2(GameTestHelper helper) { // place inventories helper.setBlock(new BlockPos(1, 2, 0), SFMBlocks.MANAGER_BLOCK.get()); From 7cfaced1242345056b649e0a149efc4164dd3eb0 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 17:30:31 -0400 Subject: [PATCH 43/49] remove unused release method for input slots --- .../common/program/LimitedInputSlotObjectPool.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java index 3d584971b..6a3adfffc 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java @@ -39,17 +39,6 @@ public LimitedInputSlot acquire( } } - /** - * Release a {@link LimitedInputSlot} back into the pool for it to be reused instead of garbage collected - */ - public void release(LimitedInputSlot obj) { - if (index == pool.length - 1) { - // we need to grow the array - pool = Arrays.copyOf(pool, pool.length * 2); - } - pool[++index] = obj; - } - /** * Release a {@link LimitedInputSlot} back into the pool for it to be reused instead of garbage collected *

From 0a1cdacd44875cb037cb0e92907cdc492510a827 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 18:46:17 -0400 Subject: [PATCH 44/49] switch to using `freed` obj property instead of old check for use-after-free involving obj pools --- .../sfm/common/program/LimitedInputSlot.java | 2 + .../program/LimitedInputSlotObjectPool.java | 66 +++++++++++++------ .../sfm/common/program/LimitedOutputSlot.java | 2 + .../program/LimitedOutputSlotObjectPool.java | 60 ++++++++++------- .../ca/teamdman/sfml/ast/InputStatement.java | 15 +---- .../ca/teamdman/sfml/ast/OutputStatement.java | 13 ++-- .../java/ca/teamdman/sfml/ast/Program.java | 10 ++- 7 files changed, 102 insertions(+), 66 deletions(-) diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java index 13aa8fdf6..5bd176866 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java @@ -10,6 +10,7 @@ public class LimitedInputSlot { @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public CAP handler; public int slot; + public boolean freed; @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public InputResourceTracker tracker; private @Nullable STACK extractSimulateCache = null; @@ -66,6 +67,7 @@ public void init(CAP handler, int slot, InputResourceTracker t this.handler = handler; this.tracker = tracker; this.slot = slot; + this.freed = false; //noinspection DataFlowIssue this.type = tracker.getResourceLimit().resourceId().getResourceType(); if (type == null) { diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java index 6a3adfffc..218897d98 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java @@ -4,48 +4,70 @@ import java.util.Arrays; import java.util.Collection; +import java.util.IdentityHashMap; /** * A pool of {@link LimitedInputSlot} objects to avoid the garbage collector - *

- * This assumes that the pool will be used in a single thread. */ public class LimitedInputSlotObjectPool { - public static final LimitedInputSlotObjectPool INSTANCE = new LimitedInputSlotObjectPool(); + public static final IdentityHashMap, Boolean> LEASED = new IdentityHashMap<>(); @SuppressWarnings("rawtypes") - private LimitedInputSlot[] pool = new LimitedInputSlot[1]; - private int index = -1; - - public int getIndex() { - return index; - } + private static LimitedInputSlot[] pool = new LimitedInputSlot[27]; + private static int index = -1; /** * Acquire a {@link LimitedInputSlot} from the pool, or creates a new one if none available */ - public LimitedInputSlot acquire( + public static LimitedInputSlot acquire( CAP handler, int slot, InputResourceTracker tracker, STACK stack ) { if (index == -1) { - return new LimitedInputSlot<>(handler, slot, tracker, stack); + var rtn = new LimitedInputSlot<>( + handler, + slot, + tracker, + stack + ); + LEASED.put(rtn, true); + return rtn; } else { @SuppressWarnings("unchecked") LimitedInputSlot obj = pool[index]; index--; obj.init(handler, slot, tracker, stack); + LEASED.put(obj, true); return obj; } } + /** + * Release a {@link LimitedInputSlot} back into the pool for it to be reused instead of garbage collected + */ + public static void release(LimitedInputSlot slot) { + if (slot.freed) { + SFM.LOGGER.warn("Release called on already freed input slot {}", slot); + return; + } + slot.freed = true; + if (LEASED.remove(slot) == null) { + SFM.LOGGER.warn("Freed an input slot that wasn't tracked as leased: {}", slot); + } + if (index == pool.length - 1) { + // we need to grow the array + pool = Arrays.copyOf(pool, pool.length * 2); + } + pool[++index] = slot; + } + /** * Release a {@link LimitedInputSlot} back into the pool for it to be reused instead of garbage collected *

* After acquiring slots, the end the index after release should be {@code check + slots.size()} */ @SuppressWarnings("rawtypes") - public void release(Collection> slots, int check) { + public static void release(Collection> slots) { // handle resizing if (index + slots.size() >= pool.length) { int slotsFree = pool.length - index - 1; @@ -54,18 +76,22 @@ public void release(Collection> slots, int check) { } // add to pool for (LimitedInputSlot slot : slots) { + if (slot.freed) { + SFM.LOGGER.warn("Release batch called on already freed input slot {}", slot); + continue; + } + slot.freed = true; index++; pool[index] = slot; + if (LEASED.remove(slot) == null) { + SFM.LOGGER.warn("Freed in batch an object that wasn't tracked as leased: {}", slot); + } } - // assert - if (index != check + slots.size()) { - SFM.LOGGER.warn( - "Index mismatch after releasing input slots, got {} expected {}", - index, - check + slots.size() - ); + } -// throw new IllegalStateException("Index mismatch after releasing slots, got " + index + " expected " + (check + slots.size())); + public static void checkInvariant() { + if (!LEASED.isEmpty()) { + SFM.LOGGER.warn("Leased objects not released: {}", LEASED); } } } diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java index b9cb3d447..95ca56b8a 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java @@ -10,6 +10,7 @@ public class LimitedOutputSlot { @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public CAP handler; public int slot; + public boolean freed; @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public OutputResourceTracker tracker; private @Nullable STACK stackInSlotCache = null; @@ -55,6 +56,7 @@ public void init(CAP handler, int slot, OutputResourceTracker this.handler = handler; this.tracker = tracker; this.slot = slot; + this.freed = false; //noinspection DataFlowIssue this.type = tracker.getLimit().resourceId().getResourceType(); if (type == null) { diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java index 619620c2c..7348e3a4a 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java @@ -4,37 +4,40 @@ import java.util.Arrays; import java.util.Collection; +import java.util.IdentityHashMap; /** * A pool of {@link LimitedOutputSlot} objects to avoid the garbage collector - *

- * This assumes that the pool will be used in a single thread. */ public class LimitedOutputSlotObjectPool { - public static final LimitedOutputSlotObjectPool INSTANCE = new LimitedOutputSlotObjectPool(); + public static final IdentityHashMap, Boolean> LEASED = new IdentityHashMap<>(); @SuppressWarnings("rawtypes") - private LimitedOutputSlot[] pool = new LimitedOutputSlot[1]; - private int index = -1; - - public int getIndex() { - return index; - } + private static LimitedOutputSlot[] pool = new LimitedOutputSlot[27]; + private static int index = -1; /** * Acquire a {@link LimitedOutputSlot} from the pool, or creates a new one if none available */ - public LimitedOutputSlot acquire( + public static LimitedOutputSlot acquire( CAP handler, int slot, OutputResourceTracker tracker, STACK stack ) { if (index == -1) { - return new LimitedOutputSlot<>(handler, slot, tracker, stack); + var rtn = new LimitedOutputSlot<>( + handler, + slot, + tracker, + stack + ); + LEASED.put(rtn, true); + return rtn; } else { @SuppressWarnings("unchecked") LimitedOutputSlot obj = pool[index]; index--; obj.init(handler, slot, tracker, stack); + LEASED.put(obj, true); return obj; } } @@ -42,12 +45,20 @@ public LimitedOutputSlot acquire( /** * Release a {@link LimitedOutputSlot} back into the pool for it to be reused instead of garbage collected */ - public void release(LimitedOutputSlot obj) { + public static void release(LimitedOutputSlot slot) { + if (slot.freed) { + SFM.LOGGER.warn("Release called on already freed output slot {}", slot); + return; + } + slot.freed = true; + if (LEASED.remove(slot) == null) { + SFM.LOGGER.warn("Freed an output slot that wasn't tracked as leased: {}", slot); + } if (index == pool.length - 1) { // we need to grow the array pool = Arrays.copyOf(pool, pool.length * 2); } - pool[++index] = obj; + pool[++index] = slot; } /** @@ -56,7 +67,7 @@ public void release(LimitedOutputSlot obj) { * After acquiring slots, the end the index after release should be {@code check + slots.size()} */ @SuppressWarnings("rawtypes") - public void release(Collection slots, int check) { + public static void release(Collection slots) { // handle resizing if (index + slots.size() >= pool.length) { int slotsFree = pool.length - index - 1; @@ -65,17 +76,22 @@ public void release(Collection slots, int check) { } // add to pool for (LimitedOutputSlot slot : slots) { + if (slot.freed) { + SFM.LOGGER.warn("Release batch called on already freed output slot {}", slot); + continue; + } + slot.freed = true; index++; pool[index] = slot; + if (LEASED.remove(slot) == null) { + SFM.LOGGER.warn("Freed in batch an output slot that wasn't tracked as leased: {}", slot); + } } - // assert - if (index != check + slots.size()) { - SFM.LOGGER.warn( - "Index mismatch after releasing output slots, got {} expected {}", - index, - check + slots.size() - ); -// throw new IllegalStateException("Index mismatch after releasing slots, got " + index + " expected " + (check + slots.size())); + } + + public static void checkInvariant() { + if (!LEASED.isEmpty()) { + SFM.LOGGER.warn("Leased objects not released: {}", LEASED); } } } diff --git a/src/main/java/ca/teamdman/sfml/ast/InputStatement.java b/src/main/java/ca/teamdman/sfml/ast/InputStatement.java index e5484dcd5..7ecfda60f 100644 --- a/src/main/java/ca/teamdman/sfml/ast/InputStatement.java +++ b/src/main/java/ca/teamdman/sfml/ast/InputStatement.java @@ -128,8 +128,6 @@ public void gatherSlots(ProgramContext context, Consumer void gatherSlots( @@ -249,7 +240,7 @@ private void gatherSlots( .debug(x -> x.accept(Constants.LocalizationKeys.LOG_PROGRAM_TICK_IO_STATEMENT_GATHER_SLOTS_SLOT_CREATED.get( finalSlot, stack, tracker.toString()))); //noinspection unchecked - acceptor.accept(LimitedInputSlotObjectPool.INSTANCE.acquire( + acceptor.accept(LimitedInputSlotObjectPool.acquire( capability, slot, (InputResourceTracker) tracker, diff --git a/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java b/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java index 3579b319f..3d6b0ed9d 100644 --- a/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java +++ b/src/main/java/ca/teamdman/sfml/ast/OutputStatement.java @@ -252,9 +252,6 @@ public void tick(ProgramContext context) { // Update allocation hint lastOutputCapacity = outputSlots.size(); - // Get assertion hint - int outputCheck = LimitedOutputSlotObjectPool.INSTANCE.getIndex(); - // Log the number of output slots context .getLogger() @@ -270,7 +267,7 @@ public void tick(ProgramContext context) { .debug(x -> x.accept(LocalizationKeys.LOG_PROGRAM_TICK_OUTPUT_STATEMENT_SHORT_CIRCUIT_NO_OUTPUT_SLOTS.get())); // Free the output slots (we acquired no slots but the assertion is still valid) - LimitedOutputSlotObjectPool.INSTANCE.release(outputSlots, outputCheck); + LimitedOutputSlotObjectPool.release(outputSlots); // Stop processing return; @@ -297,9 +294,7 @@ public void tick(ProgramContext context) { // Make sure we don't process this slot again outputSlotIter.remove(); // IMPORTANT!!!!! DONT FREE SLOTS TWICE WHEN FREEING REMAINDER BELOW // Release it - LimitedOutputSlotObjectPool.INSTANCE.release(outputSlot); - // Update the output check - outputCheck++; + LimitedOutputSlotObjectPool.release(outputSlot); // Try again continue; } @@ -321,7 +316,7 @@ public void tick(ProgramContext context) { ################ */ // Release remaining slot objects - LimitedOutputSlotObjectPool.INSTANCE.release(outputSlots, outputCheck); + LimitedOutputSlotObjectPool.release(outputSlots); } /** @@ -459,7 +454,7 @@ private void gatherSlots( .debug(x -> x.accept(LocalizationKeys.LOG_PROGRAM_TICK_IO_STATEMENT_GATHER_SLOTS_SLOT_CREATED.get( finalSlot, stack, tracker.toString()))); //noinspection unchecked - acceptor.accept(LimitedOutputSlotObjectPool.INSTANCE.acquire( + acceptor.accept(LimitedOutputSlotObjectPool.acquire( capability, slot, (OutputResourceTracker) tracker, diff --git a/src/main/java/ca/teamdman/sfml/ast/Program.java b/src/main/java/ca/teamdman/sfml/ast/Program.java index 2ee576da8..7caaddf05 100644 --- a/src/main/java/ca/teamdman/sfml/ast/Program.java +++ b/src/main/java/ca/teamdman/sfml/ast/Program.java @@ -4,9 +4,7 @@ import ca.teamdman.sfm.common.Constants; import ca.teamdman.sfm.common.SFMConfig; import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity; -import ca.teamdman.sfm.common.program.DefaultProgramBehaviour; -import ca.teamdman.sfm.common.program.ProgramContext; -import ca.teamdman.sfm.common.program.SimulateExploreAllPathsProgramBehaviour; +import ca.teamdman.sfm.common.program.*; import ca.teamdman.sfm.common.resourcetype.ResourceType; import ca.teamdman.sfm.common.util.SFMUtils; import ca.teamdman.sfml.SFMLLexer; @@ -141,6 +139,9 @@ public List getStatements() { @Override public void tick(ProgramContext context) { + LimitedInputSlotObjectPool.checkInvariant(); + LimitedOutputSlotObjectPool.checkInvariant(); + for (Trigger trigger : triggers) { // Only process triggers that should tick if (!trigger.shouldTick(context)) { @@ -193,6 +194,9 @@ public void tick(ProgramContext context) { ))); } + LimitedInputSlotObjectPool.checkInvariant(); + LimitedOutputSlotObjectPool.checkInvariant(); + if (context.getBehaviour() instanceof SimulateExploreAllPathsProgramBehaviour simulation) { simulation.onProgramFinished(this); } From 834b22e16ed60c284753c1fb57804405d5ce6579 Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sat, 13 Jul 2024 18:48:18 -0400 Subject: [PATCH 45/49] cleanup unused field and fix test inaccuracies --- .../java/ca/teamdman/sfm/SFMCorrectnessGameTests.java | 6 ++---- src/main/java/ca/teamdman/sfml/ast/InputStatement.java | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java index e2422e11c..7f8f9e219 100644 --- a/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java +++ b/src/gametest/java/ca/teamdman/sfm/SFMCorrectnessGameTests.java @@ -3188,9 +3188,8 @@ public static void count_execution_paths_conditional_1b(GameTestHelper helper) { manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get())); // set the labels - LabelPositionHolder labelPositionHolder = LabelPositionHolder.empty() + LabelPositionHolder.empty() .add("left", helper.absolutePos(leftPos)) - .add("right", helper.absolutePos(rightPos)) .save(manager.getDisk().get()); // load the program @@ -3202,9 +3201,8 @@ public static void count_execution_paths_conditional_1b(GameTestHelper helper) { END """.stripTrailing().stripIndent()); assertManagerRunning(manager); - var program = manager.getProgram().get(); - // ensure no warnings + // assert expected warnings var warnings = DiskItem.getWarnings(manager.getDisk().get()); assertTrue(warnings.size() == 1, "expected 1 warning, got " + warnings.size()); assertTrue(warnings diff --git a/src/main/java/ca/teamdman/sfml/ast/InputStatement.java b/src/main/java/ca/teamdman/sfml/ast/InputStatement.java index 7ecfda60f..925ee48cd 100644 --- a/src/main/java/ca/teamdman/sfml/ast/InputStatement.java +++ b/src/main/java/ca/teamdman/sfml/ast/InputStatement.java @@ -18,7 +18,6 @@ public final class InputStatement implements IOStatement { private final ResourceLimits resourceLimits; private final boolean each; private @Nullable ArrayDeque> limitedInputSlotsCache = null; - private int inputCheck = -2; public InputStatement( LabelAccess labelAccess, From fa69cb5484ee1b52a7ae03dab0ed0ce77db100bb Mon Sep 17 00:00:00 2001 From: TeamDman Date: Sun, 14 Jul 2024 01:04:12 -0400 Subject: [PATCH 46/49] limitedslot now tracks label,pos,dir partial resets now properly keeping tracker count --- .../common/program/CapabilityConsumer.java | 15 ++ .../sfm/common/program/LimitedInputSlot.java | 48 ++++- .../program/LimitedInputSlotObjectPool.java | 26 ++- .../sfm/common/program/LimitedOutputSlot.java | 53 +++++- .../program/LimitedOutputSlotObjectPool.java | 26 ++- .../sfm/common/resourcetype/ResourceType.java | 135 +++++++++---- .../java/ca/teamdman/sfml/ast/ASTBuilder.java | 6 +- .../ca/teamdman/sfml/ast/ForgetStatement.java | 91 ++++----- .../ca/teamdman/sfml/ast/InputStatement.java | 172 +++++++++-------- .../ca/teamdman/sfml/ast/OutputStatement.java | 177 ++++++++++-------- .../teamdman/sfml/ast/ResourceComparer.java | 24 ++- .../java/ca/teamdman/sfml/ast/RoundRobin.java | 39 +--- .../sfm/template_programs/changelog.sfml | 14 ++ 13 files changed, 494 insertions(+), 332 deletions(-) create mode 100644 src/main/java/ca/teamdman/sfm/common/program/CapabilityConsumer.java diff --git a/src/main/java/ca/teamdman/sfm/common/program/CapabilityConsumer.java b/src/main/java/ca/teamdman/sfm/common/program/CapabilityConsumer.java new file mode 100644 index 000000000..d34272c3c --- /dev/null +++ b/src/main/java/ca/teamdman/sfm/common/program/CapabilityConsumer.java @@ -0,0 +1,15 @@ +package ca.teamdman.sfm.common.program; + +import ca.teamdman.sfml.ast.Label; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; + +@FunctionalInterface +public interface CapabilityConsumer { + void accept( + Label label, + BlockPos pos, + Direction direction, + T cap + ); +} diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java index 5bd176866..2690afe7d 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlot.java @@ -1,6 +1,9 @@ package ca.teamdman.sfm.common.program; import ca.teamdman.sfm.common.resourcetype.ResourceType; +import ca.teamdman.sfml.ast.Label; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import javax.annotation.Nullable; @@ -9,6 +12,12 @@ public class LimitedInputSlot { public ResourceType type; @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public CAP handler; + @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor + public BlockPos pos; + @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor + public Label label; + @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor + public Direction direction; public int slot; public boolean freed; @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor @@ -17,9 +26,15 @@ public class LimitedInputSlot { private boolean done = false; public LimitedInputSlot( - CAP handler, int slot, InputResourceTracker tracker, STACK stack + Label label, + BlockPos pos, + Direction direction, + int slot, + CAP handler, + InputResourceTracker tracker, + STACK stack ) { - this.init(handler, slot, tracker, stack); + this.init(handler, label, pos, direction, slot, tracker, stack); } public boolean isDone() { @@ -61,13 +76,26 @@ public STACK peekExtractPotential() { return extractSimulateCache; } - public void init(CAP handler, int slot, InputResourceTracker tracker, STACK stack) { + public void init( + CAP handler, + Label label, + BlockPos pos, + Direction direction, + int slot, + InputResourceTracker tracker, + STACK stack + ) { this.done = false; this.extractSimulateCache = stack; + this.handler = handler; this.tracker = tracker; this.slot = slot; + this.pos = pos; + this.label = label; + this.direction = direction; this.freed = false; + //noinspection DataFlowIssue this.type = tracker.getResourceLimit().resourceId().getResourceType(); if (type == null) { @@ -77,11 +105,13 @@ public void init(CAP handler, int slot, InputResourceTracker t @Override public String toString() { - return "LimitedInputSlot{" + - "slot=" + slot + - ", cap=" + type.displayAsCapabilityClass() + -// ", extractSimulateCache=" + extractSimulateCache + - ", tracker=" + tracker + - '}'; + return "LimitedInputSlot{" + + "label=" + label + + ", pos=" + pos + + ", direction=" + direction + + ", slot=" + slot + + ", cap=" + type.displayAsCapabilityClass() + + ", tracker=" + tracker + + '}'; } } diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java index 218897d98..6830199c1 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedInputSlotObjectPool.java @@ -1,6 +1,9 @@ package ca.teamdman.sfm.common.program; import ca.teamdman.sfm.SFM; +import ca.teamdman.sfml.ast.Label; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import java.util.Arrays; import java.util.Collection; @@ -19,25 +22,27 @@ public class LimitedInputSlotObjectPool { * Acquire a {@link LimitedInputSlot} from the pool, or creates a new one if none available */ public static LimitedInputSlot acquire( - CAP handler, + Label label, + BlockPos pos, + Direction direction, int slot, + CAP handler, InputResourceTracker tracker, STACK stack ) { if (index == -1) { - var rtn = new LimitedInputSlot<>( - handler, - slot, - tracker, - stack - ); - LEASED.put(rtn, true); + var rtn = new LimitedInputSlot<>(label, pos, direction, slot, handler, tracker, stack); + if (LEASED.put(rtn, true) != null) { + SFM.LOGGER.warn("new input slot was somehow already leased, this should literally never happen: {}", rtn); + }; return rtn; } else { @SuppressWarnings("unchecked") LimitedInputSlot obj = pool[index]; index--; - obj.init(handler, slot, tracker, stack); - LEASED.put(obj, true); + obj.init(handler, label, pos, direction, slot, tracker, stack); + if (LEASED.put(obj, true) != null) { + SFM.LOGGER.warn("tried to lease input slot a second time: {}", obj); + }; return obj; } } @@ -92,6 +97,7 @@ public static void release(Collection> slots) { public static void checkInvariant() { if (!LEASED.isEmpty()) { SFM.LOGGER.warn("Leased objects not released: {}", LEASED); + LEASED.clear(); } } } diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java index 95ca56b8a..d24180925 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlot.java @@ -1,6 +1,9 @@ package ca.teamdman.sfm.common.program; import ca.teamdman.sfm.common.resourcetype.ResourceType; +import ca.teamdman.sfml.ast.Label; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import javax.annotation.Nullable; @@ -9,16 +12,28 @@ public class LimitedOutputSlot { public ResourceType type; @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public CAP handler; + @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor + public BlockPos pos; + @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor + public Label label; public int slot; public boolean freed; @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor public OutputResourceTracker tracker; + @SuppressWarnings("NotNullFieldNotInitialized") // done in init method in constructor + private Direction direction; private @Nullable STACK stackInSlotCache = null; public LimitedOutputSlot( - CAP handler, int slot, OutputResourceTracker tracker, STACK stack + Label label, + BlockPos pos, + Direction direction, + int slot, + CAP handler, + OutputResourceTracker tracker, + STACK stack ) { - this.init(handler, slot, tracker, stack); + this.init(handler, label, pos, direction, slot, tracker, stack); } public boolean isDone() { @@ -46,17 +61,33 @@ public STACK getStackInSlot() { return stackInSlotCache; } - public STACK insert(STACK stack, boolean simulate) { + public STACK insert( + STACK stack, + boolean simulate + ) { if (!simulate) stackInSlotCache = null; return type.insert(handler, slot, stack, simulate); } - public void init(CAP handler, int slot, OutputResourceTracker tracker, STACK stack) { + public void init( + CAP handler, + Label label, + BlockPos pos, + Direction direction, + int slot, + OutputResourceTracker tracker, + STACK stack + ) { this.stackInSlotCache = stack; + this.handler = handler; this.tracker = tracker; this.slot = slot; + this.pos = pos; + this.label = label; + this.direction = direction; this.freed = false; + //noinspection DataFlowIssue this.type = tracker.getLimit().resourceId().getResourceType(); if (type == null) { @@ -66,11 +97,13 @@ public void init(CAP handler, int slot, OutputResourceTracker @Override public String toString() { - return "LimitedOutputSlot{" + - "slot=" + slot + - ", cap=" + type.displayAsCapabilityClass() + -// ", stackInSlotCache=" + stackInSlotCache + - ", tracker=" + tracker + - '}'; + return "LimitedOutputSlot{" + + "label=" + label + + ", pos=" + pos + + ", direction=" + direction + + ", slot=" + slot + + ", cap=" + type.displayAsCapabilityClass() + + ", tracker=" + tracker + + '}'; } } diff --git a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java index 7348e3a4a..9808ba333 100644 --- a/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java +++ b/src/main/java/ca/teamdman/sfm/common/program/LimitedOutputSlotObjectPool.java @@ -1,6 +1,9 @@ package ca.teamdman.sfm.common.program; import ca.teamdman.sfm.SFM; +import ca.teamdman.sfml.ast.Label; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import java.util.Arrays; import java.util.Collection; @@ -19,25 +22,27 @@ public class LimitedOutputSlotObjectPool { * Acquire a {@link LimitedOutputSlot} from the pool, or creates a new one if none available */ public static LimitedOutputSlot acquire( - CAP handler, + Label label, + BlockPos pos, + Direction direction, int slot, + CAP handler, OutputResourceTracker tracker, STACK stack ) { if (index == -1) { - var rtn = new LimitedOutputSlot<>( - handler, - slot, - tracker, - stack - ); - LEASED.put(rtn, true); + var rtn = new LimitedOutputSlot<>(label, pos, direction, slot, handler, tracker, stack); + if (LEASED.put(rtn, true) != null) { + SFM.LOGGER.warn("new output slot was somehow already leased, this should literally never happen: {}", rtn); + } return rtn; } else { @SuppressWarnings("unchecked") LimitedOutputSlot obj = pool[index]; index--; - obj.init(handler, slot, tracker, stack); - LEASED.put(obj, true); + obj.init(handler, label, pos, direction, slot, tracker, stack); + if (LEASED.put(obj, true) != null) { + SFM.LOGGER.warn("tried to lease output slot a second time: {}", obj); + } return obj; } } @@ -92,6 +97,7 @@ public static void release(Collection slots) { public static void checkInvariant() { if (!LEASED.isEmpty()) { SFM.LOGGER.warn("Leased objects not released: {}", LEASED); + LEASED.clear(); } } } diff --git a/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java b/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java index 14be38607..2fd3ac326 100644 --- a/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java +++ b/src/main/java/ca/teamdman/sfm/common/resourcetype/ResourceType.java @@ -2,10 +2,13 @@ import ca.teamdman.sfm.common.Constants; import ca.teamdman.sfm.common.cablenetwork.CableNetwork; +import ca.teamdman.sfm.common.program.CapabilityConsumer; +import ca.teamdman.sfm.common.program.LabelPositionHolder; import ca.teamdman.sfm.common.program.ProgramContext; import ca.teamdman.sfm.common.registry.SFMResourceTypes; -import ca.teamdman.sfml.ast.LabelAccess; -import ca.teamdman.sfml.ast.ResourceIdentifier; +import ca.teamdman.sfml.ast.*; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -13,9 +16,7 @@ import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.registries.IForgeRegistry; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Stream; public abstract class ResourceType { @@ -43,22 +44,41 @@ public int hashCode() { /** * Some resource types may exceed MAX_LONG, this method should be used to get the difference between two stacks */ - public long getAmountDifference(STACK stack1, STACK stack2) { + public long getAmountDifference( + STACK stack1, + STACK stack2 + ) { return getAmount(stack1) - getAmount(stack2); } - public abstract STACK getStackInSlot(CAP cap, int slot); + public abstract STACK getStackInSlot( + CAP cap, + int slot + ); - public abstract STACK extract(CAP cap, int slot, long amount, boolean simulate); + public abstract STACK extract( + CAP cap, + int slot, + long amount, + boolean simulate + ); public abstract int getSlots(CAP handler); public abstract long getMaxStackSize(STACK stack); - public abstract long getMaxStackSize(CAP cap, int slot); + public abstract long getMaxStackSize( + CAP cap, + int slot + ); - public abstract STACK insert(CAP cap, int slot, STACK stack, boolean simulate); + public abstract STACK insert( + CAP cap, + int slot, + STACK stack, + boolean simulate + ); public abstract boolean isEmpty(STACK stack); @@ -67,7 +87,10 @@ public long getAmountDifference(STACK stack1, STACK stack2) { public abstract boolean matchesStackType(Object o); - public boolean matchesStack(ResourceIdentifier resourceId, Object stack) { + public boolean matchesStack( + ResourceIdentifier resourceId, + Object stack + ) { if (!matchesStackType(stack)) return false; @SuppressWarnings("unchecked") STACK stack_ = (STACK) stack; if (isEmpty(stack_)) return false; @@ -77,59 +100,95 @@ public boolean matchesStack(ResourceIdentifier resourceId, Obj public abstract boolean matchesCapabilityType(Object o); - public Stream getAllLabelCapabilities( - ProgramContext programContext, LabelAccess labelAccess + public void forEachCapability( + ProgramContext programContext, + LabelAccess labelAccess, + CapabilityConsumer consumer ) { - // TODO: make this return (BlockPos, Direction, CAP) tuples for better logging // Log programContext .getLogger() .trace(x -> x.accept(Constants.LocalizationKeys.LOG_RESOURCE_TYPE_GET_CAPABILITIES_BEGIN.get( - displayAsCode(), displayAsCapabilityClass(), labelAccess + displayAsCode(), + displayAsCapabilityClass(), + labelAccess ))); - Stream.Builder found = Stream.builder(); CableNetwork network = programContext.getNetwork(); + RoundRobin roundRobin = labelAccess.roundRobin(); + LabelPositionHolder labelPositionHolder = programContext.getLabelPositionHolder(); + + ArrayList> positions = new ArrayList<>(); + switch (roundRobin.getBehaviour()) { + case BY_LABEL -> { + int index = roundRobin.next(labelAccess.labels().size()); + Label label = labelAccess.labels().get(index); + for (BlockPos pos : labelPositionHolder.getPositions(label.name())) { + positions.add(Pair.of(label, pos)); + } + } + case BY_BLOCK -> { + List> candidates = new ArrayList<>(); + LongOpenHashSet seen = new LongOpenHashSet(); + for (Label label : labelAccess.labels()) { + for (BlockPos pos : labelPositionHolder.getPositions(label.name())) { + if (!seen.add(pos.asLong())) continue; + candidates.add(Pair.of(label, pos)); + } + } + if (!candidates.isEmpty()) { + positions.add(candidates.get(roundRobin.next(candidates.size()))); + } + } + case UNMODIFIED -> { + for (Label label : labelAccess.labels()) { + for (BlockPos pos : labelPositionHolder.getPositions(label.name())) { + positions.add(Pair.of(label, pos)); + } + } + } + } - // Get positions - Iterable positions = labelAccess - .roundRobin() - .gather(labelAccess, programContext.getLabelPositionHolder())::iterator; - - for (BlockPos pos : positions) { + for (var pair : positions) { + Label label = pair.getFirst(); + BlockPos pos = pair.getSecond(); // Expand pos to (pos, direction) pairs for (Direction dir : (Iterable) labelAccess.directions().stream()::iterator) { // Get capability from the network - Optional cap = network + Optional maybeCap = network .getCapability(CAPABILITY_KIND, pos, dir, programContext.getLogger()) .resolve(); - - if (cap.isPresent()) { - // Add to stream - found.add(cap.get()); + if (maybeCap.isPresent()) { programContext .getLogger() .debug(x -> x.accept(Constants.LocalizationKeys.LOG_RESOURCE_TYPE_GET_CAPABILITIES_CAP_PRESENT.get( - displayAsCapabilityClass(), pos, dir + displayAsCapabilityClass(), + pos, + dir ))); + CAP cap = maybeCap.get(); + consumer.accept(label, pos, dir, cap); } else { // Log error programContext .getLogger() .error(x -> x.accept(Constants.LocalizationKeys.LOG_RESOURCE_TYPE_GET_CAPABILITIES_CAP_NOT_PRESENT.get( - displayAsCapabilityClass(), pos, dir + displayAsCapabilityClass(), + pos, + dir ))); } } } - - return found.build().filter(Objects::nonNull); } - public Stream collect(CAP cap, LabelAccess labelAccess) { + public Stream getStacksInSlots( + CAP cap, + NumberRangeSet slots + ) { var rtn = Stream.builder(); for (int slot = 0; slot < getSlots(cap); slot++) { - if (!labelAccess.slots().contains(slot)) continue; + if (!slots.contains(slot)) continue; var stack = getStackInSlot(cap, slot); if (!isEmpty(stack)) { rtn.add(stack); @@ -161,7 +220,10 @@ public ResourceLocation getRegistryKey(STACK stack) { public abstract STACK copy(STACK stack); @SuppressWarnings("unused") - public STACK withCount(STACK stack, long count) { + public STACK withCount( + STACK stack, + long count + ) { return setCount(copy(stack), count); } @@ -174,6 +236,9 @@ public String displayAsCapabilityClass() { return CAPABILITY_KIND.getName(); } - protected abstract STACK setCount(STACK stack, long amount); + protected abstract STACK setCount( + STACK stack, + long amount + ); } diff --git a/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java b/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java index 860a3711a..8ef3a15e9 100644 --- a/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java +++ b/src/main/java/ca/teamdman/sfml/ast/ASTBuilder.java @@ -540,14 +540,14 @@ public ASTNode visitForgetStatementStatement(SFMLParser.ForgetStatementStatement @Override public ForgetStatement visitForgetstatement(SFMLParser.ForgetstatementContext ctx) { - List