nodo 0.18.5

A realtime framework for robotics
Documentation
// Copyright 2025 David Weikersdorfer

use crate::{config::EmptyEnum, prelude::Pubtime};
use serde::{Deserialize, Serialize};
use std::hash::Hash;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SignalDataType {
    Bool,
    Int64,
    Usize,
    Float64,
    String,
}

pub trait SignalKind: 'static + Copy + PartialEq + Eq + Hash {
    /// List all signal kind variants
    fn list() -> &'static [Self];

    /// The data type of this field
    fn dtype(&self) -> SignalDataType;

    /// A string representation of an enum variant
    fn as_str(&self) -> &'static str;

    /// Parses the enum variant from a string representation
    fn from_str(id: &str) -> Option<Self>;

    /// Like as_str but as an owned string
    fn to_string(self) -> String {
        self.as_str().into()
    }
}

/// The value of a signal
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SignalValue {
    Bool(bool),
    Int64(i64),
    Usize(usize),
    Float64(f64),
    String(String),
}

impl SignalValue {
    pub fn dtype(&self) -> SignalDataType {
        match self {
            SignalValue::Bool(_) => SignalDataType::Bool,
            SignalValue::Int64(_) => SignalDataType::Int64,
            SignalValue::Usize(_) => SignalDataType::Usize,
            SignalValue::Float64(_) => SignalDataType::Float64,
            SignalValue::String(_) => SignalDataType::String,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SignalProperties {
    pub dtype: SignalDataType,
}

pub trait Signals: Default + Send {
    type Kind: SignalKind;

    /// Iterates over time and value of all signals.
    /// Guaranteed to be in the same order as `Self::Kind::list()`.
    fn as_time_value_iter(
        &self,
    ) -> impl Iterator<Item = Option<SignalTimeValue>> + ExactSizeIterator;

    /// Internal function executed by nodo runtime after the codelet executed
    fn on_post_execute(&mut self, step_time: Pubtime);

    /// List of names of all signals
    fn names() -> impl Iterator<Item = &'static str> {
        Self::Kind::list().iter().map(|k| k.as_str())
    }
}

impl SignalKind for EmptyEnum {
    fn list() -> &'static [Self] {
        &[]
    }

    fn dtype(&self) -> SignalDataType {
        unreachable!()
    }

    fn from_str(_id: &str) -> Option<Self> {
        None
    }

    fn as_str(&self) -> &'static str {
        unreachable!()
    }
}

impl Signals for () {
    type Kind = EmptyEnum;

    fn as_time_value_iter(
        &self,
    ) -> impl Iterator<Item = Option<SignalTimeValue>> + ExactSizeIterator {
        std::iter::empty()
    }

    fn on_post_execute(&mut self, _: Pubtime) {}
}

/// Helper type storing a signal and tracking it's modification status
#[derive(Debug, Clone)]
pub struct SignalCell<T> {
    last_set_time: Option<Pubtime>,
    value: Option<T>,
    dirty: bool,
}

impl<T> Default for SignalCell<T> {
    fn default() -> Self {
        Self {
            last_set_time: None,
            value: None,
            dirty: true,
        }
    }
}

impl<T> SignalCell<T> {
    /// Set the signal to a new value
    #[inline]
    pub fn set(&mut self, value: T) {
        self.dirty = true;
        self.value = Some(value);
    }

    /// Clear the currently stored value
    #[inline]
    pub fn clear(&mut self) {
        self.dirty = true;
        self.value = None;
    }

    /// Internal function
    #[inline]
    pub fn on_post_execute(&mut self, time: Pubtime) {
        if self.dirty {
            self.dirty = false;
            self.last_set_time = Some(time);
        }
    }
}

impl SignalCell<usize> {
    /// Increments the value stored in the cell, or sets value to 1 if currently set to None.
    #[inline]
    pub fn increment(&mut self) {
        self.add(1)
    }

    /// Increases the value stored in the cell, or sets value if currently set to None.
    #[inline]
    pub fn add(&mut self, n: usize) {
        self.dirty = true;
        self.value = self.value.map(|v| v + n).or(Some(n));
    }
}

pub trait SignalCellCore {
    /// Get pubtime when the signal cell was last changed
    fn last_set_time(&self) -> Option<Pubtime>;
}

impl<T> SignalCellCore for SignalCell<T> {
    #[inline]
    fn last_set_time(&self) -> Option<Pubtime> {
        self.last_set_time
    }
}

pub trait SignalCellAnon: SignalCellCore {
    /// Get the latest value of the signal cell
    fn value_anon(&self) -> Option<SignalValue>;

    /// Get time and value of the signal cell
    #[inline]
    fn anon_time_value(&self) -> Option<SignalTimeValue> {
        match (self.last_set_time(), self.value_anon()) {
            (Some(time), Some(value)) => Some(SignalTimeValue { time, value }),
            (None, None) => None,
            // This can happen if the function is called between set/clear and on_post_execute
            _ => None,
        }
    }
}

impl SignalCellAnon for SignalCell<bool> {
    #[inline]
    fn value_anon(&self) -> Option<SignalValue> {
        self.value.map(|v| SignalValue::Bool(v))
    }
}

impl SignalCellAnon for SignalCell<i64> {
    #[inline]
    fn value_anon(&self) -> Option<SignalValue> {
        self.value.map(|v| SignalValue::Int64(v))
    }
}

impl SignalCellAnon for SignalCell<usize> {
    #[inline]
    fn value_anon(&self) -> Option<SignalValue> {
        self.value.map(|v| SignalValue::Usize(v))
    }
}

impl SignalCellAnon for SignalCell<f64> {
    #[inline]
    fn value_anon(&self) -> Option<SignalValue> {
        self.value.map(|v| SignalValue::Float64(v))
    }
}

impl SignalCellAnon for SignalCell<String> {
    #[inline]
    fn value_anon(&self) -> Option<SignalValue> {
        self.value.clone().map(|v| SignalValue::String(v))
    }
}

/// A combination of a signal value and the time it was last modified
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignalTimeValue {
    /// Time when the signal was last modified
    pub time: Pubtime,

    /// Latest value of the signal
    pub value: SignalValue,
}