Skip to content

feat(SemanticWorkflow): add loops (reconstructed)#114

Open
hugou74130 wants to merge 8 commits into
mupen64:mainfrom
hugou74130:feat/semantic-workflow-loops-v2
Open

feat(SemanticWorkflow): add loops (reconstructed)#114
hugou74130 wants to merge 8 commits into
mupen64:mainfrom
hugou74130:feat/semantic-workflow-loops-v2

Conversation

@hugou74130
Copy link
Copy Markdown
Contributor

Summary

This PR reconstructs the loop functionality on top of the current main branch, which already includes the semantic input changes from #100.

Background

The original loop draft in #109 was based on an older branch that has since been largely merged via #100. This PR extracts only the loop-specific changes and cleans them up for production.

Changes from #112 incorporated

  • Loop target fields (count, jump target selection)
  • Localization for loop functionality (en_US, fr_FR)
  • Loop target highlighting in section drawing (orange border for targets, blue border for loop sources)
  • Custom loop icon with light/dark theme variants

Cleanup from original draft

  • Removed debug print() statements
  • Removed rough TODO comments
  • Added proper any_changes tracking for loop controls
  • Added run_to_preview() call after loop target selection
  • Extracted SectionInputs.new() into proper implementation file
  • Cleaned up the loop target selection handler logic

Technical Details

  • New Loop class definition in SectionInputs.lua with jump_target, count, and runtime_counter fields
  • Loop playback logic in Sheet.lua evaluate_frame() — when an input with a loop reaches its end condition, it jumps back to the target input instead of advancing, until the loop count is exhausted
  • Loop counters are reset when run_to_preview() is called
  • UI controls: toggle button with loop icon, count numberbox, target selection button

Closes #112

hugou74130 added 2 commits May 3, 2026 13:59
This commit adds loop functionality to the Semantic Workflow, allowing
inputs to jump back to earlier inputs within the same section.

Features:
- Loop definition with count and jump target
- Loop toggle button with icon in InputsTab
- Loop count numberbox control
- Loop target selection via special select handler
- Loop counter reset on sheet run
- Loop target highlighting in section drawing (orange border)
- Loop source highlighting in section drawing (blue border)
- Localization support (en_US, fr_FR)
- Custom loop icon for light/dark themes

Cleanup from original draft:
- Removed debug print statements
- Removed TODO comment about 'unclean' IndexOf usage
- Added proper any_changes tracking for loop controls
- Added run_to_preview() call after target selection
- Extracted SectionInputs.new() into proper implementation file

References: mupen64#109, mupen64#112
- Replace jump_target object reference with 1-based index to fix
  save/load serialization (circular reference in json.encode)
- Add runtime guards in Sheet.evaluate_frame():
  - validate target_index is within bounds and not a forward jump
  - handle nil runtime_counter safely
  - support count=0 as while-like infinite loop
- Fix InputListGui highlighting to use composite keys
  (section_index:input_index) instead of object reference keys
- Add section guard in InputsTab loop target selector
- Initialize runtime_counter after load() if absent
Copy link
Copy Markdown
Collaborator

@FramePerfection FramePerfection left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a deeper look into this later, but at a glance I think this may be fine.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in #112 (comment), this icon needs a license notice.

hugou74130 added a commit to hugou74130/SM64LuaRedux that referenced this pull request May 3, 2026
FramePerfection requested in review that the loop/loop_light icons
(cycle from Google Material Symbols) carry a license notice.

Refs: PR mupen64#114 review comment
FramePerfection requested in review that the loop/loop_light icons
(cycle from Google Material Symbols) carry a license notice.

Refs: PR mupen64#114 review comment
Copy link
Copy Markdown
Collaborator

@FramePerfection FramePerfection left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thing I hadn't properly considered that we should define good and ideally intuitive behavior is:

  • nested loops (one loop entirely containing another)
  • interlaced loops (a loop jumping back into the middle of a previous loop).
    Perhaps the best course of action would be to reject those for now, as both cases aren't exactly trivial.

Code wise this looks decent, I'll take another look when the semantic issues are resolved, though.

--

