futuresdr 0.0.41

An Experimental Async SDR Runtime for Heterogeneous Architectures.
Documentation
# Custom Schedulers

Schedulers execute normal flowgraph block tasks and async tasks spawned through the runtime. Most applications should use `Runtime::new()` and the default `SmolScheduler`; write a scheduler only when you are experimenting with placement, latency, or executor integration.

The scheduler trait is:

```rust
pub trait Scheduler: Clone + Send + 'static {
    #[cfg(not(target_arch = "wasm32"))]
    fn run_domain(
        &self,
        blocks: Vec<Box<dyn Block>>,
        main_channel: &Sender<FlowgraphMessage>,
    ) -> Vec<Task<(BlockId, Box<dyn Block>)>>;

    fn spawn<T: Send + 'static>(
        &self,
        future: impl Future<Output = T> + Send + 'static,
    ) -> Task<T>;
}
```

`run_domain()` receives the normal send-capable blocks in a flowgraph. It must spawn each block, call `block.run(main_channel).await`, and return task handles that yield `(BlockId, Box<dyn Block>)`. The runtime waits for those tasks and restores the finished block objects into the returned flowgraph.

`spawn()` runs general async tasks on the scheduler. `Runtime::spawn()`, `Runtime::spawn_background()`, and control-plane internals use this method.

## Normal vs Local Work

Schedulers handle only the normal scheduling domain. The runtime manages local domains separately for:

- blocks added through `Flowgraph::add_local()`,
- blocks marked with `#[blocking]`.

That separation lets scheduler implementations assume that `run_domain()` receives send-capable block tasks. Blocking or thread-affine work should be placed in a local domain instead of being hidden inside the normal scheduler.

## Starting Point

Use the existing schedulers as templates:

- `SmolScheduler` is a compact general-purpose scheduler backed by `async_executor`.
- `FlowScheduler` shows deterministic block placement onto worker-local queues.

A minimal native scheduler usually needs:

- a clonable handle to an executor,
- worker thread lifecycle management,
- an implementation of `run_domain()` that spawns every block and returns its task,
- an implementation of `spawn()` for unrelated async tasks.

## Selecting a Scheduler

Construct the runtime with your scheduler:

```rust
use futuresdr::prelude::*;

let scheduler = MyScheduler::new();
let rt = Runtime::with_scheduler(scheduler);

let fg = Flowgraph::new();
rt.run(fg)?;
```

Custom schedulers should preserve the runtime contract: every spawned block task must eventually return its block object, even if the block exits because the flowgraph was stopped. If a worker thread panics, treat it as a runtime failure rather than silently dropping block state.