futuresdr 0.7.0

An Experimental Async SDR Runtime for Heterogeneous Architectures.
Documentation
use std::cell::Cell;
use std::rc::Rc;

use futuresdr::runtime::__private::KernelInterface;
use futuresdr::runtime::BlockId;
use futuresdr::runtime::Error;
use futuresdr::runtime::Flowgraph;
use futuresdr::runtime::Pmt;
use futuresdr::runtime::PortId;
use futuresdr::runtime::PortIndex;
use futuresdr::runtime::dev::prelude::*;

#[derive(Block)]
#[null_kernel]
struct PortShape {
    #[input]
    input_vec: Vec<DefaultCpuReader<u8>>,
    #[input]
    input_arr: [DefaultCpuReader<u8>; 2],
    #[input]
    input_tuple: (DefaultCpuReader<u8>, DefaultCpuReader<u8>),
    #[output]
    output_vec: Vec<DefaultCpuWriter<u8>>,
    #[output]
    output_arr: [DefaultCpuWriter<u8>; 2],
    #[output]
    output_tuple: (DefaultCpuWriter<u8>, DefaultCpuWriter<u8>),
}

impl PortShape {
    fn new() -> Self {
        fn reader() -> DefaultCpuReader<u8> {
            Default::default()
        }
        fn writer() -> DefaultCpuWriter<u8> {
            Default::default()
        }

        Self {
            input_vec: vec![reader(), reader()],
            input_arr: std::array::from_fn(|_| reader()),
            input_tuple: (reader(), reader()),
            output_vec: vec![writer(), writer()],
            output_arr: std::array::from_fn(|_| writer()),
            output_tuple: (writer(), writer()),
        }
    }
}

#[test]
fn derive_expands_vector_array_and_tuple_stream_port_names() {
    let mut block = PortShape::new();

    let mut inputs = Vec::new();
    let mut index = 0;
    while let Some((name, _)) = block.stream_input_at(PortIndex::new(index)) {
        inputs.push(name.into_string());
        index += 1;
    }
    assert_eq!(
        inputs,
        [
            "input_vec[0]",
            "input_vec[1]",
            "input_arr[0]",
            "input_arr[1]",
            "input_tuple.0",
            "input_tuple.1",
        ]
    );

    let mut outputs = Vec::new();
    let mut index = 0;
    while let Some((name, _)) = block.stream_output_at(PortIndex::new(index)) {
        outputs.push(name.into_string());
        index += 1;
    }
    assert_eq!(
        outputs,
        [
            "output_vec[0]",
            "output_vec[1]",
            "output_arr[0]",
            "output_arr[1]",
            "output_tuple.0",
            "output_tuple.1",
        ]
    );

    assert!(block.stream_input_at(PortIndex::new(5)).is_some());
    assert!(block.stream_input_at(PortIndex::new(6)).is_none());
    assert!(block.stream_output_at(PortIndex::new(2)).is_some());
    assert!(block.stream_output_at(PortIndex::new(6)).is_none());
}

#[derive(Block)]
#[message_inputs(r#await)]
#[message_outputs(r#loop)]
struct RawPortNames {
    #[input]
    r#type: DefaultCpuReader<u8>,
    #[output]
    r#async: DefaultCpuWriter<u8>,
}

impl Kernel for RawPortNames {}

impl RawPortNames {
    async fn r#await(
        &mut self,
        _io: &mut WorkIo,
        _mo: &mut MessageOutputs,
        _meta: &BlockMeta,
        _p: Pmt,
    ) -> futuresdr::runtime::Result<Pmt> {
        Ok(Pmt::Ok)
    }
}

#[test]
fn derive_strips_raw_identifier_prefixes_from_port_names() {
    let mut block = RawPortNames {
        r#type: Default::default(),
        r#async: Default::default(),
    };

    let (input_name, _) = block.stream_input_at(PortIndex::new(0)).unwrap();
    assert_eq!(input_name.as_str(), "type");
    assert!(block.stream_input_at(PortIndex::new(1)).is_none());

    let (output_name, _) = block.stream_output_at(PortIndex::new(0)).unwrap();
    assert_eq!(output_name.as_str(), "async");
    assert!(block.stream_output_at(PortIndex::new(1)).is_none());
    assert_eq!(RawPortNames::message_inputs(), &["await"]);
    assert_eq!(RawPortNames::message_outputs(), &["loop"]);
    assert_eq!(
        RawPortNames::message_input_id("r#await"),
        Some(futuresdr::runtime::PortIndex::new(0))
    );
}

