use super::*;
use mkutil::daggy::{Dag, EdgeIndex, NodeIndex, Walker};
#[derive(Debug, Clone)]
pub struct Template {
pub(super) graph: StageGraph,
pub(super) graph_err: Option<Vec<Result<(), GraphError>>>,
graph_no_err: bool,
pub stage_order: Vec<Stage>,
}
impl Default for Template {
fn default() -> Self {
Self::empty()
}
}
impl Template {
pub(super) fn empty() -> Self {
Self {
graph: StageGraph::empty(),
graph_err: None,
graph_no_err: false,
stage_order: vec![],
}
}
pub(super) fn with_root(name: &str, description: &str) -> Self {
Self {
graph: StageGraph::with_root(name, description),
..Self::empty()
}
}
fn err_mut(&mut self, graph_results: Vec<Result<(), GraphError>>) {
self.graph_no_err = !graph_results.iter().any(|r| r.is_err());
self.graph_err = Some(graph_results);
#[cfg(debug_assertions)]
{
self.print_graph_err();
self.print_dag();
}
}
pub(super) fn graph_mut_from_stages(&mut self, stages: &[Option<Stage>]) {
let mut results: Vec<Result<(), GraphError>> = vec![];
for stage in stages.iter() {
match stage {
Some(s) => {
results.push(self.graph.add_node_any_error(s.clone()));
}
None => {
results.push(Err(GraphError::StageIsNone));
}
}
}
self.err_mut(results);
}
pub(super) fn rewrite_graph_from_stages(&mut self, stages: &[&Stage]) {
let mut results: Vec<Result<(), GraphError>> = vec![];
stages.iter().for_each(|s| {
results.push(self.graph.add_node_any_error((*s).clone()));
});
self.err_mut(results);
}
pub(super) fn stage_order_mut(&mut self) {
self.stage_order = self
.list_stages()
.into_iter()
.flatten()
.map(|s| s.clone())
.collect();
}
fn list_stages(&self) -> Vec<Vec<&Stage>> {
let mut tree: Vec<Vec<&Stage>> = vec![];
dag::list_nodes_in_order(&self.graph.dag, &self.graph.children(), &mut tree);
tree
}
#[cfg(debug_assertions)]
fn print_graph_err(&self) {
self.graph_err
.as_ref()
.unwrap()
.iter()
.for_each(|r| match r {
Ok(_) => {
eprintln!("Added Stage to dependency graph successfully");
}
Err(e) => {
eprintln!("Graph mut failed: {}", e);
}
});
}
#[cfg(debug_assertions)]
fn print_dag(&self) {
eprintln!("DAG: {:?}", self.graph.dag);
}
#[cfg(feature = "gui")]
pub(super) fn graph_err_ui(&self, ui: &mut egui::Ui) {
if self.graph_no_err {
ui.colored_label(Color32::LIGHT_GREEN, "⏺")
.on_hover_text("Dependency graph contains no errors");
} else {
ui.colored_label(Color32::RED, "🚫").on_hover_text(
if let Some(results) = &self.graph_err {
results
.iter()
.filter(|r| r.is_err())
.map(|r| format!("{}", r.as_ref().err().unwrap()))
.collect::<Vec<String>>()
.join("\n")
} else {
String::from("Unknown errors occurred in Stage dependency graph")
},
);
};
}
#[cfg(feature = "gui")]
pub(super) fn stage_order_vertical_ui(
&mut self,
ui: &mut egui::Ui,
id_source: impl std::hash::Hash,
) {
egui::CollapsingHeader::new("Stages in order")
.id_source(id_source)
.show(ui, |ui| {
self.stage_order.iter_mut().for_each(|s| {
s.read_mode_ui(ui);
});
});
}
}
#[derive(Debug, Clone)]
pub(super) struct StageGraph {
dag: Dag<Stage, ()>,
root: Option<NodeIndex>,
}
impl StageGraph {
fn empty() -> Self {
Self {
dag: Dag::<Stage, ()>::new(),
root: None,
}
}
pub(super) fn with_root(root_name: &str, description: &str) -> Self {
let mut stages = Dag::<Stage, ()>::new();
let root = stages.add_node(Stage::no_dependency(root_name, description));
Self {
dag: stages,
root: Some(root),
}
}
fn children(&self) -> Vec<NodeIndex> {
match self.root {
Some(idx) => self
.dag
.children(idx)
.iter(&self.dag)
.map(|(_, n)| n)
.collect(),
None => vec![],
}
}
fn add_first_child(&mut self, child: Stage) -> Option<(EdgeIndex, NodeIndex)> {
self.root.map(|idx| self.dag.add_child(idx, (), child))
}
fn add_dependent_child(&mut self, child: Stage) -> Vec<Result<(), GraphError>> {
let child_idx = self.dag.add_node(child.clone());
let mut results: Vec<Result<(), GraphError>> = vec![];
child.depends_on.into_iter().for_each(|parent_name| {
match self.find_node_by_name(&parent_name) {
Some((parent_idx, _)) => match self.dag.add_edge(parent_idx, child_idx, ()) {
Ok(_) => {
results.push(Ok(()));
}
Err(e) => {
results.push(Err(GraphError::CycleEdge(
parent_name,
child.name.to_owned(),
e.to_string(),
)));
}
},
None => {
results.push(Err(GraphError::UndefinedDependency(
parent_name,
child.name.to_owned(),
)));
}
};
});
results
}
fn find_node_by_name(&self, name: &str) -> Option<(NodeIndex, &Stage)> {
match self.root {
Some(idx) => dag::find_by_name(&self.dag, name, idx),
None => None,
}
}
fn add_node(&mut self, stage: Stage) -> Vec<Result<(), GraphError>> {
if let Some(_) = self.find_node_by_name(stage.name()) {
return vec![Err(GraphError::StageNameDuplication(stage.name))];
};
if stage.depends_on.is_empty() {
match self.add_first_child(stage) {
Some(_) => {
vec![Ok(())]
}
None => {
vec![Err(GraphError::NonExistentRoot)]
}
}
} else {
self.add_dependent_child(stage)
}
}
fn add_node_any_error(&mut self, stage: Stage) -> Result<(), GraphError> {
match self
.add_node(stage)
.into_iter()
.filter(|r| r.is_err())
.next()
{
Some(e) => Err(e.err().unwrap()),
None => Ok(()),
}
}
}
impl Default for StageGraph {
fn default() -> Self {
Self::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn template() -> DeliveryTemplate {
DeliveryTemplate::new("PRK internal", "Applied to asset team")
}
#[test]
fn two_simple_stages() {
let mut deli = template();
deli.add_stage(Stage::no_dependency("Model", "Done in Maya and Mudbox"));
deli.add_stage(
Stage::no_dependency("Texture", "Done in Substance instead of Mari")
.depends_on("Model"),
);
deli.construct_stage_graph();
}
#[test]
fn undefined_stage() {
let mut deli = template();
deli.add_stage(
Stage::no_dependency("Stage 2", "Done in Substance instead of Mari")
.depends_on("Stage 1"),
);
deli.construct_stage_graph();
}
#[test]
fn complex_deps() {
let mut deli = template();
deli.add_stage(Stage::no_dependency("Sculpt", "Block in ZBrush"));
deli.add_stage(
Stage::no_dependency("Texture", "BaseColor First Pass").depends_on("Sculpt"),
);
deli.add_stage(
Stage::no_dependency("Rig", "With Houdini KineFX")
.depends_on("Sculpt")
.depends_on("Retopology")
.depends_on("Texture"),
);
deli.add_stage(Stage::no_dependency("Animation", "Inside UE").depends_on("Rig"));
deli.add_stage(
Stage::no_dependency("VFX", "With Niagara")
.depends_on("Sculpt")
.depends_on("Animation"),
);
deli.add_stage(Stage::no_dependency("Sculpt", "Detailed in 3D Coat"));
deli.construct_stage_graph();
}
}