跳转到内容

Choosing a Marker

This content is for v1. Switch to the latest version for up-to-date documentation.

此内容尚不支持你的语言。

The bridge uses BRIDGE_* markers to decide how C++ functions are exposed to Dart. Choosing the wrong one can cause blocking, deadlocks, or performance issues. This page is a cheat sheet plus decision flowchart.

Marker Execution Thread C++ Return Type Dart Return Type Can Block Can Call DartFn
BRIDGE_SYNC io_context T T ❌ (deadlock)
BRIDGE_ASYNC io_context coroutine async_simple::coro::Lazy<T> Future<T> ✅ (co_await)
BRIDGE_NORMAL thread_pool T Future<T> ✅ (syncAwait)
Stream io_context void + StreamSink<T> Stream<T> Depends on implementation

Core principles:

  • Never block the io_context thread
  • DartFn cannot be used with BRIDGE_SYNC

The C++ function executes synchronously on the bridge’s io thread, and the result is returned to Dart immediately.

BRIDGE_SYNC
std::int32_t bridge_version() { return 42; }

Suitable for:

  • Pure computation, getters, constant reads
  • Microsecond-level operations (usually < 1 μs)
  • No file access, network, locks, or sleep

Not suitable for:

  • Blocking calls
  • Calling DartFn (permanent deadlock, because the Dart reply requires the io thread)

2. BRIDGE_ASYNC — Asynchronous Coroutines

Section titled “2. BRIDGE_ASYNC — Asynchronous Coroutines”

The C++ function returns async_simple::coro::Lazy<T> and runs as a coroutine on the io thread; when it hits co_await, it suspends without occupying the thread.

BRIDGE_ASYNC
async_simple::coro::Lazy<std::int32_t> add(std::int32_t a, std::int32_t b) {
co_return a + b;
}
BRIDGE_ASYNC
async_simple::coro::Lazy<std::string> fetch_url(std::string url) {
// You can co_await channels, sleep, DartFn, or spawn_blocking.
auto result = co_await co::oneshot::recv();
co_return result;
}

Suitable for:

  • Asynchronous IO
  • Waiting for other coroutines / channels / Dart callbacks
  • Composing multiple asynchronous operations

Not suitable for:

  • Blocking operations (use BRIDGE_NORMAL or spawn_blocking)

The C++ function is an ordinary function (it does not return Lazy); the bridge automatically dispatches it to the thread_pool. On the Dart side it is still Future<T>.

BRIDGE_NORMAL
std::string sleep_greeting(std::string name) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return "Hello, " + name;
}

Suitable for:

  • File IO, synchronous network IO libraries
  • CPU-intensive computation
  • Any operation that blocks or takes more than microseconds

Notes:

  • The function can block internally because it runs on the thread pool, not the io thread
  • You can still call DartFn via async_simple::coro::syncAwait(dcb::spawn(...))

A function is exported as a Dart Stream<T> when it carries an export marker (BRIDGE_SYNC / BRIDGE_ASYNC / BRIDGE_NORMAL) and takes a required dcb::StreamSink<T> parameter. The export marker is the gate: a StreamSink parameter alone does not export the function (the generator warns and skips it). For plain void stream functions use BRIDGE_NORMAL; the sink parameter makes it a stream regardless of the marker’s usual scheduling, and the generated Dart API returns Stream<T>.

BRIDGE_NORMAL
void ticks(dcb::StreamSink<std::int32_t> sink, std::int32_t count) {
for (std::int32_t i = 0; i < count; ++i) {
sink.add(i);
}
sink.end();
}

Notes:

  • The function may return immediately; keep the sink and call add() later from any thread
  • sink.error(msg) sends an error event; sink.end() closes the stream normally
  • Cancelling the subscription only stops Dart-side reception; the C++ side continues running and later add() calls are silently dropped
  • std::optional<dcb::StreamSink<T>> makes the stream optional: the generated Dart signature takes a StreamController<T>? input parameter instead of returning a Stream<T>; use it on BRIDGE_SYNC / BRIDGE_ASYNC / BRIDGE_NORMAL functions (there is no separate stream marker; sync events are delivered after the FFI call returns)

See Streams for complete examples (required and optional streams, cancellation, threading, errors).

dcb::DartFn<Ret(Args...)> represents a Dart closure. It is not a function marker itself, but a parameter type, and must be paired with BRIDGE_ASYNC or BRIDGE_NORMAL.

Call it with co_await inside a BRIDGE_ASYNC coroutine; the io thread actually suspends.

BRIDGE_ASYNC
async_simple::coro::Lazy<std::string> greet(
dcb::DartFn<std::string(std::string)> callback, std::string name) {
auto reply = co_await callback(name);
co_return "Dart said: " + reply;
}

Use the BRIDGE_PERSIST marker so the closure is not automatically unregistered after the call returns; it can be stored and invoked repeatedly.

BRIDGE_SYNC
BRIDGE_PERSIST
bool register_callback(dcb::DartFn<std::string(std::string)> callback);
BRIDGE_NORMAL
std::string invoke_callback(std::string name);
// ❌ Deadlock
BRIDGE_SYNC
std::string bad(dcb::DartFn<std::string(std::string)> callback);
Need to return a Stream?
→ Yes: export marker (typically BRIDGE_NORMAL) + dcb::StreamSink<T> parameter
→ Optional: std::optional<dcb::StreamSink<T>> parameter on a sync/async/normal function
Need to call a Dart closure?
→ Yes: BRIDGE_ASYNC + DartFn (co_await)
→ Or: BRIDGE_NORMAL + syncAwait(dcb::spawn(fn(args)))
Will the function block / take time / do file IO?
→ Yes: BRIDGE_NORMAL
Is the function asynchronous, needing co_await / channel / sleep?
→ Yes: BRIDGE_ASYNC
Is it just pure computation / getter / microsecond-level operation?
→ Yes: BRIDGE_SYNC
Mistake Consequence
Blocking inside BRIDGE_SYNC io thread stalls, the entire bridge becomes unresponsive
BRIDGE_SYNC + DartFn Permanent deadlock
Blocking inside BRIDGE_ASYNC Same as blocking inside BRIDGE_SYNC
Writing co_await inside BRIDGE_NORMAL Compile error, because it is not a coroutine