dreamwell-engine 1.0.0

Dreamwell pure-logic engine library — transforms, hierarchy, canon pipeline, spatial math, hashing, tile rules, validation, waymark schema, material/lighting descriptors. No SpacetimeDB dependency.
Documentation
//! Scene hierarchy — parent-child relationships via index vectors.
//!
//! Split from scene transforms to allow borrow-safe propagation:
//! `propagate_from_roots(&mut transforms, &hierarchy)` borrows
//! transforms mutably and hierarchy immutably without alias conflict.

/// SoA parent-child hierarchy.
/// Indices are entity slots (0..capacity). Not entity IDs.
#[derive(Debug, Clone)]
pub struct SceneHierarchy {
    /// Parent slot index for each entity. `None` = root.
    pub parent: Vec<Option<u32>>,
    /// Children slot indices for each entity.
    pub children: Vec<Vec<u32>>,
}

impl SceneHierarchy {
    /// Create with pre-allocated capacity.
    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            parent: vec![None; capacity],
            children: vec![Vec::new(); capacity],
        }
    }

    /// Set parent of `child`. Pass `None` to make root.
    /// Maintains bidirectional consistency.
    pub fn set_parent(&mut self, child: u32, new_parent: Option<u32>) {
        let c = child as usize;
        if c >= self.parent.len() {
            return;
        }
        // Remove from old parent's children list.
        if let Some(old_p) = self.parent[c] {
            let old_p = old_p as usize;
            if old_p < self.children.len() {
                self.children[old_p].retain(|&id| id != child);
            }
        }
        // Set new parent.
        self.parent[c] = new_parent;
        // Add to new parent's children list.
        if let Some(p) = new_parent {
            let p = p as usize;
            if p < self.children.len() && !self.children[p].contains(&child) {
                self.children[p].push(child);
            }
        }
    }

    /// Children of an entity by slot index.
    pub fn children_of(&self, entity: u32) -> &[u32] {
        let idx = entity as usize;
        if idx < self.children.len() {
            &self.children[idx]
        } else {
            &[]
        }
    }

    /// All root entities (no parent).
    pub fn roots(&self) -> Vec<u32> {
        self.parent
            .iter()
            .enumerate()
            .filter_map(|(i, p)| if p.is_none() { Some(i as u32) } else { None })
            .collect()
    }

    /// Number of entity slots.
    pub fn len(&self) -> usize {
        self.parent.len()
    }

    /// Whether the hierarchy is empty.
    pub fn is_empty(&self) -> bool {
        self.parent.is_empty()
    }
}

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

    #[test]
    fn with_capacity_creates_roots() {
        let h = SceneHierarchy::with_capacity(10);
        assert_eq!(h.len(), 10);
        assert_eq!(h.roots().len(), 10); // all roots by default
    }

    #[test]
    fn set_parent_and_children() {
        let mut h = SceneHierarchy::with_capacity(5);
        h.set_parent(1, Some(0));
        h.set_parent(2, Some(0));
        assert_eq!(h.children_of(0), &[1, 2]);
        assert_eq!(h.parent[1], Some(0));
    }

    #[test]
    fn reparent_removes_from_old() {
        let mut h = SceneHierarchy::with_capacity(5);
        h.set_parent(2, Some(0));
        assert_eq!(h.children_of(0), &[2]);
        h.set_parent(2, Some(1));
        assert!(h.children_of(0).is_empty());
        assert_eq!(h.children_of(1), &[2]);
    }

    #[test]
    fn make_root() {
        let mut h = SceneHierarchy::with_capacity(3);
        h.set_parent(1, Some(0));
        h.set_parent(1, None);
        assert!(h.children_of(0).is_empty());
        assert_eq!(h.parent[1], None);
    }

    #[test]
    fn roots_returns_parentless() {
        let mut h = SceneHierarchy::with_capacity(4);
        h.set_parent(1, Some(0));
        h.set_parent(2, Some(0));
        // 0, 3 are roots
        let roots = h.roots();
        assert!(roots.contains(&0));
        assert!(roots.contains(&3));
        assert!(!roots.contains(&1));
    }

    #[test]
    fn out_of_bounds_safe() {
        let mut h = SceneHierarchy::with_capacity(2);
        h.set_parent(99, Some(0)); // no-op
        assert!(h.children_of(99).is_empty());
    }
}