Skip to main content

fantasy_craft/scene/
scene_loader.rs

1use macroquad::prelude::*;
2use hecs::Entity;
3use serde_json::Value;
4use std::collections::HashMap;
5use crate::core::context::Context;
6use crate::prelude::Parent;
7use crate::scene::scene_format::{SceneFile, SceneEntry};
8// We need to import the Error trait explicitly
9use std::error::Error;
10use async_recursion::async_recursion;
11
12pub trait ComponentLoader: Send + Sync + 'static {
13    fn load(&self, ctx: &mut Context, entity: Entity, data: &Value);
14}
15
16pub struct SceneLoader {
17    component_loaders: HashMap<String, Box<dyn ComponentLoader>>,
18}
19
20impl SceneLoader {
21    pub fn new() -> Self {
22        Self {
23            component_loaders: HashMap::new(),
24        }
25    }
26
27    pub fn register<S: Into<String>>(&mut self, name: S, loader: Box<dyn ComponentLoader>) -> &mut Self {
28        self.component_loaders.insert(name.into(), loader);
29        self
30    }
31
32    // Public entry point
33    pub async fn load_scene_from_file(&self, path: &str, ctx: &mut Context) -> Result<(), Box<dyn Error>> {
34        let mut entity_map: HashMap<String, Entity> = HashMap::new();
35        let mut parent_queue: Vec<(Entity, String)> = Vec::new();
36
37        info!("SceneLoader: Starting load from root: {}", path);
38
39        self.load_scene_internal(path, ctx, &mut entity_map, &mut parent_queue).await?;
40
41        self.process_parent_queue(ctx, &entity_map, parent_queue);
42
43        info!("SceneLoader: Loading complete.");
44        Ok(())
45    }
46
47    // Internal recursive function
48    // We explicitly cast errors to Box<dyn Error> to satisfy the signature
49    #[async_recursion]
50    async fn load_scene_internal(
51        &self,
52        path: &str,
53        ctx: &mut Context,
54        entity_map: &mut HashMap<String, Entity>,
55        parent_queue: &mut Vec<(Entity, String)>,
56    ) -> Result<(), Box<dyn Error>> { // <--- The return type we must strictly adhere to
57        
58        // 1. Load string using Macroquad's HTTP/FS abstraction
59        let json_content = load_string(path)
60            .await
61            .map_err(|e| {
62                error!("SceneLoader: Failed to load file '{}': {}", path, e);
63                // Explicitly box the macroquad error into the trait object
64                Box::new(e) as Box<dyn Error>
65            })?;
66
67        // 2. Parse JSON
68        let scene_data: SceneFile = serde_json::from_str(&json_content)
69            .map_err(|e| {
70                error!("SceneLoader: Failed to parse JSON in '{}': {}", path, e);
71                // Explicitly box the serde error into the trait object
72                Box::new(e) as Box<dyn Error>
73            })?;
74
75        // 3. Resolve current directory URL-safely
76        let current_dir = if let Some(last_slash_idx) = path.rfind('/') {
77            &path[0..=last_slash_idx]
78        } else {
79            ""
80        };
81
82        for entry in scene_data.entities {
83            match entry {
84                SceneEntry::Entity(entity_data) => {
85                    let entity = ctx.world.spawn(());
86                    
87                    if entity_map.insert(entity_data.id.clone(), entity).is_some() {
88                        warn!("Warning: Duplicate entity ID found: '{}'. Overwriting.", entity_data.id);
89                    }
90
91                    for (component_name, component_data) in entity_data.components {
92                        if component_name == "Parent" {
93                            if let Some(target_id) = component_data.as_str() {
94                                parent_queue.push((entity, target_id.to_string()));
95                            } else {
96                                warn!("Warning: 'Parent' target for entity {} is invalid.", entity_data.id);
97                            }
98                            continue;
99                        }
100
101                        if let Some(loader) = self.component_loaders.get(&component_name) {
102                            loader.load(ctx, entity, &component_data);
103                        } else {
104                            warn!("Warning: No component loader registered for '{}'", component_name);
105                        }
106                    }
107                }
108                
109                SceneEntry::Import(import_data) => {
110                    let import_path_str = format!("{}{}", current_dir, import_data.import);
111
112                    info!("Importing sub-scene from: {}", import_path_str);
113
114                    // 4. Recursive call
115                    // Since the recursive function already returns Result<(), Box<dyn Error>>,
116                    // the ? operator works fine here.
117                    self.load_scene_internal(
118                        &import_path_str,
119                        ctx,
120                        entity_map,
121                        parent_queue,
122                    ).await?;
123                }
124            }
125        }
126
127        Ok(())
128    }
129
130    fn process_parent_queue(
131        &self,
132        ctx: &mut Context,
133        entity_map: &HashMap<String, Entity>,
134        parent_queue: Vec<(Entity, String)>,
135    ) {
136        for (entity, parent_id) in parent_queue {
137            if let Some(parent_entity) = entity_map.get(&parent_id) {
138                if ctx.world.contains(entity) {
139                    ctx.world
140                        .insert_one(entity, Parent(*parent_entity))
141                        .expect("Failed to add Parent component");
142                }
143            } else {
144                warn!("Warning: Parent entity not found with ID '{}' for child entity {:?}", parent_id, entity);
145            }
146        }
147    }
148}