goud_engine/context_registry/scene/
manager.rs1use std::collections::HashMap;
4
5use crate::core::error::GoudError;
6use crate::ecs::World;
7
8pub type SceneId = u32;
10
11pub const DEFAULT_SCENE_NAME: &str = "default";
13
14#[derive(Debug)]
22enum SceneSlot {
23 Occupied { name: String, world: Box<World> },
25 Free,
27}
28
29#[derive(Debug)]
44pub struct SceneManager {
45 scenes: Vec<SceneSlot>,
47 name_to_id: HashMap<String, SceneId>,
49 active_scenes: Vec<SceneId>,
51 default_scene: SceneId,
53}
54
55impl SceneManager {
56 pub fn new() -> Self {
59 let mut manager = Self {
60 scenes: Vec::new(),
61 name_to_id: HashMap::new(),
62 active_scenes: Vec::new(),
63 default_scene: 0,
64 };
65 let id = manager
67 .create_scene(DEFAULT_SCENE_NAME)
68 .expect("creating the default scene must not fail");
69 manager.default_scene = id;
70 manager.active_scenes.push(id);
72 manager
73 }
74
75 pub fn create_scene(&mut self, name: &str) -> Result<SceneId, GoudError> {
84 if self.name_to_id.contains_key(name) {
85 return Err(GoudError::ResourceAlreadyExists(format!(
86 "Scene '{}' already exists",
87 name
88 )));
89 }
90
91 let id = self.find_free_slot().unwrap_or_else(|| {
93 let id = self.scenes.len() as SceneId;
94 self.scenes.push(SceneSlot::Free);
95 id
96 });
97
98 self.scenes[id as usize] = SceneSlot::Occupied {
99 name: name.to_string(),
100 world: Box::new(World::new()),
101 };
102 self.name_to_id.insert(name.to_string(), id);
103
104 Ok(id)
105 }
106
107 pub fn destroy_scene(&mut self, id: SceneId) -> Result<(), GoudError> {
112 if id == self.default_scene {
113 return Err(GoudError::InvalidState(
114 "Cannot destroy the default scene".to_string(),
115 ));
116 }
117
118 let index = id as usize;
119 if index >= self.scenes.len() {
120 return Err(GoudError::ResourceNotFound(format!(
121 "Scene id {} not found",
122 id
123 )));
124 }
125
126 match &self.scenes[index] {
127 SceneSlot::Occupied { name, .. } => {
128 self.name_to_id.remove(name);
129 }
130 SceneSlot::Free => {
131 return Err(GoudError::ResourceNotFound(format!(
132 "Scene id {} not found",
133 id
134 )));
135 }
136 }
137
138 self.scenes[index] = SceneSlot::Free;
139 self.active_scenes.retain(|&s| s != id);
140
141 Ok(())
142 }
143
144 pub fn get_scene(&self, id: SceneId) -> Option<&World> {
150 self.scenes.get(id as usize).and_then(|slot| match slot {
151 SceneSlot::Occupied { world, .. } => Some(world.as_ref()),
152 SceneSlot::Free => None,
153 })
154 }
155
156 pub fn get_scene_mut(&mut self, id: SceneId) -> Option<&mut World> {
158 self.scenes
159 .get_mut(id as usize)
160 .and_then(|slot| match slot {
161 SceneSlot::Occupied { world, .. } => Some(world.as_mut()),
162 SceneSlot::Free => None,
163 })
164 }
165
166 pub fn get_scene_by_name(&self, name: &str) -> Option<SceneId> {
168 self.name_to_id.get(name).copied()
169 }
170
171 pub fn get_scene_name(&self, id: SceneId) -> Option<&str> {
173 self.scenes.get(id as usize).and_then(|slot| match slot {
174 SceneSlot::Occupied { name, .. } => Some(name.as_str()),
175 SceneSlot::Free => None,
176 })
177 }
178
179 pub fn set_active(&mut self, id: SceneId, active: bool) -> Result<(), GoudError> {
188 if !self.scene_exists(id) {
189 return Err(GoudError::ResourceNotFound(format!(
190 "Scene id {} not found",
191 id
192 )));
193 }
194
195 if active {
196 if !self.active_scenes.contains(&id) {
197 self.active_scenes.push(id);
198 }
199 } else {
200 self.active_scenes.retain(|&s| s != id);
201 }
202
203 Ok(())
204 }
205
206 pub fn is_active(&self, id: SceneId) -> bool {
208 self.active_scenes.contains(&id)
209 }
210
211 pub fn active_scenes(&self) -> &[SceneId] {
213 &self.active_scenes
214 }
215
216 pub fn scene_count(&self) -> usize {
222 self.scenes
223 .iter()
224 .filter(|s| matches!(s, SceneSlot::Occupied { .. }))
225 .count()
226 }
227
228 #[inline]
230 pub fn default_scene(&self) -> SceneId {
231 self.default_scene
232 }
233
234 fn find_free_slot(&self) -> Option<SceneId> {
240 self.scenes
241 .iter()
242 .position(|s| matches!(s, SceneSlot::Free))
243 .map(|i| i as SceneId)
244 }
245
246 fn scene_exists(&self, id: SceneId) -> bool {
248 self.scenes
249 .get(id as usize)
250 .is_some_and(|s| matches!(s, SceneSlot::Occupied { .. }))
251 }
252}
253
254impl Default for SceneManager {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260#[cfg(test)]
265mod tests {
266 use super::*;
267 use crate::ecs::Component;
268
269 #[derive(Debug, Clone, PartialEq)]
271 struct Health(u32);
272 impl Component for Health {}
273
274 #[test]
277 fn test_default_scene_on_new() {
278 let mgr = SceneManager::new();
279
280 assert_eq!(mgr.scene_count(), 1);
281 assert_eq!(mgr.default_scene(), 0);
282 assert!(mgr.is_active(mgr.default_scene()));
283 assert_eq!(mgr.active_scenes(), &[0]);
284
285 assert!(mgr.get_scene(mgr.default_scene()).is_some());
287 }
288
289 #[test]
292 fn test_create_scene() {
293 let mut mgr = SceneManager::new();
294 let id = mgr.create_scene("level_2").unwrap();
295
296 assert_ne!(id, mgr.default_scene());
297 assert_eq!(mgr.scene_count(), 2);
298 assert!(mgr.get_scene(id).is_some());
299 }
300
301 #[test]
302 fn test_create_duplicate_fails() {
303 let mut mgr = SceneManager::new();
304 mgr.create_scene("level_2").unwrap();
305
306 let result = mgr.create_scene("level_2");
307 assert!(result.is_err());
308 }
309
310 #[test]
311 fn test_destroy_scene() {
312 let mut mgr = SceneManager::new();
313 let id = mgr.create_scene("temp").unwrap();
314
315 mgr.destroy_scene(id).unwrap();
316
317 assert_eq!(mgr.scene_count(), 1);
318 assert!(mgr.get_scene(id).is_none());
319 assert!(mgr.get_scene_by_name("temp").is_none());
320 }
321
322 #[test]
323 fn test_destroy_default_fails() {
324 let mut mgr = SceneManager::new();
325 let result = mgr.destroy_scene(mgr.default_scene());
326 assert!(result.is_err());
327 }
328
329 #[test]
330 fn test_destroy_nonexistent_fails() {
331 let mut mgr = SceneManager::new();
332 let result = mgr.destroy_scene(999);
333 assert!(result.is_err());
334 }
335
336 #[test]
339 fn test_get_scene_by_name() {
340 let mut mgr = SceneManager::new();
341 let id = mgr.create_scene("my_scene").unwrap();
342
343 assert_eq!(mgr.get_scene_by_name("my_scene"), Some(id));
344 assert_eq!(
345 mgr.get_scene_by_name(DEFAULT_SCENE_NAME),
346 Some(mgr.default_scene())
347 );
348 assert_eq!(mgr.get_scene_by_name("nope"), None);
349 }
350
351 #[test]
354 fn test_entity_isolation() {
355 let mut mgr = SceneManager::new();
356 let scene_a = mgr.default_scene();
357 let scene_b = mgr.create_scene("b").unwrap();
358
359 let entity_a = mgr.get_scene_mut(scene_a).unwrap().spawn_empty();
361 mgr.get_scene_mut(scene_a)
362 .unwrap()
363 .insert(entity_a, Health(100));
364
365 assert_eq!(mgr.get_scene(scene_a).unwrap().entity_count(), 1);
367 assert_eq!(mgr.get_scene(scene_b).unwrap().entity_count(), 0);
368
369 assert!(!mgr.get_scene(scene_b).unwrap().is_alive(entity_a));
371
372 let entity_b = mgr.get_scene_mut(scene_b).unwrap().spawn_empty();
374 mgr.get_scene_mut(scene_b)
375 .unwrap()
376 .insert(entity_b, Health(50));
377
378 assert_eq!(mgr.get_scene(scene_a).unwrap().entity_count(), 1);
379 assert_eq!(mgr.get_scene(scene_b).unwrap().entity_count(), 1);
380
381 assert_eq!(
383 mgr.get_scene(scene_a).unwrap().get::<Health>(entity_a),
384 Some(&Health(100))
385 );
386 assert_eq!(
387 mgr.get_scene(scene_b).unwrap().get::<Health>(entity_b),
388 Some(&Health(50))
389 );
390 }
391
392 #[test]
395 fn test_multiple_active_scenes() {
396 let mut mgr = SceneManager::new();
397 let b = mgr.create_scene("b").unwrap();
398 let c = mgr.create_scene("c").unwrap();
399
400 mgr.set_active(b, true).unwrap();
401 mgr.set_active(c, true).unwrap();
402
403 assert_eq!(mgr.active_scenes().len(), 3);
404 assert!(mgr.is_active(mgr.default_scene()));
405 assert!(mgr.is_active(b));
406 assert!(mgr.is_active(c));
407 }
408
409 #[test]
410 fn test_set_inactive() {
411 let mut mgr = SceneManager::new();
412 let b = mgr.create_scene("b").unwrap();
413 mgr.set_active(b, true).unwrap();
414 assert!(mgr.is_active(b));
415
416 mgr.set_active(b, false).unwrap();
417 assert!(!mgr.is_active(b));
418 }
419
420 #[test]
421 fn test_set_active_nonexistent_fails() {
422 let mut mgr = SceneManager::new();
423 let result = mgr.set_active(999, true);
424 assert!(result.is_err());
425 }
426
427 #[test]
428 fn test_set_active_idempotent() {
429 let mut mgr = SceneManager::new();
430 let b = mgr.create_scene("b").unwrap();
431
432 mgr.set_active(b, true).unwrap();
433 mgr.set_active(b, true).unwrap();
434
435 assert_eq!(mgr.active_scenes().iter().filter(|&&s| s == b).count(), 1);
437 }
438
439 #[test]
442 fn test_slot_reuse_after_destroy() {
443 let mut mgr = SceneManager::new();
444 let a = mgr.create_scene("a").unwrap();
445 mgr.destroy_scene(a).unwrap();
446
447 let b = mgr.create_scene("b").unwrap();
449 assert_eq!(b, a);
450 }
451
452 #[test]
455 fn test_destroy_removes_from_active() {
456 let mut mgr = SceneManager::new();
457 let b = mgr.create_scene("b").unwrap();
458 mgr.set_active(b, true).unwrap();
459 assert!(mgr.is_active(b));
460
461 mgr.destroy_scene(b).unwrap();
462 assert!(!mgr.is_active(b));
463 }
464}