Two-Thread Architecture
Rill separates processing into two independent threads communicating through lock-free SPSC queues:
Signal Thread (hard or soft RT)
Runs the process callback — generate() → process() → consume() → Port::propagate().
No heap allocs, no locks, no syscalls. The graph is a single-threaded static DAG — nodes are
never added or removed after construction, and topology never changes.
Inside the I/O callback tick:
actor.drain()— applies queuedCommandEnum::SetParametercommands from the actor mailbox- Builds a
RenderContextwith sample clock, transport state, and hardware clock correction Source::generate()/Processor::process()/Sink::consume()viaprocess_block(&ctx)Port::propagate()— recursive DAG traversal through direct port pointers- Sends
CommandEnum::ClockTickto the parent Patchbay actor
All rill-core::buffer types (DelayLine, TapeLoop, PipeBuffer, RingBuffer, FanOutBuffer, FanInBuffer)
are used exclusively inside this path. No atomics, no locks — the graph is a single-threaded static DAG.
Control Thread (tokio green threads)
Runs Patchbay with automatons (LFO, envelopes, sequencers). Communicates with the signal
thread via the graph actor mailbox — messages are CommandEnum variants, sent via
ActorRef<CommandEnum> and drained inline inside the callback tick. No separate queue types are needed.
Servo — the primary automaton-to-parameter bridge:
- Receives
CommandEnum::ClockTickfrom the graph - Advances time and calls
automaton.step() - Applies
ControlStrategyandConflictStrategy - Sends
CommandEnum::SetParameterto the graph'sActorRef<CommandEnum> - The
SetParameterlands in the graph's actor mailbox; next I/O callback tick,actor.drain()applies it
Communication channels
I/O callback tick: Actor mailbox (CommandEnum):
actor.drain() ◄────────── SetParameter (servo → graph)
generate() / process() / consume()
port.propagate() Control path:
── ClockTick ──→ Servo ──→ automaton.step()
── SetParameter ──→ graph_ref (next tick drain)
Rule of thumb
If data crosses threads, send CommandEnum variants through ActorRef<CommandEnum>.
Everything else is single-threaded within the signal graph running inside the I/O callback.