Architecture (v2)
Layered architecture
Section titled “Layered architecture”Dart / Flutter app └─ Dart package (DartCppBridge, codec, FFI bindings) └─ FFI binary framesNative library ├─ Runtime (Asio io_context + IoContextScheduler + blocking pool) ├─ Session registry (one session per Dart isolate) ├─ Wire dispatch (frame routing and method_id) ├─ Codec (ByteReader / ByteWriter) ├─ Channels and stdexec scheduler adapters └─ User code (BRIDGE_SYNC / BRIDGE_ASYNC / BRIDGE_NORMAL)The generator scans BRIDGE_* markers and emits the wire dispatch, Dart FFI
bindings, and the Dart API layer. Async C++ declarations use
stdexec::task<T> or another supported sender in v2.
Runtime
Section titled “Runtime”The process-wide `dcb::Runtime~ owns:
- an Asio io scheduler exposed as
IoContextScheduler(one runner by default, configurable before startup); - a blocking Asio thread pool;
- scheduler-aware channels and timers;
- the Dart post callback and session lifecycle.
A task started by generated dispatch is scheduler-affine. A foreign loop is not wrapped as an async-simple executor in v2; it exposes a plain stdexec scheduler that can be used with sender composition.
Call flow
Section titled “Call flow”Dart → dcb_invokeSyncMethod → decode request on the io thread → call BRIDGE_SYNC function → encode responseOk / responseErr → return to DartThe function must be short and non-blocking.
Dart → dcb_invokeAsyncMethod → decode request → create stdexec::task<T> → starts_on(io_scheduler, task) → co_await sender / channel / timer / DartFn → post responseOk / responseErr to the Dart sessionGenerated coroutine dispatch uses a zero-capture IIFE. Arguments are passed as parameters so the coroutine frame owns all state after the dispatch function returns.
Normal
Section titled “Normal”Dart → dcb_invokeNormalMethod → post ordinary C++ function to the blocking pool → encode result or exception → post response to DartStream and DartFn
Section titled “Stream and DartFn”A stream uses a StreamSink<T> to post streamData, streamEnd, or
streamErr frames. Dart unsubscription stops delivery; the native
operation may continue and late sink calls are dropped.
A DartFn call posts a dartFnCall frame and awaits a oneshot sender.
The io thread suspends while Dart executes the closure, then resumes when the
reply arrives.
Threading model
Section titled “Threading model”- io thread: frame dispatch, scheduler work, non-blocking timers, and DartFn initiation;
- blocking pool:
BRIDGE_NORMALandspawn_blockingwork; - foreign loop threads: user-provided stdexec schedulers;
- Dart isolate: Dart code and closure execution.
Never block an io scheduler runner. Use dcb::sync_wait only from a worker or
external thread, and use stop tokens for cooperative cancellation. Multiple io
runners do not remove this rule: a raw stdexec::sync_wait occupies its runner
until completion and deadlocks the scheduler if all runners wait for work on it.