#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SceneNodeType {
Shot,
Act,
Sequence,
Episode,
Season,
}
impl SceneNodeType {
#[must_use]
pub fn level(&self) -> u8 {
match self {
SceneNodeType::Shot => 0,
SceneNodeType::Act => 1,
SceneNodeType::Sequence => 2,
SceneNodeType::Episode => 3,
SceneNodeType::Season => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct SceneNode {
pub id: u64,
pub name: String,
pub node_type: SceneNodeType,
pub children: Vec<u64>,
pub start_frame: u64,
pub end_frame: u64,
}
impl SceneNode {
#[must_use]
pub fn duration_frames(&self) -> u64 {
self.end_frame.saturating_sub(self.start_frame)
}
#[must_use]
pub fn contains_frame(&self, f: u64) -> bool {
f >= self.start_frame && f < self.end_frame
}
}
#[derive(Debug, Default)]
pub struct SceneGraph {
nodes: Vec<SceneNode>,
next_id: u64,
}
impl SceneGraph {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_node(
&mut self,
name: impl Into<String>,
node_type: SceneNodeType,
start_frame: u64,
end_frame: u64,
) -> u64 {
let id = self.next_id;
self.next_id += 1;
self.nodes.push(SceneNode {
id,
name: name.into(),
node_type,
children: Vec::new(),
start_frame,
end_frame,
});
id
}
pub fn add_child(&mut self, parent_id: u64, child_id: u64) -> bool {
if !self.nodes.iter().any(|n| n.id == child_id) {
return false;
}
if let Some(parent) = self.nodes.iter_mut().find(|n| n.id == parent_id) {
if !parent.children.contains(&child_id) {
parent.children.push(child_id);
}
true
} else {
false
}
}
#[must_use]
pub fn find(&self, id: u64) -> Option<&SceneNode> {
self.nodes.iter().find(|n| n.id == id)
}
#[must_use]
pub fn root_nodes(&self) -> Vec<&SceneNode> {
let child_ids: std::collections::HashSet<u64> = self
.nodes
.iter()
.flat_map(|n| n.children.iter().copied())
.collect();
self.nodes
.iter()
.filter(|n| !child_ids.contains(&n.id))
.collect()
}
}
pub struct SceneGraphWalker<'a> {
graph: &'a SceneGraph,
stack: Vec<u64>,
}
impl<'a> SceneGraphWalker<'a> {
#[must_use]
pub fn new(graph: &'a SceneGraph, root_id: u64) -> Option<Self> {
if graph.find(root_id).is_some() {
Some(Self {
graph,
stack: vec![root_id],
})
} else {
None
}
}
}
impl<'a> Iterator for SceneGraphWalker<'a> {
type Item = &'a SceneNode;
fn next(&mut self) -> Option<Self::Item> {
let id = self.stack.pop()?;
let node = self.graph.find(id)?;
for &child_id in node.children.iter().rev() {
self.stack.push(child_id);
}
Some(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_type_level() {
assert_eq!(SceneNodeType::Shot.level(), 0);
assert_eq!(SceneNodeType::Act.level(), 1);
assert_eq!(SceneNodeType::Sequence.level(), 2);
assert_eq!(SceneNodeType::Episode.level(), 3);
assert_eq!(SceneNodeType::Season.level(), 4);
}
#[test]
fn test_node_duration_frames() {
let node = SceneNode {
id: 0,
name: "shot1".into(),
node_type: SceneNodeType::Shot,
children: vec![],
start_frame: 100,
end_frame: 250,
};
assert_eq!(node.duration_frames(), 150);
}
#[test]
fn test_node_duration_frames_saturates() {
let node = SceneNode {
id: 1,
name: "empty".into(),
node_type: SceneNodeType::Shot,
children: vec![],
start_frame: 200,
end_frame: 100, };
assert_eq!(node.duration_frames(), 0);
}
#[test]
fn test_node_contains_frame() {
let node = SceneNode {
id: 2,
name: "s".into(),
node_type: SceneNodeType::Shot,
children: vec![],
start_frame: 10,
end_frame: 20,
};
assert!(node.contains_frame(10));
assert!(node.contains_frame(19));
assert!(!node.contains_frame(20)); assert!(!node.contains_frame(9));
}
#[test]
fn test_scene_graph_add_node_ids() {
let mut g = SceneGraph::new();
let id0 = g.add_node("A", SceneNodeType::Shot, 0, 10);
let id1 = g.add_node("B", SceneNodeType::Act, 10, 20);
assert_eq!(id0, 0);
assert_eq!(id1, 1);
}
#[test]
fn test_scene_graph_find() {
let mut g = SceneGraph::new();
let id = g.add_node("X", SceneNodeType::Sequence, 0, 100);
assert!(g.find(id).is_some());
assert!(g.find(999).is_none());
}
#[test]
fn test_scene_graph_add_child_success() {
let mut g = SceneGraph::new();
let parent = g.add_node("P", SceneNodeType::Act, 0, 100);
let child = g.add_node("C", SceneNodeType::Shot, 0, 50);
assert!(g.add_child(parent, child));
let p = g.find(parent).expect("should succeed in test");
assert!(p.children.contains(&child));
}
#[test]
fn test_scene_graph_add_child_missing_parent() {
let mut g = SceneGraph::new();
let child = g.add_node("C", SceneNodeType::Shot, 0, 10);
assert!(!g.add_child(999, child));
}
#[test]
fn test_scene_graph_add_child_missing_child() {
let mut g = SceneGraph::new();
let parent = g.add_node("P", SceneNodeType::Act, 0, 100);
assert!(!g.add_child(parent, 999));
}
#[test]
fn test_root_nodes_single() {
let mut g = SceneGraph::new();
let root = g.add_node("root", SceneNodeType::Season, 0, 10_000);
let child = g.add_node("ep1", SceneNodeType::Episode, 0, 5_000);
g.add_child(root, child);
let roots = g.root_nodes();
assert_eq!(roots.len(), 1);
assert_eq!(roots[0].id, root);
}
#[test]
fn test_root_nodes_multiple() {
let mut g = SceneGraph::new();
g.add_node("A", SceneNodeType::Shot, 0, 10);
g.add_node("B", SceneNodeType::Shot, 10, 20);
assert_eq!(g.root_nodes().len(), 2);
}
#[test]
fn test_walker_visits_all() {
let mut g = SceneGraph::new();
let root = g.add_node("root", SceneNodeType::Act, 0, 100);
let c1 = g.add_node("c1", SceneNodeType::Shot, 0, 50);
let c2 = g.add_node("c2", SceneNodeType::Shot, 50, 100);
g.add_child(root, c1);
g.add_child(root, c2);
let visited: Vec<u64> = SceneGraphWalker::new(&g, root)
.expect("should succeed in test")
.map(|n| n.id)
.collect();
assert_eq!(visited.len(), 3);
assert!(visited.contains(&root));
assert!(visited.contains(&c1));
assert!(visited.contains(&c2));
}
#[test]
fn test_walker_missing_root() {
let g = SceneGraph::new();
assert!(SceneGraphWalker::new(&g, 0).is_none());
}
#[test]
fn test_scene_graph_node_data() {
let mut g = SceneGraph::new();
let id = g.add_node("MyShot", SceneNodeType::Shot, 5, 15);
let n = g.find(id).expect("should succeed in test");
assert_eq!(n.name, "MyShot");
assert_eq!(n.start_frame, 5);
assert_eq!(n.end_frame, 15);
assert_eq!(n.node_type, SceneNodeType::Shot);
}
#[test]
fn test_add_child_idempotent() {
let mut g = SceneGraph::new();
let p = g.add_node("P", SceneNodeType::Act, 0, 100);
let c = g.add_node("C", SceneNodeType::Shot, 0, 50);
g.add_child(p, c);
g.add_child(p, c); let parent = g.find(p).expect("should succeed in test");
assert_eq!(parent.children.len(), 1);
}
}