RuntimeFlow_backup - SergeiGolos/wod-wiki GitHub Wiki
This document outlines the current (2025-04-26) behaviour of the wod.wiki runtime layer—including the script stack, JIT compilation, runtime trace, and how events flowing in from the UI are converted into actions that mutate application state. Code snippets are TypeScript extracts taken directly from the source-tree so that the interfaces referenced here stay in sync with the implementation.
flowchart TD
%% Compile-time
subgraph "Compile phase Editor"
L[Lexer] --> P[Parser] --> I[Interpreter] --> J[RuntimeJIT]
end
%% Runtime phase
J --> SS[Script Stack]
SS -. consumes .-> E1[Event Handlers]
E1 --> RT[RuntimeTrace]
RT --> A1[Action Dispatcher]
A1 --> UI[React UI]
-
RuntimeJIT converts a parsed
WodRuntimeScript
(AST) into an executable stack ofStatementNode
s. - The stack is stored in
RuntimeState.pointer
and walked by event-driven handler classes (e.g.NextStatementHandler
). - Each handler records execution progress in
RuntimeTrace
and publishes one or moreRuntimeAction
s that the UI consumes via React context providers.
The JIT produces a linearised list of nodes that represent the workout in execution order.
// timer.types.ts
export interface StatementNode {
id: number; // Globally unique, used by RuntimeTrace
kind: StatementKind; // Round, Exercise, Timer, …
rounds?: number; // Optional repetition meta
// …additional compiler-time metadata
}
// runtime internal
export type ScriptStack = StatementNode[];
During execution the top of the stack represents the current instruction. Handlers may push or pop nodes (e.g., when entering / leaving repeating groups).
RuntimeTrace
keeps a per-node execution ledger—counting how many rounds (iterations) a node has completed in total and in the current invocation chain.
export class RuntimeTrace {
private trace: Map<number, [number, number]> = new Map();
public history: StatementKey[] = [];
get(id: number): number { /* current round */ }
getTotal(id: number): number { /* lifetime rounds */ }
nextRound(id: number): number { /* convenience accessor */ }
set(stack: StatementNode[]): StatementKey { /* snapshot */ }
}
-
trace
→ mapsStatementNode.id
→[currentRound, totalRounds]
. -
history
→ ordered list ofStatementKey
s; each key represents the full path through nested statements that led to an executed leaf. -
set
is invoked by every handler just before emitting aDisplayUpdateAction
. This ensures UI counters (⏱️, 🔁, 💪) always reflect the exact runtime state the user sees.
sequenceDiagram
participant UI as React UI
participant Runtime as RuntimeEngine
participant Handler as NextStatementHandler & co.
UI->>Runtime: NextStatementEvent (button click / autoplay)
Runtime->>Handler: delegate(event)
Handler->>Handler: trace.set(stack)
Handler-->>UI: NextStatementAction (💡 update editor & timer)
opt Completion
Handler-->>UI: SetResultAction (🏁 metrics)
end
export enum EventType {
NextStatement = "next", // advance pointer
TogglePause = "pause", // play/pause timer
Reset = "reset", // restart workout
// …
}
export interface RuntimeEvent {
type: EventType;
payload?: unknown;
}
Events originate from user interaction (ButtonRibbon
) or timer hooks (auto-advance when clock hits 0).
export enum ActionType {
DisplayUpdate = "display_update",
SetButton = "set_button_state",
NextStatement = "next_statement",
SetResult = "set_result",
}
export interface RuntimeAction {
type: ActionType;
payload?: unknown;
}
- The Action Dispatcher inside the runtime publishes actions via
EventEmitter
, which the UI subscribes to. - Idempotence: Each action fully describes the resulting UI state—ensuring React components can re-render predictably from any subscription point.
flowchart TB
subgraph Compile-once
AST[WodRuntimeScript\n AST] --> JIT[RuntimeJIT]
JIT --> Stack[ScriptStack]
end
subgraph Execute-many Event loop
Stack -->|handlers| Trace[RuntimeTrace]
Trace --> Actions
Actions --> UI
end
- JIT resolves static structure—round counts, repeat groups, AMRAP blocks—so the runtime need only perform constant-time pointer arithmetic during execution.
- Trace records dynamic data—how many rounds have actually been performed, including user-initiated resets, pauses, or skips.
When a user starts a workout:
-
MonacoEditor
emitsonScriptCompiled
with a freshWodRuntimeScript
. -
RuntimeJIT
linearises the AST into aScriptStack
and initialisesRuntimeTrace
. - UI dispatches the first
NextStatementEvent
(either automatically or via start button). -
NextStatementHandler
pops the next node, updatesRuntimeTrace
, and returns aNextStatementAction
&DisplayUpdateAction
. - The UI updates timer digits, round counters, and inlay hints in a single React render.
- Steps 3–5 repeat until the stack is empty, at which point a
SetResultAction
finalises metrics and presents them inResultsDisplay
.
-
Group Operator Strategies (Round-Robin/Compose/Repeat) plug into the JIT via
*CompilerStrategy
classes. -
Custom Metrics (e.g., heartrate) can be added by extending
RuntimeMetric
and teachingWodResults
to render new icon columns. -
New Events should be handled by deriving from
RuntimeEventHandler
and registered inRuntimeEngine
.