Skip to content

Flow Controller

v0.1.1New

Flow Controller is the central orchestrator in the LOPflow system. It manages a TouchDesigner component as a controllable multi-step process, exposing a clean API through an Internal OP shortcut (iop.LOPflow) that the target component can call to drive the flow forward.

  • Step sequence defined in the parameter panel with per-step duration and routing
  • Three step behaviors: instant auto-advance, timed auto-advance, and wait-for-trigger
  • Persistent flow context (iop.LOPflow.Context) shared across all steps in a run
  • Chained callbacks — both assigned and DAT callbacks fire for every event
  • Dashboard integration for real-time monitoring from LOP Studio
  • Flow history and flow graph generation for debugging and visualization
  • Unique process ID per run for tracking concurrent or sequential flows

When you pulse Start, the controller generates a unique process ID, clears the context, and enters the first step. Each step runs onStepEnter, then behaves according to its duration setting:

  • duration = 0 (default): the step auto-advances to the next step immediately after onStepEnter completes.
  • duration = -1: the step waits indefinitely. The flow only advances when Next Step is pulsed, or when iop.LOPflow.NextStep() is called from the target component or a callback.
  • duration > 0 (seconds): the step waits for the specified time then auto-advances. A trigger can interrupt and advance early.

Each step also has a then setting controlling where the flow goes after the step completes:

  • Next Step: advance to the following step (at the last step, this ends the flow)
  • End Flow: immediately call Finish()
  • Any step name from the sequence: jump directly to that step (enabling loops and branches)

None — the Flow Controller has no wired inputs.

  • Output 1: Not used for data; present for future integration.
  • Output 2: Not used for data; present for future integration.

The primary integration path is the iop.LOPflow Internal OP shortcut installed on the target component, and the callbacks DAT.

  1. Place a Flow Controller in your network.
  2. On the Setup page, set Target Component to the component you want to orchestrate.
  3. Give it a descriptive Flow Name.
  4. Pulse Setup to install the LOPflow Internal OP shortcut on the target. The Setup Status field confirms success.
  5. A callbacks DAT (named {target}_flow_callbacks) is automatically created and docked to the target component. The Callbacks DAT field is also set automatically if a matching DAT is found docked to the target.
  1. Go to the Steps page.
  2. Use the Step sequence to add steps. Each step has:
    • label: Display name shown in the dashboard and used to auto-generate the step’s internal name.
    • duration: 0 (default) to auto-advance instantly, a positive number of seconds for timed advance, or type -1 to wait indefinitely for a trigger.
    • then: Where to go after this step completes (Next Step, End Flow, or a named step for branching/looping).
  3. Step names are auto-generated from the label (lowercased, spaces replaced with underscores). If you rename a step, the old name is tracked in history so GoToStep('old_name') still resolves correctly.
  • Pulse Start on the Control page to begin. The Status field updates to Running and Current Step shows the active step.
  • Pulse Stop to abort the current run. Status returns to Ready.
  • When a wait-duration step is active, the Waiting field shows Yes. Pulse Next Step to advance manually.

Calling the Flow API from the Target Component

Section titled “Calling the Flow API from the Target Component”

Once set up, the target component drives flow progression by calling methods on iop.LOPflow:

# Advance to next step (use in onStepEnter for auto-type steps)
iop.LOPflow.NextStep()
# Pause at current step waiting for external trigger
iop.LOPflow.WaitForContinue(step_data={'status': 'Processing...'})
# Send a mid-step update without advancing
iop.LOPflow.Update(step_data={'progress': 0.5})
# End the flow with a result
iop.LOPflow.Finish(result='Complete!', result_type='success')
# Jump to a specific step by name
iop.LOPflow.GoToStep('review')
# Read/write flow context (shared state across all steps)
iop.LOPflow.Set('my_value', 42, source='callback:onStepEnter')
val = iop.LOPflow.Get('my_value')

These calls are made from inside callbacks — typically onStepEnter — in the callbacks DAT docked to the target component.

The context is a persistent key-value store (iop.LOPflow.Context) that lives for the duration of one flow run. It is cleared automatically when Start is pulsed.

Every call to iop.LOPflow.Set(key, value) fires the onContextChange callback and pulses the OnContextChange reactive dependency. This lets FlowState operators and Script CHOPs observe context changes in real time.

Enable Expose to Dashboard on the Setup page to make this controller visible in the LOP Studio Dashboard. When enabled, the dashboard receives real-time status updates whenever the flow state changes.

Enable Expose Flow Details to also include the full flow context, run history, and a flow graph (nodes and edges) in the dashboard payload. This is useful for debugging and visualization but adds data volume.

