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}