nodo_runtime 0.18.5

Runtime for NODO applications
Documentation
// Copyright 2024 David Weikersdorfer

use crate::StateMachine;
use eyre::Report;
use nodo::{
    app::AppSetupScheduleContext,
    codelet::{Lifecycle, Transition, Vise, ViseTrait},
    monitors::SharedAppMonitor,
    prelude::{Outcome, ParameterId, ParameterSet, ParameterWithPropertiesSet, RUNNING},
};

/// Executes a sequence of nodes
pub(crate) struct SequenceExecutor {
    schedule_name: String,
    sequence_name: String,
    items: Vec<StateMachine<Vise>>,
    monitor: SharedAppMonitor,
}

impl SequenceExecutor {
    pub fn new<'a, I: IntoIterator<Item = Vise>>(
        context: &mut AppSetupScheduleContext<'a>,
        schedule_name: String,
        sequence_name: String,
        vises: I,
    ) -> Self {
        context.on_begin_sequence(sequence_name.clone());

        let mut items = Vec::new();

        for mut vise in vises.into_iter() {
            context.on_node(&mut vise);
            items.push(StateMachine::new(vise));
        }

        context.on_end_sequence();

        Self {
            schedule_name,
            sequence_name,
            items,
            monitor: context.monitor(),
        }
    }

    pub fn get_parameters_with_properties(
        &self,
    ) -> ParameterWithPropertiesSet<String, &'static str> {
        let mut result = ParameterWithPropertiesSet::default();
        for item in self.items.iter() {
            result.extend(item.inner().get_parameters_with_properties().map_into(
                |(ParameterId((), b), v)| (ParameterId(item.inner().name().to_string(), b), v),
            ));
        }
        result
    }

    pub fn configure(&mut self, config: &ParameterSet<String, String>) {
        // TODO Handle errors properly. It gets complicated because we don't directly know in
        // which worker/schedule a node is and currently try them all. It's also not clear yet
        // how errors shouled be handled if some parameters are set successful and some fail.
        for (key, value) in config.iter() {
            if let Some(vise) = self.find_vise_by_name_mut(key.node()) {
                if let Err(err) = vise.configure(key.param(), value) {
                    log::error!("{err:?}");
                }
            }
        }
    }

    fn find_vise_by_name_mut(&mut self, needle: &str) -> Option<&mut Vise> {
        self.items
            .iter_mut()
            .map(|sm| sm.inner_mut())
            .find(|vise| vise.name() == needle)
    }
}

impl Lifecycle for SequenceExecutor {
    fn cycle(&mut self, transition: Transition) -> Outcome {
        let mut result = SequenceExecutorCycleResult::new();

        for csm in self.items.iter_mut() {
            match csm.transition(transition) {
                Err(err) => {
                    result.mark(csm.inner(), err.into());
                }
                Ok(_) => {}
            }
        }

        match result.into() {
            Some(err) => Err(err),
            None => RUNNING,
        }
    }
}

struct SequenceExecutorCycleResult {
    maybe: Option<SequenceExecutorCycleError>,
}

impl SequenceExecutorCycleResult {
    fn new() -> Self {
        SequenceExecutorCycleResult { maybe: None }
    }

    fn mark(&mut self, vise: &Vise, error: Report) {
        if self.maybe.is_none() {
            self.maybe = Some(SequenceExecutorCycleError::new());
        }

        // SAFETY: `maybe` is cannot be None due to code above
        self.maybe.as_mut().unwrap().mark(vise, error);
    }
}

#[derive(thiserror::Error, Debug)]
#[error("SequenceExecutorCycleError({:?})", self.failures)]
struct SequenceExecutorCycleError {
    failures: Vec<(String, Report)>,
}

impl SequenceExecutorCycleError {
    fn new() -> Self {
        SequenceExecutorCycleError {
            failures: Vec::new(),
        }
    }

    fn mark(&mut self, vise: &Vise, error: Report) {
        self.failures.push((vise.name().to_string(), error));
    }
}

impl From<SequenceExecutorCycleResult> for Option<eyre::Report> {
    fn from(value: SequenceExecutorCycleResult) -> Self {
        match value.maybe {
            Some(x) => Some(x.into()),
            None => None,
        }
    }
}