gmt_dos_actors/framework/
model.rs

1//! # Model framework
2//!
3//! The model module define the interface to build a [Model].
4//!
5//! Any structure that implements [Task], and its super trait [Check], can be part of an actors [Model].
6//!
7//! [Model]: crate::model::Model
8
9use std::path::PathBuf;
10
11use crate::graph::GraphError;
12use crate::model::{Model, UnknownOrReady};
13use crate::system::System;
14use crate::{
15    actor::PlainActor,
16    graph::{self, Graph},
17    model::{self, PlainModel},
18    ActorError,
19};
20
21#[derive(Debug, thiserror::Error)]
22pub enum CheckError {
23    #[error("error in Task from Actor")]
24    FromActor(#[from] ActorError),
25    #[error("error in Task from Model")]
26    FromModel(#[from] model::ModelError),
27}
28
29/// Interface for model verification routines
30///
31pub trait Check {
32    /// Validates the inputs
33    ///
34    /// Returns en error if there are some inputs but the inputs rate is zero
35    /// or if there are no inputs and the inputs rate is positive
36    fn check_inputs(&self) -> std::result::Result<(), CheckError>;
37    /// Validates the outputs
38    ///
39    /// Returns en error if there are some outputs but the outputs rate is zero
40    /// or if there are no outputs and the outputs rate is positive
41    fn check_outputs(&self) -> std::result::Result<(), CheckError>;
42    /// Return the number of inputs
43    fn n_inputs(&self) -> usize;
44    /// Return the number of outputs
45    fn n_outputs(&self) -> usize;
46    /// Return the hash # of inputs
47    fn inputs_hashes(&self) -> Vec<u64>;
48    /// Return the hash # of outputs
49    fn outputs_hashes(&self) -> Vec<u64>;
50    fn _as_plain(&self) -> PlainActor;
51    fn is_system(&self) -> bool {
52        false
53    }
54}
55
56#[derive(Debug, thiserror::Error)]
57pub enum TaskError {
58    #[error("error in Task from Actor")]
59    FromActor(#[from] ActorError),
60    #[error("error in Task from Model")]
61    FromModel(#[from] model::ModelError),
62}
63
64/// Interface for running model components
65#[async_trait::async_trait]
66pub trait Task: Check + std::fmt::Display + Send + Sync {
67    /// Runs the [Actor](crate::actor::Actor) infinite loop
68    ///
69    /// The loop ends when the client data is [None] or when either the sending of receiving
70    /// end of a channel is dropped
71    async fn async_run(&mut self) -> std::result::Result<(), TaskError>;
72    /// Run the actor loop in a dedicated thread
73    fn spawn(self) -> tokio::task::JoinHandle<std::result::Result<(), TaskError>>
74    where
75        Self: Sized + 'static,
76    {
77        tokio::spawn(async move { Box::new(self).task().await })
78    }
79    /// Run the actor loop
80    async fn task(self: Box<Self>) -> std::result::Result<(), TaskError>;
81    fn as_plain(&self) -> PlainActor;
82}
83
84/// Flowchart name
85pub trait GetName {
86    /// Returns the flowchart name
87    fn get_name(&self) -> String {
88        "integrated_model".into()
89    }
90}
91
92#[derive(Debug, thiserror::Error)]
93pub enum FlowChartError {
94    #[error("no graph to walk, may be there is no actors!")]
95    NoGraph,
96    #[error("failed to write SVG charts")]
97    Rendering(#[from] graph::RenderError),
98    #[error("failed to process graph")]
99    Graph(#[from] GraphError),
100}
101
102/// Actors flowchart interface
103pub trait FlowChart: GetName {
104    /// Returns the actors network graph
105    fn graph(&self) -> Option<Graph>;
106    /// Writes the flowchart to an HTML file
107    ///
108    /// Optionnaly, one can get the [Graphviz](https://www.graphviz.org/) dot files to
109    /// be written as well by setting the environment variable `TO_DOT` to 1.
110    fn to_html(&self) -> std::result::Result<PathBuf, FlowChartError> {
111        Ok(self
112            .graph()
113            .ok_or(FlowChartError::NoGraph)?
114            .to_dot()?
115            .walk()
116            .into_svg()?
117            .to_html()?)
118    }
119
120    /// Writes the actors flowchart to an HTML file
121    ///
122    /// The flowchart file is written either in the current directory
123    /// or in the directory give by the environment variable `DATA_REPO`.
124    /// The flowchart is created with [Graphviz](https://www.graphviz.org/) neato filter,
125    /// other filters can be specified with the environment variable `FLOWCHART`
126    fn flowchart(self) -> Self
127    where
128        Self: Sized,
129    {
130        if let Err(e) = self.to_html() {
131            println!("failed to write flowchart Web page caused by:\n {e:?}");
132        }
133        self
134    }
135    /// Writes the actors flowchart to an HTML file and open it in the default browser
136    fn flowchart_open(self) -> Self
137    where
138        Self: Sized,
139    {
140        match self.to_html() {
141            Ok(path) => {
142                if let Err(e) = open::that(path) {
143                    println!("failed to open flowchart Web page caused by:\n {e:?}");
144                }
145            }
146            Err(e) => println!("failed to write flowchart Web page caused by:\n {e:?}"),
147        };
148        self
149    }
150}
151impl<S: UnknownOrReady> FlowChart for Model<S>
152// where
153//     for<'a> &'a T: IntoIterator<Item = PlainActor>,
154{
155    fn graph(&self) -> Option<Graph> {
156        // let actors: Vec<_> = self.into_iter().collect();
157        let actors = PlainModel::from_iter(self);
158        if actors.is_empty() {
159            None
160        } else {
161            Some(Graph::new(self.get_name(), actors))
162        }
163    }
164    /*
165    fn flowchart(self) -> Self {
166        match self.graph() {
167            None => println!("no graph to make, may be there is no actors!"),
168            Some(graph) => match graph.walk().into_svg() {
169                Ok(r) => {
170                    if let Err(e) = r.to_html() {
171                        println!("failed to write flowchart Web page caused by:\n {e}");
172                    }
173                }
174                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
175            },
176        }
177        self
178    } */
179
180    /*     fn flowchart_open(self) -> Self {
181        match self.graph() {
182            None => println!("no graph to make, may be there is no actors!"),
183            Some(graph) => match graph.walk().into_svg() {
184                Ok(r) => match r.to_html() {
185                    Ok(path) => {
186                        if let Err(e) = open::that(path) {
187                            println!("failed to open flowchart Web page caused by:\n {e}");
188                        }
189                    }
190                    Err(e) => println!("failed to write flowchart Web page caused by:\n {e}"),
191                },
192                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
193            },
194        }
195        self
196    }*/
197}
198
199// pub trait SystemFlowChart {
200//     fn graph(&self) -> Option<Graph>;
201//     // fn flowchart(&self) -> &Self;
202// }
203impl<T: System> FlowChart for T
204where
205    for<'a> &'a T: IntoIterator<Item = Box<&'a dyn Check>>,
206{
207    fn graph(&self) -> Option<Graph> {
208        // let actors: Vec<_> = self.into_iter().map(|x| x._as_plain()).collect();
209        let actors = PlainModel::from_iter(self);
210        if actors.is_empty() {
211            None
212        } else {
213            Some(Graph::new(self.get_name(), actors))
214        }
215    }
216
217    /*     fn flowchart(&self) -> &Self {
218        match self.graph() {
219            None => println!("no graph to make, may be there is no actors!"),
220            Some(graph) => match graph.walk().into_svg() {
221                Ok(r) => {
222                    if let Err(e) = r.to_html() {
223                        println!("failed to write flowchart Web page caused by:\n {e}");
224                    }
225                }
226                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
227            },
228        }
229        &self
230    } */
231}
232
233#[cfg(test)]
234mod tests {
235    use std::process::{Command, Stdio};
236    #[test]
237    fn pipe() {
238        let graph = Command::new("echo")
239            .arg(r#"digraph G { a -> b }"#)
240            .stdout(Stdio::piped())
241            .spawn()
242            .unwrap();
243        let svg = Command::new("dot")
244            .arg("-Tsvg")
245            .stdin(Stdio::from(graph.stdout.unwrap()))
246            .stdout(Stdio::piped())
247            .spawn()
248            .unwrap();
249        let output = svg.wait_with_output().unwrap();
250        let result = std::str::from_utf8(&output.stdout).unwrap();
251        let svg = result.lines().skip(6).collect::<Vec<_>>().join("");
252        println!("{:#}", &svg);
253    }
254}