capability-core-string-skeleton 0.1.0

A Rust crate providing a framework to generate and manipulate skill tree skeletons using string-based node representations. It aggregates dispatch and leaf holder nodes into scalable domain models.
Documentation
// ---------------- [ File: capability-core-string-skeleton/src/core_string_skeleton.rs ]
crate::ix!();

/// To generate this structure, we determine each node in the stripped-down model.
///
#[derive(Hash,SaveLoad,PartialEq,Eq,AiJsonTemplate,Serialize,Deserialize,Getters, Setters, Builder, Debug, Clone)]
#[getset(get = "pub", set = "pub")]
pub struct CoreStringSkeleton {

    /// This field holds *verbatim* the lower-kebab-case target-name belonging to the tree grow process.
    #[builder(default = "\"target\".to_string()")]
    #[serde(default)]
    target_name: String,

    #[builder(default)]
    dispatch_nodes:  Vec<CoreSkeletalDispatchNode>,

    #[builder(default)]
    aggregate_nodes:  Vec<CoreSkeletalAggregateNode>,

    #[builder(default)]
    leaf_holder_nodes: Vec<CoreSkeletalLeafHolderNode>,
}

impl CoreStringSkeleton {
    /// Sorts **all** node vectors in‑place so that every parent precedes its
    /// descendants—no matter whether the root is a *dispatch* **or** an
    /// *aggregate* node.
    ///
    /// The algorithm:
    /// 1. Builds an adjacency map from `children`.
    /// 2. Performs a breadth‑first walk from every root to assign a depth to
    ///    each node name.
    /// 3. Combines *all* nodes into a single list, sorts globally by
    ///    `(depth, name)`, then writes the results back into the three
    ///    category vectors.  
    /// 4. Cycles or dangling references are logged and placed **after** all
    ///    reachable nodes.
    ///
    /// Complexity is **O(n log n)**.
    #[instrument(level = "trace", skip_all)]
    pub fn sort_in_place_top_down(&mut self) {
        use std::collections::{HashMap, HashSet, VecDeque};

        // ---------- gather adjacency + child set ----------
        let mut child_set = HashSet::<String>::new();
        let mut adj: HashMap<String, Vec<String>> = HashMap::new();

        for n in &self.dispatch_nodes {
            let entry = adj.entry(n.name().clone()).or_default();
            for ch in n.children() {
                child_set.insert(ch.name().clone());
                entry.push(ch.name().clone());
            }
        }
        for n in &self.aggregate_nodes {
            let entry = adj.entry(n.name().clone()).or_default();
            for ch in n.children() {
                child_set.insert(ch.name().clone());
                entry.push(ch.name().clone());
            }
        }

        // ---------- collect all node names ----------
        let mut all_names = HashSet::<String>::new();
        for n in &self.dispatch_nodes   { all_names.insert(n.name().clone()); }
        for n in &self.aggregate_nodes  { all_names.insert(n.name().clone()); }
        for n in &self.leaf_holder_nodes{ all_names.insert(n.name().clone()); }

        // ---------- identify roots ----------
        let roots: Vec<String> = all_names.iter()
            .filter(|n| !child_set.contains(*n))
            .cloned()
            .collect();
        trace!(?roots, "identified root nodes");

        // ---------- breadth‑first depth assignment ----------
        let mut depth: HashMap<String, usize> = HashMap::new();
        let mut q: VecDeque<(String, usize)> = VecDeque::new();
        for r in &roots { q.push_back((r.clone(), 0)); }

        while let Some((name, d)) = q.pop_front() {
            if depth.contains_key(&name) { continue; }
            depth.insert(name.clone(), d);
            for ch in adj.get(&name).unwrap_or(&Vec::new()) {
                q.push_back((ch.clone(), d + 1));
            }
        }
        let fallback = depth.values().copied().max().unwrap_or(0) + 1;
        for n in &all_names { depth.entry(n.clone()).or_insert(fallback); }

        // ---------- global sort across *all* node categories ----------
        enum NodeWrap {
            Dispatch(CoreSkeletalDispatchNode),
            Aggregate(CoreSkeletalAggregateNode),
            Leaf(CoreSkeletalLeafHolderNode),
        }

        let mut combined: Vec<NodeWrap> = Vec::new();
        combined.extend(std::mem::take(&mut self.dispatch_nodes)
            .into_iter()
            .map(NodeWrap::Dispatch));
        combined.extend(std::mem::take(&mut self.aggregate_nodes)
            .into_iter()
            .map(NodeWrap::Aggregate));
        combined.extend(std::mem::take(&mut self.leaf_holder_nodes)
            .into_iter()
            .map(NodeWrap::Leaf));

        let name_of = |n: &NodeWrap| -> String {
            match n {
                NodeWrap::Dispatch(d)  => d.name().to_string(),
                NodeWrap::Aggregate(a) => a.name().to_string(),
                NodeWrap::Leaf(l)      => l.name().to_string(),
            }
        };

        combined.sort_by(|a, b| {
            depth[&name_of(a)]
                .cmp(&depth[&name_of(b)])
                .then(name_of(a).cmp(&name_of(b)))
        });

        // ---------- write the sorted order back ----------
        let mut dispatch_sorted   = Vec::new();
        let mut aggregate_sorted  = Vec::new();
        let mut leaf_sorted       = Vec::new();

        for n in combined {
            match n {
                NodeWrap::Dispatch(d)  => dispatch_sorted.push(d),
                NodeWrap::Aggregate(a) => aggregate_sorted.push(a),
                NodeWrap::Leaf(l)      => leaf_sorted.push(l),
            }
        }

        self.dispatch_nodes    = dispatch_sorted;
        self.aggregate_nodes   = aggregate_sorted;
        self.leaf_holder_nodes = leaf_sorted;

        trace!("CoreStringSkeleton fully sorted top‑down across all node categories.");
    }
}