Skip to main content

dreamwell_engine/waymark/
transitions.rs

1use super::errors::{TopologyError, WaymarkError};
2use super::topology::{TopologyLayer, TopologyTree};
3use crate::physics::properties::PropertyValue;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum TransitionMode {
9    SeamlessStream,
10    PhaseOverlay,
11    CutScene,
12    FadeBlack,
13    Portal,
14    Teleport,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct TransitionDef {
19    pub id: String,
20    pub name: String,
21    pub from_layer: TopologyLayer,
22    pub from_id: String,
23    pub to_layer: TopologyLayer,
24    pub to_id: String,
25    pub mode: TransitionMode,
26    pub properties: HashMap<String, PropertyValue>,
27}
28
29#[derive(Debug, Clone, Default)]
30pub struct TransitionRegistry {
31    transitions: Vec<TransitionDef>,
32    id_index: HashMap<String, usize>,
33}
34
35impl TransitionRegistry {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    pub fn register(&mut self, def: TransitionDef) -> Result<(), WaymarkError> {
41        if self.id_index.contains_key(&def.id) {
42            return Err(WaymarkError::Topology(TopologyError::InvalidTransition {
43                from: def.from_id.clone(),
44                to: def.to_id.clone(),
45            }));
46        }
47        let idx = self.transitions.len();
48        self.id_index.insert(def.id.clone(), idx);
49        self.transitions.push(def);
50        Ok(())
51    }
52
53    pub fn find(&self, id: &str) -> Option<&TransitionDef> {
54        self.id_index.get(id).map(|&i| &self.transitions[i])
55    }
56
57    pub fn from_node(&self, node_id: &str) -> Vec<&TransitionDef> {
58        self.transitions.iter().filter(|t| t.from_id == node_id).collect()
59    }
60
61    pub fn to_node(&self, node_id: &str) -> Vec<&TransitionDef> {
62        self.transitions.iter().filter(|t| t.to_id == node_id).collect()
63    }
64
65    pub fn validate(&self, tree: &TopologyTree) -> Vec<WaymarkError> {
66        let mut errors = Vec::new();
67        for t in &self.transitions {
68            if tree.get(&t.from_id).is_none() {
69                errors.push(WaymarkError::Topology(TopologyError::InvalidTransition {
70                    from: t.from_id.clone(),
71                    to: t.to_id.clone(),
72                }));
73            }
74            if tree.get(&t.to_id).is_none() {
75                errors.push(WaymarkError::Topology(TopologyError::InvalidTransition {
76                    from: t.from_id.clone(),
77                    to: t.to_id.clone(),
78                }));
79            }
80            // Check adjacency or realm overlay
81            let diff = (t.from_layer.depth() as i8 - t.to_layer.depth() as i8).unsigned_abs();
82            if diff > 1 && t.from_layer != TopologyLayer::Realm && t.to_layer != TopologyLayer::Realm {
83                errors.push(WaymarkError::Topology(TopologyError::InvalidTransition {
84                    from: format!("{:?}:{}", t.from_layer, t.from_id),
85                    to: format!("{:?}:{}", t.to_layer, t.to_id),
86                }));
87            }
88        }
89        errors
90    }
91
92    pub fn len(&self) -> usize {
93        self.transitions.len()
94    }
95    pub fn is_empty(&self) -> bool {
96        self.transitions.is_empty()
97    }
98}
99
100impl TransitionMode {
101    pub const ALL: &[TransitionMode] = &[
102        Self::SeamlessStream,
103        Self::PhaseOverlay,
104        Self::CutScene,
105        Self::FadeBlack,
106        Self::Portal,
107        Self::Teleport,
108    ];
109
110    pub fn name(&self) -> &'static str {
111        match self {
112            Self::SeamlessStream => "Seamless Stream",
113            Self::PhaseOverlay => "Phase Overlay",
114            Self::CutScene => "Cut Scene",
115            Self::FadeBlack => "Fade Black",
116            Self::Portal => "Portal",
117            Self::Teleport => "Teleport",
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::super::topology::TopologyNode;
125    use super::*;
126
127    fn make_tree() -> TopologyTree {
128        let mut tree = TopologyTree::new();
129        tree.insert(TopologyNode {
130            layer: TopologyLayer::Location,
131            id: "loc1".into(),
132            name: "L1".into(),
133            parent_id: None,
134            properties: HashMap::new(),
135            tags: vec![],
136        })
137        .unwrap();
138        tree.insert(TopologyNode {
139            layer: TopologyLayer::Room,
140            id: "room1".into(),
141            name: "R1".into(),
142            parent_id: Some("loc1".into()),
143            properties: HashMap::new(),
144            tags: vec![],
145        })
146        .unwrap();
147        tree
148    }
149
150    #[test]
151    fn valid_adjacent_transition() {
152        let tree = make_tree();
153        let mut reg = TransitionRegistry::new();
154        reg.register(TransitionDef {
155            id: "t1".into(),
156            name: "T1".into(),
157            from_layer: TopologyLayer::Location,
158            from_id: "loc1".into(),
159            to_layer: TopologyLayer::Room,
160            to_id: "room1".into(),
161            mode: TransitionMode::SeamlessStream,
162            properties: HashMap::new(),
163        })
164        .unwrap();
165        let errors = reg.validate(&tree);
166        assert!(errors.is_empty());
167    }
168
169    #[test]
170    fn invalid_missing_node() {
171        let tree = make_tree();
172        let mut reg = TransitionRegistry::new();
173        reg.register(TransitionDef {
174            id: "t2".into(),
175            name: "T2".into(),
176            from_layer: TopologyLayer::Location,
177            from_id: "loc1".into(),
178            to_layer: TopologyLayer::Room,
179            to_id: "missing".into(),
180            mode: TransitionMode::Portal,
181            properties: HashMap::new(),
182        })
183        .unwrap();
184        let errors = reg.validate(&tree);
185        assert!(!errors.is_empty());
186    }
187
188    #[test]
189    fn transition_mode_all() {
190        assert_eq!(TransitionMode::ALL.len(), 6);
191    }
192
193    #[test]
194    fn from_node_query() {
195        let mut reg = TransitionRegistry::new();
196        reg.register(TransitionDef {
197            id: "t3".into(),
198            name: "T3".into(),
199            from_layer: TopologyLayer::Location,
200            from_id: "loc1".into(),
201            to_layer: TopologyLayer::Room,
202            to_id: "room1".into(),
203            mode: TransitionMode::FadeBlack,
204            properties: HashMap::new(),
205        })
206        .unwrap();
207        assert_eq!(reg.from_node("loc1").len(), 1);
208        assert_eq!(reg.to_node("room1").len(), 1);
209    }
210}