use std::{env, path::Path, process::Command};
use crate::{actor::PlainActor, graph::Graph, model, system::System, 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),
}
#[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;
}
pub trait GetName {
fn get_name(&self) -> String {
"integrated_model".into()
}
}
pub trait FlowChart: GetName {
fn graph(&self) -> Option<Graph>;
fn flowchart(self) -> Self;
}
impl<T: GetName> FlowChart for T
where
for<'a> &'a T: IntoIterator<Item = PlainActor>,
{
fn graph(&self) -> Option<Graph> {
let actors: Vec<_> = self.into_iter().collect();
if actors.is_empty() {
None
} else {
Some(Graph::new(actors))
}
}
fn flowchart(self) -> Self {
let name = self.get_name();
let root_env = env::var("DATA_REPO").unwrap_or_else(|_| ".".to_string());
let path = Path::new(&root_env).join(&name);
if let Some(graph) = self.graph() {
match graph.to_dot(path.with_extension("dot")) {
Ok(_) => {
if let Err(e) =
Command::new(env::var("FLOWCHART").unwrap_or("neato".to_string()))
.arg("-Gstart=rand")
.arg("-Tsvg")
.arg("-O")
.arg(path.with_extension("dot").to_str().unwrap())
.output()
{
println!(
"Failed to convert Graphviz dot file {path:?} to SVG image with {e}"
)
}
}
Err(e) => println!("Failed to write Graphviz dot file {path:?} with {e}"),
}
}
self
}
}
pub trait SystemFlowChart {
fn graph(&self) -> Option<Graph>;
fn flowchart(&self) -> &Self;
}
impl<T: System> SystemFlowChart for T
where
for<'a> &'a T: IntoIterator<Item = Box<&'a dyn Check>>,
{
fn graph(&self) -> Option<Graph> {
let actors: Vec<_> = self.into_iter().map(|x| x._as_plain()).collect();
if actors.is_empty() {
None
} else {
Some(Graph::new(actors))
}
}
fn flowchart(&self) -> &Self {
let name = self.get_name();
let root_env = env::var("DATA_REPO").unwrap_or_else(|_| ".".to_string());
let path = Path::new(&root_env).join(&name);
if let Some(graph) = self.graph() {
match graph.to_dot(path.with_extension("dot")) {
Ok(_) => {
if let Err(e) =
Command::new(env::var("FLOWCHART").unwrap_or("neato".to_string()))
.arg("-Gstart=rand")
.arg("-Tsvg")
.arg("-O")
.arg(path.with_extension("dot").to_str().unwrap())
.output()
{
println!(
"Failed to convert Graphviz dot file {path:?} to SVG image with {e}"
)
}
}
Err(e) => println!("Failed to write Graphviz dot file {path:?} with {e}"),
}
}
&self
}
}