Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runtime

A FutureSDR Runtime owns a Scheduler and starts one or more Flowgraphs. On native targets, the runtime can start an integrated web server to serve a web UI and expose the control port interface for runtime and flowgraph interaction.

Running a Flowgraph

The simplest way to execute a flowgraph is to construct a runtime, pass the flowgraph to run(), and block until it terminates.

let mut fg = Flowgraph::new();
// set up the flowgraph

let fg = Runtime::new().run(fg)?;

The run() method is a blocking call that takes ownership of the flowgraph and returns the finished flowgraph after all blocks have terminated. This is useful when you need to inspect blocks after execution, for example to read data or statistics.

In async code, use run_async() instead:

let mut fg = Flowgraph::new();
// set up the flowgraph

let fg = Runtime::new().run_async(fg).await?;

Starting a Flowgraph

Use start_async() when the application should keep doing other work while the flowgraph is running. It returns once all blocks have initialized.

let mut fg = Flowgraph::new();
// set up the flowgraph

let rt = Runtime::new();
let running = rt.start_async(fg).await?;

On native targets, start() provides the same behavior from synchronous code:

let mut fg = Flowgraph::new();
// set up the flowgraph

let rt = Runtime::new();
let running = rt.start(fg)?;

Both methods return a RunningFlowgraph. It combines the completion task with a FlowgraphHandle:

let running = rt.start(fg)?;
let handle = running.handle();

Runtime::block_on(async move { handle.post(block_id, "handler_name", Pmt::U32(42)).await })?;

let fg = running.wait()?;

Use running.post() and running.call() to interact with blocks, running.wait() to block until termination on native targets, and running.wait_async().await in async code. running.stop_and_wait().await requests shutdown and then recovers the finished flowgraph. Use running.handle() when you need to keep a clonable control handle. If you need to pass the two parts around separately, running.split() returns the FlowgraphTask and FlowgraphHandle.

Selecting a Scheduler

To use a different scheduler or change its configuration, you can specify it when constructing the runtime.

let mut fg = Flowgraph::new();
// set up the flowgraph

let rt = Runtime::with_scheduler(FlowScheduler::new());
rt.run(fg)?;

Runtime Handle

A RuntimeHandle is a clonable control handle for the runtime. It is useful when other tasks, threads, web handlers, or callbacks need to start flowgraphs or query the flowgraphs registered with the runtime control plane.

let rt = Runtime::new();
let runtime_handle = rt.handle();

Runtime::block_on(async move {
    let mut fg = Flowgraph::new();
    // set up the flowgraph

    let running = runtime_handle.start(fg).await?;
    let flowgraph_handle = running.handle();
    let description = flowgraph_handle.describe().await?;

    Ok::<_, futuresdr::runtime::Error>(())
})?;

RuntimeHandle::start() returns a RunningFlowgraph. It also registers the flowgraph with the runtime control plane, so it remains available through get_flowgraph() and the control port.

Initialization and Logging

Constructing a runtime calls futuresdr::runtime::init() automatically. At the moment, initialization installs FutureSDR’s default tracing subscriber when no subscriber is already installed. Call it manually only when application code wants to emit FutureSDR logs before constructing the runtime:

use futuresdr::prelude::*;

fn main() -> Result<()> {
    futuresdr::runtime::init();
    info!("parsing configuration before the runtime exists");

    let rt = Runtime::new();
    // build and run flowgraphs

    Ok(())
}

If an application installs its own tracing subscriber first, FutureSDR leaves it in place. See Logging for runtime and compile-time filtering details.