A Script CHOP using flow_channels_scriptchop.py can be placed inside the Flow Controller to expose flow state as CHOP channels. The Script CHOP produces channels including:

  • on_start, on_finish, on_update, on_next_step — pulse events (momentary 0/1)
  • current_step, max_steps, waiting_for_continue — step progress
  • status_idle, status_running, status_completed, status_stopped, status_error — mutually exclusive status channels
  • flow_active, progress — derived convenience channels
  • Optional result_length, step_data_length, last_update_time data channels (disabled by default)

All callbacks receive an info dict. All callbacks also receive ownerComp (the target component) in the dict.

CallbackWhen it fires
onFlowStartFlow begins; receives process_id, inputs, list of steps
onStepEnterEntering each step; receives full step dict and metadata
onStepExitExiting each step before advancing
onNextStepAfter advancing to the next step
onWaitForContinueWhen a step enters waiting state
onFlowUpdateWhen iop.LOPflow.Update() is called mid-step
onContextChangeWhen iop.LOPflow.Set() changes a context value
onFlowFinishFlow ends; receives result and result_type
onErrorOn unhandled errors
onStepsChangedWhen the Steps sequence parameters are modified in the UI

The callback system uses chained callbacks: both any programmatically assigned callback and the DAT callback fire for every event, in that order. The DAT callback gets the final say on returnValue.

  1. Create a Flow Controller and set Target Component to your processing COMP.
  2. On the Setup page, pulse Setup.
  3. On the Steps page, add 3 steps:
    • Step 0: label Initialize, duration -1, then Next Step
    • Step 1: label Process, duration -1, then Next Step
    • Step 2: label Complete, duration 0, then End Flow
  4. In the callbacks DAT (auto-created next to your target comp), add logic to onStepEnter:
    • When step_name == 'initialize': prepare resources, then call iop.LOPflow.NextStep()
    • When step_name == 'process': start work, call iop.LOPflow.WaitForContinue() until done
    • When step_name == 'complete': read result from context
  5. Pulse Start on the Control page.
  1. Add a step with a label, set duration to 2.0 (seconds), and then to Next Step.
  2. The flow will automatically advance to the next step two seconds after entering this step.
  3. You can still advance early by calling iop.LOPflow.NextStep() from within the onStepEnter callback before the timer fires.
  1. Add multiple steps including a step named review and a step named retry.
  2. On the review step, set then to retry using the dropdown (step names appear in the menu after they are defined).
  3. When this step completes, the flow jumps back to retry instead of advancing linearly.
  1. On the Setup page, enable Expose to Dashboard.
  2. Set a descriptive Flow Name so it’s identifiable in the dashboard list.
  3. Optionally enable Expose Flow Details to see the full context dict and step history in the dashboard.

Status shows “Not Setup” Pulse Setup on the Setup page. A Target Component must be set first.

Setup fails with “No available IOP slots” The target component’s IOP slot table (up to 10 slots) is full. Check if another Flow Controller is already installed on the same target, or use a different target component.

“Next Step” pulse has no effect The flow must be running and a wait-duration step must be active. Check that Waiting shows Yes on the Control page.

Callbacks DAT is not found automatically The auto-detection looks for a text DAT docked to the target component containing onFlowStart or onStepEnter. If your callbacks DAT has a different structure, set Callbacks DAT manually on the Setup page.

Step names are not resolving in GoToStep() If a step was renamed (its label changed), the old slug name is stored in a rename history table (step_name_history). Old names resolve automatically. If you need to clear the history, this can be done via the ClearNameHistory() method from a script.

Flow restarts on project reload but state is lost The controller persists its setup (IOP slot, target path, process ID) in component storage across reloads. However, in-progress flow execution state (which step was active) is not restored — the flow will show Ready rather than Running after a reload.

Flow Control Header
Start (Start) op('flow_controller').par.Start Pulse

Start the flow

Default:
False
Stop (Stop) op('flow_controller').par.Stop Pulse

Stop the current flow

Default:
False
Next Step (Nextstep) op('flow_controller').par.Nextstep Pulse

Advance to next step (when waiting)

Default:
False
Status Header
Status (Status) op('flow_controller').par.Status Str

Current flow status

Default:
"" (Empty String)
Current Step (Currentstep) op('flow_controller').par.Currentstep Str

Current step name and index

Default:
"" (Empty String)
Waiting (Waiting) op('flow_controller').par.Waiting Str

Whether flow is waiting for continuation

Default:
"" (Empty String)
Last Result (Lastresult) op('flow_controller').par.Lastresult Str

