ReferenceControlArcConceptsReactive Execution

Reactive Execution

How Arc executes programs using reactive dataflow and stratified scheduling

Why This Matters

Consider two functions reading the same pressure sensor:

tank_pressure -> safety_check{}
tank_pressure -> controller{}

In traditional programming, if tank_pressure updates to 500 psi while your code is running, safety_check might see 500 psi while controller sees the old value (400 psi). This inconsistency could cause subtle bugs or safety issues.

Arc prevents this. Both functions see exactly the same value. Either both see 400 psi or both see 500 psi, never a mix.

This is called stratified execution, and it’s why Arc programs are predictable and safe.

The Reactive Model

Arc programs don’t execute line-by-line like traditional code. Instead, they use a reactive dataflow model where computations run automatically in response to new data arriving on channels.

You declare how data flows through your system, and Arc handles the scheduling:

// When tank_pressure updates, scale it and write to display
tank_pressure -> scale{factor=2.0} -> pressure_display

When tank_pressure receives new data, Arc runs scale and writes the result to pressure_display. You don’t poll or schedule anything yourself.

Flow Statements and Edges

Flow statements connect channels and functions using edges. There are two types:

Continuous Edges (->)

Run every time data arrives:

// Scale sensor readings continuously
tank_pressure -> scale{factor=2.0} -> pressure_scaled
// Run a control loop every 50ms
interval{period=50ms} -> controller{}
// Average two sensors
 (sensor_1 + sensor_2) / 2.0 -> average_display

Every time the source produces data, the entire pipeline executes.

Conditional Edges (=>)

Fire while the source expression is truthy:

// Advance to the next stage while pressure is above target
tank_pressure > 500 => next
// Jump to abort while pressure exceeds the limit
tank_pressure > 600 => abort
// Call a function while pressure stays low
tank_pressure < 20 => log_message{}

A conditional edge has no hidden state. On ticks where the source is truthy, it propagates to its target. On ticks where it’s falsy, it doesn’t.

Edges whose target is a stage or sequence name are transitions. See Sequences and Stages for how transitions combine with stage activation.

Timing nodes (interval and wait) reset on stage re-entry. An interval fires immediately on the first tick after re-entry, and a wait restarts its countdown from zero.

A common mistake is using -> when you meant =>. -> fires on every data arrival from the source. => fires only while the source is truthy. For most stage transitions, => is what you want.

Stratified Execution

Arc guarantees deterministic, glitch-free execution through stratification:

Snapshot consistency: All nodes in an execution cycle see the same values. If a channel updates while the graph is executing, every node sees the same snapshot.

Deterministic order: Nodes execute in a guaranteed order based on their dependencies. The compiler organizes nodes into “strata” (layers), where each stratum only executes after all nodes in the previous stratum have completed.

Without this guarantee, debugging becomes difficult and safety certification nearly impossible. You’d never know if a bug was caused by inconsistent data or actual logic errors.

Stages and Functions

Arc programs have two kinds of code, and they serve different purposes.

Stage bodies wire your system together. They declare which channels feed which functions, and under what conditions the program should transition between stages. This is the reactive part of Arc. You don’t write loops or conditionals here. You describe data flow:

stage pressurize {
    1 -> valve_cmd
    tank_pressure -> controller{} -> throttle
    tank_pressure > 500 => next
}

Function bodies do the actual computation. They run once each time the reactive system triggers them, and they support the same control flow you’d expect from any programming language: variables, if/else, and for loops:

func clamp(value f64) f64 {
    if value > 500 {
        return 500.0
    }
    if value < 0 {
        return 0.0
    }
    return value
}

Stages handle timing and ordering. Functions handle the math. This separation is what keeps Arc programs predictable.

Expressions in Flows

Inline expressions work as implicit functions:

// Comparison
temperature > 100 -> alarm{}
// Arithmetic
 (sensor_1 + sensor_2) / 2.0 -> display
// Logical combination
pressure > 100 or emergency -> shutdown{}

These expressions can reference channels and literals, but not local variables from function bodies.

Cycle Detection

Flow graphs must be acyclic. You can’t create a loop where node A depends on node B which depends on node A. The compiler will reject this with an error.

The exception is stage transitions using =>. These can form cycles because they represent valid state machine behavior:

sequence main {
    stage idle {
        start_btn => next
    }
    stage running {
        stop_btn => idle // valid: can return to idle
    }
}