Skip to main content

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    #[error(transparent)]
63    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
64}
65
66/// Interface for running model components
67#[async_trait::async_trait]
68pub trait Task: Check + std::fmt::Display + Send + Sync {
69    /// Runs the [Actor](crate::actor::Actor) infinite loop
70    ///
71    /// The loop ends when the client data is [None] or when either the sending of receiving
72    /// end of a channel is dropped
73    async fn async_run(&mut self) -> std::result::Result<(), TaskError>;
74    /// Run the actor loop in a dedicated thread
75    fn spawn(self) -> tokio::task::JoinHandle<std::result::Result<(), TaskError>>
76    where
77        Self: Sized + 'static,
78    {
79        tokio::spawn(async move { Box::new(self).task().await })
80    }
81    /// Run the actor loop
82    async fn task(self: Box<Self>) -> std::result::Result<(), TaskError>;
83    fn as_plain(&self) -> PlainActor;
84    fn name(&self) -> &'static str {
85        "dos-actors task"
86    }
87}
88
89/// Flowchart name
90pub trait GetName {
91    /// Returns the flowchart name
92    fn get_name(&self) -> String {
93        "integrated_model".into()
94    }
95}
96
97#[derive(Debug, thiserror::Error)]
98pub enum FlowChartError {
99    #[error("no graph to walk, may be there is no actors!")]
100    NoGraph,
101    #[error("failed to write SVG charts")]
102    Rendering(#[from] graph::RenderError),
103    #[error("failed to process graph")]
104    Graph(#[from] GraphError),
105}
106
107/// Actors flowchart interface
108pub trait FlowChart: GetName {
109    /// Returns the actors network graph
110    fn graph(&self) -> Option<Graph>;
111    /// Writes the flowchart to an HTML file
112    ///
113    /// Optionnaly, one can get the [Graphviz](https://www.graphviz.org/) dot files to
114    /// be written as well by setting the environment variable `TO_DOT` to 1.
115    fn to_html(&self) -> std::result::Result<PathBuf, FlowChartError> {
116        Ok(self
117            .graph()
118            .ok_or(FlowChartError::NoGraph)?
119            .to_dot()?
120            .walk()
121            .into_svg()?
122            .to_html()?)
123    }
124
125    /// Writes the actors flowchart to an HTML file
126    ///
127    /// The flowchart file is written either in the current directory
128    /// or in the directory give by the environment variable `DATA_REPO`.
129    /// The flowchart is created with [Graphviz](https://www.graphviz.org/) neato filter,
130    /// other filters can be specified with the environment variable `FLOWCHART`
131    fn flowchart(self) -> Self
132    where
133        Self: Sized,
134    {
135        if let Err(e) = self.to_html() {
136            println!("failed to write flowchart Web page caused by:\n {e:?}");
137        }
138        self
139    }
140    /// Writes the actors flowchart to an HTML file and open it in the default browser
141    fn flowchart_open(self) -> Self
142    where
143        Self: Sized,
144    {
145        match self.to_html() {
146            Ok(path) => {
147                if let Err(_) = open::that(&path) {
148                    // println!("failed to open flowchart Web page caused by:\n {e:?}");
149
150                    log::info!("model flowchart written to {path:?}");
151                }
152            }
153            Err(e) => println!("failed to write flowchart Web page caused by:\n {e:?}"),
154        };
155        self
156    }
157}
158impl<S: UnknownOrReady> FlowChart for Model<S>
159// where
160//     for<'a> &'a T: IntoIterator<Item = PlainActor>,
161{
162    fn graph(&self) -> Option<Graph> {
163        // let actors: Vec<_> = self.into_iter().collect();
164        let actors = PlainModel::from_iter(self);
165        if actors.is_empty() {
166            None
167        } else {
168            Some(Graph::new(self.get_name(), actors))
169        }
170    }
171    /*
172    fn flowchart(self) -> Self {
173        match self.graph() {
174            None => println!("no graph to make, may be there is no actors!"),
175            Some(graph) => match graph.walk().into_svg() {
176                Ok(r) => {
177                    if let Err(e) = r.to_html() {
178                        println!("failed to write flowchart Web page caused by:\n {e}");
179                    }
180                }
181                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
182            },
183        }
184        self
185    } */
186
187    /*     fn flowchart_open(self) -> Self {
188        match self.graph() {
189            None => println!("no graph to make, may be there is no actors!"),
190            Some(graph) => match graph.walk().into_svg() {
191                Ok(r) => match r.to_html() {
192                    Ok(path) => {
193                        if let Err(e) = open::that(path) {
194                            println!("failed to open flowchart Web page caused by:\n {e}");
195                        }
196                    }
197                    Err(e) => println!("failed to write flowchart Web page caused by:\n {e}"),
198                },
199                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
200            },
201        }
202        self
203    }*/
204}
205
206// pub trait SystemFlowChart {
207//     fn graph(&self) -> Option<Graph>;
208//     // fn flowchart(&self) -> &Self;
209// }
210impl<T: System> FlowChart for T
211where
212    for<'a> &'a T: IntoIterator<Item = Box<&'a dyn Check>>,
213{
214    fn graph(&self) -> Option<Graph> {
215        // let actors: Vec<_> = self.into_iter().map(|x| x._as_plain()).collect();
216        let actors = PlainModel::from_iter(self);
217        if actors.is_empty() {
218            None
219        } else {
220            Some(Graph::new(self.get_name(), actors))
221        }
222    }
223
224    /*     fn flowchart(&self) -> &Self {
225        match self.graph() {
226            None => println!("no graph to make, may be there is no actors!"),
227            Some(graph) => match graph.walk().into_svg() {
228                Ok(r) => {
229                    if let Err(e) = r.to_html() {
230                        println!("failed to write flowchart Web page caused by:\n {e}");
231                    }
232                }
233                Err(e) => println!("failed to write SVG charts caused by:\n {e}"),
234            },
235        }
236        &self
237    } */
238}
239
240#[cfg(test)]
241mod tests {
242    use std::process::{Command, Stdio};
243    #[test]
244    fn pipe() {
245        let graph = Command::new("echo")
246            .arg(r#"digraph G { a -> b }"#)
247            .stdout(Stdio::piped())
248            .spawn()
249            .unwrap();
250        let svg = Command::new("dot")
251            .arg("-Tsvg")
252            .stdin(Stdio::from(graph.stdout.unwrap()))
253            .stdout(Stdio::piped())
254            .spawn()
255            .unwrap();
256        let output = svg.wait_with_output().unwrap();
257        let result = std::str::from_utf8(&output.stdout).unwrap();
258        let svg = result.lines().skip(6).collect::<Vec<_>>().join("");
259        println!("{:#}", &svg);
260    }
261}