Last flow result

Default:
"" (Empty String)
Target Header
Target Component (Targetcomp) op('flow_controller').par.Targetcomp OP

The component to manage as a flow

Default:
"" (Empty String)
Flow Name (Flowname) op('flow_controller').par.Flowname Str

Human-readable name for this flow

Default:
"" (Empty String)
IOP Shortcut Header
Setup (Setup) op('flow_controller').par.Setup Pulse

Setup the Internal OP shortcut on target component

Default:
False
Cleanup (Cleanup) op('flow_controller').par.Cleanup Pulse

Remove the Internal OP shortcut from target component

Default:
False
Setup Status (Setupstatus) op('flow_controller').par.Setupstatus Str

IOP shortcut setup status

Default:
"" (Empty String)
Process ID (Activeprocessid) op('flow_controller').par.Activeprocessid Str

Current unique process ID

Default:
"" (Empty String)
Options Header
Expose to Dashboard (Exposeprocess) op('flow_controller').par.Exposeprocess Toggle

Make this FlowController discoverable from LOP Studio Dashboard

Default:
False
Callbacks Header
Callbacks DAT (Callbackdat) op('flow_controller').par.Callbackdat OP

DAT with callback functions (onFlowStart, onStepEnter, etc.)

Default:
"" (Empty String)
Expose Flow Details (Exposeflowdetails) op('flow_controller').par.Exposeflowdetails Toggle

Include context, history, and flow graph in dashboard data (for debugging/visualization)

Default:
False
Step (Step) op('flow_controller').par.Step Sequence
Default:
0
label (Step0label) op('flow_controller').par.Step0label Str
Default:
"" (Empty String)
duration (Step0duration) op('flow_controller').par.Step0duration Float
Default:
0.0
Range:
0 to 1
Slider Range:
0 to 1
then (Step0then) op('flow_controller').par.Step0then Menu
Default:
next
Options:
next, end, start
Available Callbacks:
  • onFlowStart
  • onFlowFinish
  • onStepEnter
  • onStepExit
  • onNextStep
  • onWaitForContinue
  • onFlowUpdate
  • onContextChange
  • onError
  • onStepsChanged
Example Callback Structure:
def onFlowStart(info):
# Called when the flow starts
# info contains: process_id, start_time, source, inputs, steps
# info['ownerComp'] - the FlowController component
return

def onFlowFinish(info):
# Called when the flow completes
# info contains: process_id, result, source, response_dict
return

def onStepEnter(info):
# Called when entering a step
# info contains: step, step_index, step_name, step_type, step_label,
#   step_duration, step_then, step_data, source
#
# step_type is derived from duration:
#   'auto' = instant (duration 0) or timed (duration > 0)
#   'wait' = wait indefinitely (duration -1)
return

def onStepExit(info):
# Called when exiting a step
# info contains: step, step_index, step_name, step_type, source
return

def onNextStep(info):
# Called when advancing to the next step
# info contains: step, step_data, source
return

def onWaitForContinue(info):
# Called when flow pauses waiting for continuation
# info contains: step, step_data, update_data, source
return

def onFlowUpdate(info):
# Called when flow sends a mid-step update
# info contains: step, step_data, update_data, advance_step, source
return

def onContextChange(info):
# Called when a context value changes via iop.LOPflow.Set()
# info contains: key, value, old_value, source, step
# source examples: 'api', 'callback:onStepEnter', 'FlowTrigger:myTrigger'
return

def onError(info):
# Called when the flow encounters an error
# info contains: error, process_id
return

def onStepsChanged(info):
# Called when the Steps sequence parameters are modified
# info contains: steps, max_steps
return
v0.1.12026-03-16

Initial release

v0.1.02026-03-12
  • Add flow history tracking with ring buffer audit trail - Add flow graph generation (nodes/edges) for dashboard visualization - Add Exposeflowdetails toggle for dashboard context/history/graph exposure - Add context change dedup via unique _id per Set() call - CurrentStep is -1 when not running - Add guards on all step transitions when flow not active
  • Add Context system (DependDict) with Get/Set/ClearContext API - Add source parameter to all lifecycle methods for audit trail - Add OnContextChange dependency for reactive observers - Add onContextChange callback to emptyCallbacks - Update step_manager to propagate source through transitions - Add flow_context_design.md reference document
  • Add extends field to manifest
  • Add src/ modules (step_manager, dashboard_bridge, parameter_sync, chained_callbacks) - Add reference/ with implementation docs and test examples - Update README with proper description and structure - Fix manifest.json description and category
  • Initial commit