Skip to main content

Control flow

Beyond plain data dependencies, nodes take conditions, loops, priorities, grouped barriers, and ordering-only edges. This page uses examples/stream-analytics as the running example — a sensor pipeline that exercises all of them without external dependencies:

generate (factor=32) ─► classify ─► handle_anomaly (priority: high)
└─► smooth (priority: low)
─► compute_stats (factor=4, grouped barrier)
└─► aggregate ─► report (variadic fan-in)
log_event ($dep on all classify tasks)

Conditions

A Condition makes a node instance execute only when a plugin function's result satisfies a comparison. From examples/stream-analytics/run_bench.py:

cond_anomaly = tm.Condition(
operation="Eq",
value=True,
value_type="bool",
func="check_bool",
args=[classify.out()], # $res(classify, i) — 1:1 instance mapping
)
app.node("handle_anomaly", func="amplify_reading",
factor=total_readings, priority="high", condition=cond_anomaly,
args=[generate.out(), classify.wait()])

At runtime, instance i calls check_bool(classify[i]) and runs only when the result equals true. The smooth node uses the same function with operation="Neq", so exactly one branch fires per reading. Supported operation strings include "Eq" and "Neq" (tomii/_loop.py).

Priorities

priority="high" / priority="low" on a node biases the scheduler. stream-analytics marks the anomaly branch high and the smoothing branch low, so anomaly handling is dispatched ahead of routine work when both are ready.

Barriers and grouped barriers

n.wait(i) is a barrier: wait for instance i, receive no value. A range barrier with group_by splits the wait into independent groups:

compute_stats = app.node("compute_stats", func="compute_sensor_stats",
factor=num_sensors,
args=[generate.wait(0, total_readings, group_by=readings_per_sensor)])

With 32 readings and group_by=8, compute_stats[i] fires when generate[i*8 .. (i+1)*8] complete — four independent groups instead of one global barrier. In the JSON this emits a $barrier arg whose predecessor carries a group_by factor.

Ordering-only edges: $dep

n.dep(...) orders execution without moving data:

app.node("log_event", func="log_stream_event",
args=[classify.dep(0, total_readings)])

log_event fires once after all classify instances complete, but no classify result is fetched — the arg slot receives None, and the runtime skips result storage for producers whose only non-barrier successors are $dep edges (tomii/_node.py).

Loops

A Loop re-executes a node's function for a fixed number of iterations (from the repository README.md):

loop_node = app.node("proc", func="process", factor=200,
loop=tm.Loop("iter", factor=loop_factor))

Loop(name, factor) takes an int or a Var as the iteration count. Per-iteration arguments go in loop_args.

Index functions

IndexFunc(function, args) maps an incoming network packet to the node instance index that consumes it. It belongs to network configuration, not to ordinary nodes — see Network sources.

Factor expressions

Any factor — on nodes, variables, loops, or group_by — is either a literal integer or the name of an integer-valued variable; "$workers" resolves to the worker count (tomii-core/src/json_structs.rs). This is what makes instance counts tunable without touching code — see Runtime knobs.

Verifying control flow

Run the example and its checker:

python examples/stream-analytics/run_bench.py
python examples/stream-analytics/verify.py # prints PASS

With the default anomaly_threshold = 5.0 the anomaly branch fires; raise it to 10.0 to exercise the smoothing branch instead (examples/stream-analytics/README.md).