dreamwell_engine/waymark/
transitions.rs1use 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 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}