---@class Loop Runtime information for a section loop.
---@field jump_target integer The 1-based index of the input to jump to within the same section. Can be the current input's index (self-loop), but must not be larger than the looping input's index.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally made this field a SectionInputs because that way the loop target correctly move along when inputs are added before it.
This functionality should be restored, by either:

  • making this a SectionInputs again and only use indexing logic for the save/load routines, and handling deletion of the loop target explicitly
  • changing the target indices of looping inputs when inserting and deleting inputs before their respective targets

I'd personally probably actually go for the second option, now that I think about it.

Comment on lines +502 to +504
if loop_targets[section_index .. ":" .. input_index] then
BreitbandGraphics.draw_rectangle(section_rect, '#FF8000FF', 2)
end
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be better to only draw the target rectangle for the currently selected looping input, if any, because drawing them all makes it impossible to distinguish which looping input is jumping where.

hugou74130 added 2 commits May 3, 2026 20:18


Point 2 (option B): When inserting or deleting inputs, adjust the
jump_target indices of looping inputs in the same section.

- Insert before  => increment targets >= insert_index
- Insert after   => increment targets >= insert_index
- Delete         => decrement targets > delete_index,
                   remove loop if target == delete_index

Point 3: Only draw the orange target rectangle for the currently
selected looping input, instead of all targets at once. This makes
it possible to distinguish which loop jumps where.

Refs: PR mupen64#114 review comments
FramePerfection requested that nested and interlaced loops be rejected
as non-trivial for now.

- InputsTab: add is_loop_target_valid() and gate special_select_handler
  so that selecting a loop target which would create a nested or
  interlaced loop is silently ignored.

- Sheet: add is_loop_valid() and treat invalid loops as non-existent
  during playback, guarding against corrupted/legacy sheets.

Refs: PR mupen64#114 review comments
Copy link
Copy Markdown
Collaborator

@FramePerfection FramePerfection left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything seems to work correctly with my suggested overlap check change, so functionally I'd say this is ready for merge.

However, I do think it'd be nice if you wrote up a small tutorial page similar to the Atan one (which I admit I'll have to review for whether it's up to date with the actual implementation lol) on how to use the feature, specifically in regards to the color coding for now, because I feel like that can be confusing without explanation.

On a related note, I've noticed that the text lines in the help page don't wrap anymore like they used to, which is another issue (potentially related to #86 (EDIT: yeah, 5b99324 broke it, refer to #120)), but that's a different issue not related to writing the text itself.

Comment thread src/views/SemanticWorkflow/Implementations/InputsTab.lua Outdated
Comment on lines +16 to +37
---@param section Section
---@param own_index integer
---@param new_target integer
---@return boolean
local function is_loop_valid(section, own_index, new_target)
for other_index, other_input in ipairs(section.inputs) do
if other_index ~= own_index and other_input.loop then
local other_target = other_input.loop.jump_target
if other_target then
local a, b = new_target, own_index
local c, d = other_target, other_index
local nested = (a < c and d < b) or (c < a and b < d)
local interlaced = (a < c and c < b and b < d) or (c < a and a < d and d < b)
if nested or interlaced then
return false
end
end
end
end
return true
end

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a near exact duplicate of the (faulty) logic in is_loop_target_valid. Either extract the core logic, or remove the validation at this stage entirely, as sheet validation is a separate concern to be addressed at a later time. (See "Proposed Enhancement" in #26)

if target_index == nil or target_index < 1 or target_index > #section.inputs
or target_index > self._input_index
or (loop.count ~= 0 and runtime_counter >= loop.count)
or not is_loop_valid(section, self._input_index, target_index)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment regarding this validation.

More apparently here, though: I don't see validation having a place in evaluation logic whatsoever, since that's a hot path and much more performance critical than "loading" logic (i.e. opening a project from the drive), especially with how validation logic tends to be expensive itself (this one contains a loop over the entire section's input range).

If you wish to address file validation to catch corrupted or "legacy" files, do so in a separate PR.
Do note, however, that the current model of the file format is intentionally left loose for now to allow quick evolution, only insisting on the semantic versioning model that's described in the help page (which explicitly allows "catastrophically" (i.e. script crashing) breaking changes between major versions).

Comment thread src/views/SemanticWorkflow/Implementations/InputListGui.lua
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants