Skip to main content

goud_engine/context_registry/scene/
loading.rs

1//! Scene loading and unloading utilities.
2//!
3//! Provides [`SceneLoader`] for synchronous scene load/unload/save operations
4//! and [`DeferredSceneLoad`] for background JSON parsing with deferred
5//! integration into the [`SceneManager`].
6
7use std::sync::mpsc;
8
9use crate::core::error::GoudError;
10
11use super::data::SceneData;
12use super::manager::{SceneId, SceneManager};
13use super::serialization::{deserialize_scene, scene_from_json, scene_to_json, serialize_scene};
14
15// =============================================================================
16// SceneLoader
17// =============================================================================
18
19/// Static helper methods for loading and unloading scenes.
20pub struct SceneLoader;
21
22impl SceneLoader {
23    /// Loads a scene from a [`SceneData`] into the manager.
24    ///
25    /// Creates a new scene with the given name, registers built-in
26    /// serializable components, and deserializes the scene data into
27    /// the world.
28    ///
29    /// # Errors
30    ///
31    /// Returns an error if:
32    /// - A scene with the same name already exists
33    /// - Deserialization of scene data fails
34    pub fn load_scene(
35        manager: &mut SceneManager,
36        name: &str,
37        data: SceneData,
38    ) -> Result<SceneId, GoudError> {
39        let id = manager.create_scene(name)?;
40        let world = manager.get_scene_mut(id).ok_or_else(|| {
41            GoudError::InternalError("Scene was created but not accessible".to_string())
42        })?;
43        world.register_builtin_serializables();
44        deserialize_scene(&data, world)?;
45        Ok(id)
46    }
47
48    /// Unloads a scene by name.
49    ///
50    /// Looks up the scene by name and destroys it. Dropping the
51    /// [`World`] cleans up all entities and components.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if:
56    /// - No scene with the given name exists
57    /// - The scene is the default scene (which cannot be destroyed)
58    pub fn unload_scene(manager: &mut SceneManager, name: &str) -> Result<(), GoudError> {
59        let id = manager
60            .get_scene_by_name(name)
61            .ok_or_else(|| GoudError::ResourceNotFound(format!("Scene '{}' not found", name)))?;
62        manager.destroy_scene(id)
63    }
64
65    /// Loads a scene from a JSON string.
66    ///
67    /// Parses the JSON into [`SceneData`], then delegates to
68    /// [`load_scene`](Self::load_scene).
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if:
73    /// - The JSON is invalid or does not match the expected schema
74    /// - Scene creation or deserialization fails
75    pub fn load_scene_from_json(
76        manager: &mut SceneManager,
77        name: &str,
78        json: &str,
79    ) -> Result<SceneId, GoudError> {
80        let data = scene_from_json(json)?;
81        Self::load_scene(manager, name, data)
82    }
83
84    /// Saves a scene to a JSON string.
85    ///
86    /// Serializes all entities and components in the scene's world,
87    /// then converts to a pretty-printed JSON string.
88    ///
89    /// # Errors
90    ///
91    /// Returns an error if:
92    /// - The scene ID does not exist
93    /// - Serialization fails
94    pub fn save_scene_to_json(manager: &SceneManager, id: SceneId) -> Result<String, GoudError> {
95        let world = manager
96            .get_scene(id)
97            .ok_or_else(|| GoudError::ResourceNotFound(format!("Scene id {} not found", id)))?;
98        let name = manager
99            .get_scene_name(id)
100            .ok_or_else(|| GoudError::ResourceNotFound(format!("Scene id {} not found", id)))?;
101        let scene_data = serialize_scene(world, name)?;
102        scene_to_json(&scene_data)
103    }
104}
105
106// =============================================================================
107// DeferredSceneLoad
108// =============================================================================
109
110/// Result of a deferred scene load operation.
111struct DeferredResult {
112    /// Name to use when creating the scene in the manager.
113    name: String,
114    /// Parsed scene data or an error from JSON parsing.
115    data: Result<SceneData, GoudError>,
116}
117
118/// Deferred (background-thread) scene loader.
119///
120/// Allows JSON parsing to happen on a background thread while the
121/// main thread continues. Call [`process_completed`](Self::process_completed)
122/// each frame to integrate any finished loads into the [`SceneManager`].
123///
124/// # Example
125///
126/// ```ignore
127/// let deferred = DeferredSceneLoad::new();
128/// deferred.request_load("level_2".into(), json_string);
129///
130/// // Later (e.g., next frame):
131/// let results = deferred.process_completed(&mut manager);
132/// ```
133pub struct DeferredSceneLoad {
134    sender: mpsc::Sender<DeferredResult>,
135    receiver: mpsc::Receiver<DeferredResult>,
136}
137
138impl DeferredSceneLoad {
139    /// Creates a new deferred scene loader.
140    pub fn new() -> Self {
141        let (sender, receiver) = mpsc::channel();
142        Self { sender, receiver }
143    }
144
145    /// Requests a scene load on a background thread.
146    ///
147    /// Spawns a thread to parse the JSON string into [`SceneData`].
148    /// The result is sent through a channel and can be consumed by
149    /// [`process_completed`](Self::process_completed).
150    pub fn request_load(&self, name: String, json: String) {
151        let sender = self.sender.clone();
152        std::thread::spawn(move || {
153            let data = scene_from_json(&json);
154            // Ignore send errors -- receiver may have been dropped.
155            let _ = sender.send(DeferredResult { name, data });
156        });
157    }
158
159    /// Drains all completed loads and integrates them into the manager.
160    ///
161    /// For each completed parse, calls [`SceneLoader::load_scene`] to
162    /// create the scene. Returns a vec of results (one per completed
163    /// load).
164    pub fn process_completed(&self, manager: &mut SceneManager) -> Vec<Result<SceneId, GoudError>> {
165        let mut results = Vec::new();
166        while let Ok(deferred) = self.receiver.try_recv() {
167            let result = match deferred.data {
168                Ok(data) => SceneLoader::load_scene(manager, &deferred.name, data),
169                Err(e) => Err(e),
170            };
171            results.push(result);
172        }
173        results
174    }
175}
176
177impl Default for DeferredSceneLoad {
178    fn default() -> Self {
179        Self::new()
180    }
181}
182
183// =============================================================================
184// Tests
185// =============================================================================
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::core::math::Vec2;
191    use crate::ecs::components::hierarchy::Name;
192    use crate::ecs::components::Transform2D;
193    use crate::ecs::World;
194
195    /// Helper: build a SceneData with one entity (named, with transform).
196    fn sample_scene_data() -> SceneData {
197        let mut world = World::new();
198        world.register_builtin_serializables();
199
200        let e = world.spawn_empty();
201        world.insert(e, Name::new("hero"));
202        world.insert(e, Transform2D::new(Vec2::new(10.0, 20.0), 0.0, Vec2::one()));
203
204        serialize_scene(&world, "sample").unwrap()
205    }
206
207    // ----- load from SceneData -----------------------------------------------
208
209    #[test]
210    fn test_load_scene_creates_entities() {
211        let mut mgr = SceneManager::new();
212        let data = sample_scene_data();
213
214        let id = SceneLoader::load_scene(&mut mgr, "level", data).unwrap();
215
216        let world = mgr.get_scene(id).unwrap();
217        assert!(
218            world.entity_count() > 0,
219            "loaded scene should have entities"
220        );
221    }
222
223    // ----- unload scene ------------------------------------------------------
224
225    #[test]
226    fn test_unload_scene_removes_from_manager() {
227        let mut mgr = SceneManager::new();
228        let data = sample_scene_data();
229
230        SceneLoader::load_scene(&mut mgr, "level", data).unwrap();
231        SceneLoader::unload_scene(&mut mgr, "level").unwrap();
232
233        assert!(
234            mgr.get_scene_by_name("level").is_none(),
235            "scene should be gone after unload"
236        );
237    }
238
239    #[test]
240    fn test_load_then_unload_entity_count_returns_to_zero() {
241        let mut mgr = SceneManager::new();
242
243        let data = sample_scene_data();
244        let id = SceneLoader::load_scene(&mut mgr, "level", data).unwrap();
245
246        // Verify entities were loaded
247        let world = mgr.get_scene(id).unwrap();
248        assert!(
249            world.entity_count() > 0,
250            "loaded scene should have entities"
251        );
252
253        // Unload the scene
254        SceneLoader::unload_scene(&mut mgr, "level").unwrap();
255
256        // Verify the scene is no longer accessible (World was dropped)
257        assert!(
258            mgr.get_scene(id).is_none(),
259            "scene should be gone after unload"
260        );
261    }
262
263    // ----- load from JSON ----------------------------------------------------
264
265    #[test]
266    fn test_load_scene_from_json() {
267        let mut mgr = SceneManager::new();
268        let data = sample_scene_data();
269        let json = scene_to_json(&data).unwrap();
270
271        let id = SceneLoader::load_scene_from_json(&mut mgr, "json_level", &json).unwrap();
272
273        let world = mgr.get_scene(id).unwrap();
274        assert!(world.entity_count() > 0);
275    }
276
277    // ----- save to JSON ------------------------------------------------------
278
279    #[test]
280    fn test_save_scene_to_json() {
281        let mut mgr = SceneManager::new();
282        let data = sample_scene_data();
283        let id = SceneLoader::load_scene(&mut mgr, "save_test", data).unwrap();
284
285        let json = SceneLoader::save_scene_to_json(&mgr, id).unwrap();
286        assert!(json.contains("entities"));
287    }
288
289    // ----- error cases -------------------------------------------------------
290
291    #[test]
292    fn test_unload_nonexistent_scene_errors() {
293        let mut mgr = SceneManager::new();
294        let result = SceneLoader::unload_scene(&mut mgr, "nope");
295        assert!(result.is_err());
296    }
297
298    #[test]
299    fn test_load_duplicate_name_errors() {
300        let mut mgr = SceneManager::new();
301        let data = sample_scene_data();
302
303        SceneLoader::load_scene(&mut mgr, "dup", data.clone()).unwrap();
304        let result = SceneLoader::load_scene(&mut mgr, "dup", data);
305        assert!(result.is_err());
306    }
307
308    // ----- deferred loading --------------------------------------------------
309
310    #[test]
311    fn test_deferred_load_creates_scene() {
312        let mut mgr = SceneManager::new();
313        let data = sample_scene_data();
314        let json = scene_to_json(&data).unwrap();
315
316        let deferred = DeferredSceneLoad::new();
317        deferred.request_load("deferred_level".to_string(), json);
318
319        // Busy-poll with timeout for deferred load completion.
320        let start = std::time::Instant::now();
321        let timeout = std::time::Duration::from_secs(5);
322        let results = loop {
323            let results = deferred.process_completed(&mut mgr);
324            if !results.is_empty() {
325                break results;
326            }
327            if start.elapsed() > timeout {
328                panic!("Deferred load did not complete within timeout");
329            }
330            std::thread::yield_now();
331        };
332
333        assert_eq!(results.len(), 1);
334        assert!(results[0].is_ok());
335
336        let id = results[0].as_ref().unwrap();
337        let world = mgr.get_scene(*id).unwrap();
338        assert!(world.entity_count() > 0);
339    }
340
341    #[test]
342    fn test_deferred_load_invalid_json_returns_error() {
343        let mut mgr = SceneManager::new();
344
345        let deferred = DeferredSceneLoad::new();
346        deferred.request_load("bad".to_string(), "not valid json".to_string());
347
348        // Busy-poll with timeout for deferred load completion.
349        let start = std::time::Instant::now();
350        let timeout = std::time::Duration::from_secs(5);
351        let results = loop {
352            let results = deferred.process_completed(&mut mgr);
353            if !results.is_empty() {
354                break results;
355            }
356            if start.elapsed() > timeout {
357                panic!("Deferred load did not complete within timeout");
358            }
359            std::thread::yield_now();
360        };
361
362        assert_eq!(results.len(), 1);
363        assert!(results[0].is_err());
364    }
365}