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}