capability-skeleton 0.1.0

A Rust library for managing and building complex hierarchical tree structures such as skill trees. Supports serialization, error handling, and deep tree metrics.
Documentation
// ---------------- [ File: capability-skeleton/src/build_node_level_map.rs ]
crate::ix!();

/// A local BFS returning node_id => level, skipping unconnected nodes.
#[instrument(level = "trace", skip(skel))]
pub fn build_node_level_map(skel: &Skeleton) -> HashMap<u16, u8> {
    trace!("Building node->level map with BFS for Skeleton.");

    let mut visited = HashSet::new();
    let mut map = HashMap::<u16, u8>::new();
    let mut queue = VecDeque::new();

    if let Some(rid) = skel.root_id() {
        // rid is a u16, so we can push it directly with level=0
        queue.push_back((*rid, 0u8));
    } else {
        debug!("No root => empty map");
        return map;
    }

    while let Some((nid, lvl)) = queue.pop_front() {
        // If we’ve visited nid before, skip
        if !visited.insert(nid) {
            continue;
        }
        // Insert into the map
        map.insert(nid, lvl);

        // Find the node with ID == nid
        if let Some(node) = skel.nodes().iter().find(|n| n.id() == nid) {
            // For each child, push => BFS
            for &child_id in node.child_ids() {
                let next_lvl = lvl.saturating_add(1);
                queue.push_back((child_id, next_lvl));
            }
        }
    }

    trace!("Finished BFS => discovered {} connected nodes", map.len());
    map
}

#[cfg(test)]
mod build_node_level_map_assessment {
    use super::*;

    fn make_node(id: u16, child_ids: &[u16]) -> SkeletonNode {
        let k = if child_ids.is_empty() {
            NodeKind::LeafHolder
        } else {
            NodeKind::Dispatch
        };
        SkeletonNodeBuilder::default()
            .id(id)
            .child_ids(child_ids.to_vec())
            .name(format!("Node_{id}"))
            .original_key(format!("Node_{id}"))
            .build(k)
            .unwrap()
    }

    #[traced_test]
    fn check_empty_skeleton() {
        let skel = SkeletonBuilder::default().build().unwrap();
        let map = build_node_level_map(&skel);
        assert!(map.is_empty());
    }

    #[traced_test]
    fn check_single_node() {
        let node0 = make_node(0, &[]);
        let skel = SkeletonBuilder::default()
            .nodes(vec![node0])
            .root_id(Some(0))
            .build()
            .unwrap();
        let map = build_node_level_map(&skel);
        assert_eq!(map.len(), 1);
        assert_eq!(map.get(&0), Some(&0u8));
    }

    #[traced_test]
    fn check_linear_chain() {
        let n0 = make_node(0, &[1]);
        let n1 = make_node(1, &[2]);
        let n2 = make_node(2, &[]);
        let skel = SkeletonBuilder::default()
            .nodes(vec![n0, n1, n2])
            .root_id(Some(0))
            .build()
            .unwrap();
        let map = build_node_level_map(&skel);
        assert_eq!(map.get(&0), Some(&0u8));
        assert_eq!(map.get(&1), Some(&1u8));
        assert_eq!(map.get(&2), Some(&2u8));
        assert_eq!(map.len(), 3);
    }

    #[traced_test]
    fn check_branching() {
        let n0 = make_node(0, &[1,2]);
        let n1 = make_node(1, &[3]);
        let n2 = make_node(2, &[]);
        let n3 = make_node(3, &[]);
        let skel = SkeletonBuilder::default()
            .nodes(vec![n0,n1,n2,n3])
            .root_id(Some(0))
            .build()
            .unwrap();
        let map = build_node_level_map(&skel);
        assert_eq!(map.get(&0), Some(&0u8));
        assert_eq!(map.get(&1), Some(&1u8));
        assert_eq!(map.get(&2), Some(&1u8));
        assert_eq!(map.get(&3), Some(&2u8));
    }

    #[traced_test]
    fn check_unconnected_nodes() {
        // root=0 => BFS sees 0,1 => doesn't see 2,3
        let n0 = make_node(0, &[1]);
        let n1 = make_node(1, &[]);
        let n2 = make_node(2, &[3]);
        let n3 = make_node(3, &[]);
        let skel = SkeletonBuilder::default()
            .nodes(vec![n0,n1,n2,n3])
            .root_id(Some(0))
            .build()
            .unwrap();

        let map = build_node_level_map(&skel);
        assert_eq!(map.len(), 2);
        assert_eq!(map.get(&0), Some(&0u8));
        assert_eq!(map.get(&1), Some(&1u8));
    }

    #[traced_test]
    fn check_cycle_doesnt_loop() {
        // 0->1, 1->0
        let n0 = make_node(0, &[1]);
        let n1 = make_node(1, &[0]);
        let skel = SkeletonBuilder::default()
            .nodes(vec![n0,n1])
            .root_id(Some(0))
            .build()
            .unwrap();

        let map = build_node_level_map(&skel);
        assert_eq!(map.len(), 2);
        assert_eq!(map.get(&0), Some(&0u8));
        assert_eq!(map.get(&1), Some(&1u8));
    }
}