hgame 0.26.4

CG production management structs, e.g. of assets, personnels, progress, etc.
Documentation
use super::*;
use mkutil::daggy::{Dag, EdgeIndex, NodeIndex, Walker};

// -------------------------------------------------------------------------------
#[derive(Debug, Clone)]
pub struct Template {
    pub(super) graph: StageGraph,

    /// Errors resulted from each `Self::graph_mut_from_stages` invocation are stored here.
    pub(super) graph_err: Option<Vec<Result<(), GraphError>>>,

    /// Simple boolean for efficient UI display.
    graph_no_err: bool,

    /// Topologically sorted `Stage`s, cloned for efficient display to UI.
    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();
        }
    }

    /// Iterates over the given `stages` -- normally populated via deserialization --
    /// from start to finish and constructs the internal DAG.
    /// Some of the described `Stage`s can be wrong, and we're
    /// storing the errors encountered in `Self::graph_err` for ease of debugging.
    /// NOTE: This expects a root node already exists in the 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);
    }

    /// Lists stages in order from graph.
    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![];
        // lists from the graph root
        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)]
/// The Directed Acyclic Graph that is constructed from user-defined
/// `Stage`s and their dependencies.
pub(super) struct StageGraph {
    dag: Dag<Stage, ()>,
    /// Put behind an `Option` because using a `NodeIndex` default of `0` doesn't work:
    /// it causes app freeze while adding nodes and forgetting to mutate the root prior to that.
    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),
        }
    }

    /// `NodeIndex`s of all children from the graph root.
    fn children(&self) -> Vec<NodeIndex> {
        match self.root {
            Some(idx) => self
                .dag
                .children(idx)
                .iter(&self.dag)
                .map(|(_, n)| n)
                .collect(),
            None => vec![],
        }
    }

    /// Adds a node with `Self::root` as parent.
    fn add_first_child(&mut self, child: Stage) -> Option<(EdgeIndex, NodeIndex)> {
        self.root.map(|idx| self.dag.add_child(idx, (), child))
    }

    /// Adds a node whose parents' names are known,
    /// by looking at the `Stage::depends_on` definition.
    /// Some of the described dependencies can be wrong, so we're
    /// storing the errors encountered for ease of debugging.
    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,
        }
    }

    /// Adds any non-root `Stage`s to the DAG.
    /// Returns sequence of `Result`s of all dependencies described in the given `Stage`.
    /// NOTE: This expects a root node already exists.
    fn add_node(&mut self, stage: Stage) -> Vec<Result<(), GraphError>> {
        // avoids name duplication
        if let Some(_) = self.find_node_by_name(stage.name()) {
            return vec![Err(GraphError::StageNameDuplication(stage.name))];
        };

        if stage.depends_on.is_empty() {
            // just a simple add
            match self.add_first_child(stage) {
                Some(_) => {
                    // added successfully
                    vec![Ok(())]
                }
                None => {
                    vec![Err(GraphError::NonExistentRoot)]
                }
            }
        } else {
            // adds node AND described edges
            self.add_dependent_child(stage)
        }
    }

    /// Returns the first encountered `Err` of the stage adding.
    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();
    }
}