#[derive(Block)]
#[message_inputs(ping = "renamed-ping", plain)]
#[message_outputs(out, done)]
struct MessageContract {
    seen: Vec<Pmt>,
}

impl Kernel for MessageContract {}

impl MessageContract {
    async fn ping(
        &mut self,
        _io: &mut WorkIo,
        _mo: &mut MessageOutputs,
        _meta: &BlockMeta,
        p: Pmt,
    ) -> futuresdr::runtime::Result<Pmt> {
        self.seen.push(p);
        Ok(Pmt::Usize(self.seen.len()))
    }

    async fn plain(
        &mut self,
        _io: &mut WorkIo,
        _mo: &mut MessageOutputs,
        _meta: &BlockMeta,
        p: Pmt,
    ) -> futuresdr::runtime::Result<Pmt> {
        self.seen.push(p);
        Ok(Pmt::Ok)
    }
}

#[test]
fn derive_exposes_message_metadata_and_dispatches_handlers() {
    assert_eq!(
        MessageContract::message_inputs(),
        &["renamed-ping", "plain"]
    );
    assert_eq!(MessageContract::message_outputs(), &["out", "done"]);

    let mut block = MessageContract { seen: Vec::new() };
    let mut io = WorkIo {
        call_again: false,
        finished: false,
    };
    let mut mo = MessageOutputs::new(BlockId(0), &["out", "done"]);
    let meta = BlockMeta::new();

    let ret = futuresdr::runtime::block_on(block.call_handler(
        BlockId(0),
        &mut io,
        &mut mo,
        &meta,
        MessageContract::message_input_id("renamed-ping").unwrap(),
        Pmt::U32(7),
    ))
    .unwrap();
    assert_eq!(ret, Pmt::Usize(1));

    let ret = futuresdr::runtime::block_on(block.call_handler(
        BlockId(0),
        &mut io,
        &mut mo,
        &meta,
        futuresdr::runtime::PortIndex::new(1),
        Pmt::U32(9),
    ))
    .unwrap();
    assert_eq!(ret, Pmt::Ok);
    assert_eq!(block.seen, vec![Pmt::U32(7), Pmt::U32(9)]);

    let err = futuresdr::runtime::block_on(block.call_handler(
        BlockId(0),
        &mut io,
        &mut mo,
        &meta,
        futuresdr::runtime::PortIndex::new(99),
        Pmt::Null,
    ))
    .unwrap_err();
    assert!(matches!(
        err,
        Error::InvalidMessagePort(block_id, port)
            if block_id == BlockId(0) && port == PortId::index(99)
    ));
}

#[derive(Block)]
#[message_outputs(out)]
struct NonSendMessageSource {
    _state: Rc<Cell<usize>>,
}

impl Kernel for NonSendMessageSource {}

#[derive(Block)]
#[message_inputs(input)]
struct MessageSink;

impl Kernel for MessageSink {}

impl MessageSink {
    async fn input(
        &mut self,
        _io: &mut WorkIo,
        _mo: &mut MessageOutputs,
        _meta: &BlockMeta,
        _p: Pmt,
    ) -> futuresdr::runtime::Result<Pmt> {
        Ok(Pmt::Ok)
    }
}

#[test]
fn flowgraph_validates_message_outputs_from_cached_static_metadata() {
    let mut fg = Flowgraph::new();
    let domain = fg.local_domain().unwrap();
    let src = fg
        .with_local_domain(domain, |ctx| {
            Ok(ctx.add(NonSendMessageSource {
                _state: Rc::new(Cell::new(0)),
            }))
        })
        .unwrap();
    let dst = fg.add(MessageSink).unwrap();

    let err = fg.message(src, "missing", dst, "input").unwrap_err();
    assert!(matches!(err, Error::InvalidMessagePort(_, port) if port.name() == "missing"));

    fg.message(src, "out", dst, "input").unwrap();
}

#[derive(Block)]
#[null_kernel]
struct NonSendLocalBlock {
    state: Rc<Cell<usize>>,
}

#[test]
fn non_send_blocks_can_be_added_to_local_domains() {
    let mut fg = Flowgraph::new();
    let domain = fg.local_domain().unwrap();
    let block = fg
        .with_local_domain(domain, |ctx| {
            Ok(ctx.add(NonSendLocalBlock {
                state: Rc::new(Cell::new(42)),
            }))
        })
        .unwrap();

    let value = block.with(&fg, |block| block.state.get()).unwrap();
    assert_eq!(value, 42);
}