use std::path::PathBuf;
use crate::graph::GraphError;
use crate::model::{Model, UnknownOrReady};
use crate::system::System;
use crate::{
actor::PlainActor,
graph::{self, Graph},
model::{self, PlainModel},
ActorError,
};
#[derive(Debug, thiserror::Error)]
pub enum CheckError {
#[error("error in Task from Actor")]
FromActor(#[from] ActorError),
#[error("error in Task from Model")]
FromModel(#[from] model::ModelError),
}
pub trait Check {
fn check_inputs(&self) -> std::result::Result<(), CheckError>;
fn check_outputs(&self) -> std::result::Result<(), CheckError>;
fn n_inputs(&self) -> usize;
fn n_outputs(&self) -> usize;
fn inputs_hashes(&self) -> Vec<u64>;
fn outputs_hashes(&self) -> Vec<u64>;
fn _as_plain(&self) -> PlainActor;
fn is_system(&self) -> bool {
false
}
}
#[derive(Debug, thiserror::Error)]
pub enum TaskError {
#[error("error in Task from Actor")]
FromActor(#[from] ActorError),
#[error("error in Task from Model")]
FromModel(#[from] model::ModelError),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error + Send + Sync>),
}
#[async_trait::async_trait]
pub trait Task: Check + std::fmt::Display + Send + Sync {
async fn async_run(&mut self) -> std::result::Result<(), TaskError>;
fn spawn(self) -> tokio::task::JoinHandle<std::result::Result<(), TaskError>>
where
Self: Sized + 'static,
{
tokio::spawn(async move { Box::new(self).task().await })
}
async fn task(self: Box<Self>) -> std::result::Result<(), TaskError>;
fn as_plain(&self) -> PlainActor;
fn name(&self) -> &'static str {
"dos-actors task"
}
}
pub trait GetName {
fn get_name(&self) -> String {
"integrated_model".into()
}
}
#[derive(Debug, thiserror::Error)]
pub enum FlowChartError {
#[error("no graph to walk, may be there is no actors!")]
NoGraph,
#[error("failed to write SVG charts")]
Rendering(#[from] graph::RenderError),
#[error("failed to process graph")]
Graph(#[from] GraphError),
}
pub trait FlowChart: GetName {
fn graph(&self) -> Option<Graph>;
fn to_html(&self) -> std::result::Result<PathBuf, FlowChartError> {
Ok(self
.graph()
.ok_or(FlowChartError::NoGraph)?
.to_dot()?
.walk()
.into_svg()?
.to_html()?)
}
fn flowchart(self) -> Self
where
Self: Sized,
{
if let Err(e) = self.to_html() {
println!("failed to write flowchart Web page caused by:\n {e:?}");
}
self
}
fn flowchart_open(self) -> Self
where
Self: Sized,
{
match self.to_html() {
Ok(path) => {
if let Err(_) = open::that(&path) {
log::info!("model flowchart written to {path:?}");
}
}
Err(e) => println!("failed to write flowchart Web page caused by:\n {e:?}"),
};
self
}
}
impl<S: UnknownOrReady> FlowChart for Model<S>
{
fn graph(&self) -> Option<Graph> {
let actors = PlainModel::from_iter(self);
if actors.is_empty() {
None
} else {
Some(Graph::new(self.get_name(), actors))
}
}
}
impl<T: System> FlowChart for T
where
for<'a> &'a T: IntoIterator<Item = Box<&'a dyn Check>>,
{
fn graph(&self) -> Option<Graph> {
let actors = PlainModel::from_iter(self);
if actors.is_empty() {
None
} else {
Some(Graph::new(self.get_name(), actors))
}
}
}
#[cfg(test)]
mod tests {
use std::process::{Command, Stdio};
#[test]
fn pipe() {
let graph = Command::new("echo")
.arg(r#"digraph G { a -> b }"#)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let svg = Command::new("dot")
.arg("-Tsvg")
.stdin(Stdio::from(graph.stdout.unwrap()))
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = svg.wait_with_output().unwrap();
let result = std::str::from_utf8(&output.stdout).unwrap();
let svg = result.lines().skip(6).collect::<Vec<_>>().join("");
println!("{:#}", &svg);
}
}