Skip to main content

goud_engine/context_registry/scene/
prefab.rs

1//! Prefab system for reusable entity templates.
2//!
3//! A [`PrefabData`] captures one or more entities (with their components)
4//! as a template that can be instantiated multiple times into a [`World`].
5//! Prefabs support nesting via the [`PrefabRef`] component.
6
7use std::collections::{HashMap, HashSet};
8
9use crate::core::error::GoudError;
10use crate::ecs::components::hierarchy::Children;
11use crate::ecs::entity::Entity;
12use crate::ecs::Component;
13use crate::ecs::World;
14
15use super::data::{EntityData, SceneData, SerializedEntity};
16use super::serialization::deserialize_scene;
17
18// =============================================================================
19// PrefabRef Component
20// =============================================================================
21
22/// A component that references another prefab by name.
23///
24/// When an entity carries this component, the prefab system can
25/// resolve it during instantiation to recursively spawn nested
26/// prefabs.
27#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
28pub struct PrefabRef {
29    /// The name of the referenced prefab.
30    pub name: String,
31}
32
33impl PrefabRef {
34    /// Creates a new `PrefabRef` with the given prefab name.
35    pub fn new(name: &str) -> Self {
36        Self {
37            name: name.to_string(),
38        }
39    }
40}
41
42impl Component for PrefabRef {}
43
44// =============================================================================
45// PrefabData
46// =============================================================================
47
48/// A reusable entity template.
49///
50/// Contains the serialized form of one or more entities that can be
51/// instantiated (cloned) into a [`World`] any number of times. Each
52/// instantiation creates fresh entities with remapped IDs.
53#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
54pub struct PrefabData {
55    /// Human-readable name of the prefab.
56    pub name: String,
57    /// Serialized entities that make up the prefab.
58    pub entities: Vec<EntityData>,
59}
60
61impl PrefabData {
62    /// Creates a prefab from a root entity and its children.
63    ///
64    /// Serializes the root entity and recursively collects all
65    /// descendant entities via [`Children`] components.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if:
70    /// - The root entity cannot be serialized
71    /// - A cycle is detected in the hierarchy (malformed Children data)
72    pub fn from_entity(world: &World, root: Entity, name: &str) -> Result<Self, GoudError> {
73        let mut entities = Vec::new();
74        let mut visited = HashSet::new();
75        Self::collect_entity(world, root, &mut entities, &mut visited)?;
76
77        Ok(Self {
78            name: name.to_string(),
79            entities,
80        })
81    }
82
83    /// Instantiates the prefab into a world, spawning new entities.
84    ///
85    /// Returns the root entity (the first entity in the remap) and a
86    /// list of all newly spawned entities.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if deserialization fails.
91    pub fn instantiate(&self, world: &mut World) -> Result<Entity, GoudError> {
92        let (root, _) = self.instantiate_with_entities(world)?;
93        Ok(root)
94    }
95
96    /// Like [`instantiate`](Self::instantiate) but also returns the
97    /// list of newly spawned entities.
98    fn instantiate_with_entities(
99        &self,
100        world: &mut World,
101    ) -> Result<(Entity, Vec<Entity>), GoudError> {
102        if self.entities.is_empty() {
103            return Err(GoudError::InternalError(
104                "Prefab has no entities to instantiate".to_string(),
105            ));
106        }
107
108        let scene_data = SceneData {
109            name: self.name.clone(),
110            entities: self.entities.clone(),
111        };
112
113        let remap = deserialize_scene(&scene_data, world)?;
114
115        let root_key = self.entities[0].id;
116        let root = remap.0.get(&root_key).copied().ok_or_else(|| {
117            GoudError::InternalError(
118                "Root entity not found in remap after instantiation".to_string(),
119            )
120        })?;
121
122        let spawned: Vec<Entity> = remap.0.values().copied().collect();
123        Ok((root, spawned))
124    }
125
126    /// Parses a prefab from a JSON string.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the JSON is invalid.
131    pub fn from_json(json: &str) -> Result<Self, GoudError> {
132        serde_json::from_str(json)
133            .map_err(|e| GoudError::InternalError(format!("Failed to parse prefab JSON: {}", e)))
134    }
135
136    /// Serializes the prefab to a JSON string.
137    ///
138    /// # Errors
139    ///
140    /// Returns an error if serialization fails.
141    pub fn to_json(&self) -> Result<String, GoudError> {
142        serde_json::to_string_pretty(self).map_err(|e| {
143            GoudError::InternalError(format!("Failed to serialize prefab to JSON: {}", e))
144        })
145    }
146
147    /// Recursively collects entity data from a root entity.
148    ///
149    /// # Cycle Detection
150    ///
151    /// Detects cycles in the Children hierarchy. If an entity is visited twice,
152    /// returns an error. This guards against malformed hierarchy data where
153    /// a child incorrectly references an ancestor.
154    fn collect_entity(
155        world: &World,
156        entity: Entity,
157        out: &mut Vec<EntityData>,
158        visited: &mut HashSet<Entity>,
159    ) -> Result<(), GoudError> {
160        if !visited.insert(entity) {
161            return Err(GoudError::InternalError(format!(
162                "Cycle detected in entity hierarchy: entity {:?} visited twice",
163                entity
164            )));
165        }
166
167        let json = world.serialize_entity(entity).ok_or_else(|| {
168            GoudError::InternalError(format!("Failed to serialize entity {:?}", entity))
169        })?;
170
171        let components_map = json
172            .get("components")
173            .and_then(|v| v.as_object())
174            .map(|m| {
175                m.iter()
176                    .map(|(k, v)| (k.clone(), v.clone()))
177                    .collect::<HashMap<String, serde_json::Value>>()
178            })
179            .unwrap_or_default();
180
181        out.push(EntityData {
182            id: SerializedEntity::from_entity(entity),
183            components: components_map,
184        });
185
186        // Recurse into children.
187        if let Some(children) = world.get::<Children>(entity) {
188            let child_entities: Vec<Entity> = children.as_slice().to_vec();
189            for child in child_entities {
190                Self::collect_entity(world, child, out, visited)?;
191            }
192        }
193
194        Ok(())
195    }
196}
197
198// =============================================================================
199// Nested Prefab Instantiation
200// =============================================================================
201
202/// Instantiates a prefab and recursively resolves any [`PrefabRef`]
203/// components by looking up prefabs in the provided registry.
204///
205/// # Cycle Detection
206///
207/// Tracks which prefab names are currently being instantiated. If a
208/// cycle is detected (e.g., prefab A references B, B references A),
209/// returns an error.
210///
211/// # Errors
212///
213/// Returns an error if:
214/// - Instantiation of any prefab fails
215/// - A referenced prefab is not found in the registry
216/// - A cycle is detected in prefab references
217pub fn instantiate_with_prefabs(
218    data: &PrefabData,
219    world: &mut World,
220    prefab_registry: &HashMap<String, PrefabData>,
221) -> Result<Entity, GoudError> {
222    let mut visited = HashSet::new();
223    instantiate_recursive(data, world, prefab_registry, &mut visited)
224}
225
226/// Internal recursive instantiation with cycle detection.
227fn instantiate_recursive(
228    data: &PrefabData,
229    world: &mut World,
230    prefab_registry: &HashMap<String, PrefabData>,
231    visited: &mut HashSet<String>,
232) -> Result<Entity, GoudError> {
233    if !visited.insert(data.name.clone()) {
234        return Err(GoudError::InternalError(format!(
235            "Cycle detected in prefab references: '{}'",
236            data.name
237        )));
238    }
239
240    let (root, spawned) = data.instantiate_with_entities(world)?;
241
242    // Only check newly spawned entities for PrefabRef components.
243    let prefab_refs: Vec<(Entity, String)> = spawned
244        .iter()
245        .filter_map(|&e| world.get::<PrefabRef>(e).map(|pr| (e, pr.name.clone())))
246        .collect();
247
248    for (entity, ref_name) in prefab_refs {
249        // Remove the PrefabRef before recursing to avoid
250        // re-processing it in nested calls.
251        world.remove::<PrefabRef>(entity);
252
253        if let Some(child_prefab) = prefab_registry.get(&ref_name) {
254            instantiate_recursive(child_prefab, world, prefab_registry, visited)?;
255        } else {
256            return Err(GoudError::ResourceNotFound(format!(
257                "Referenced prefab '{}' not found in registry",
258                ref_name
259            )));
260        }
261    }
262
263    visited.remove(&data.name);
264
265    Ok(root)
266}
267
268// =============================================================================
269// Tests
270// =============================================================================
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use crate::core::math::Vec2;
276    use crate::ecs::components::hierarchy::Name;
277    use crate::ecs::components::Transform2D;
278
279    /// Helper: create a world with built-in serializables + PrefabRef.
280    fn test_world() -> World {
281        let mut world = World::new();
282        world.register_builtin_serializables();
283        world.register_serializable::<PrefabRef>();
284        world
285    }
286
287    // ----- from_entity + instantiate -----------------------------------------
288
289    #[test]
290    fn test_prefab_from_entity_and_instantiate() {
291        let mut world = test_world();
292
293        let entity = world.spawn_empty();
294        world.insert(entity, Name::new("soldier"));
295        world.insert(
296            entity,
297            Transform2D::new(Vec2::new(1.0, 2.0), 0.0, Vec2::one()),
298        );
299
300        let prefab = PrefabData::from_entity(&world, entity, "soldier_prefab").unwrap();
301
302        // Instantiate into same world.
303        let new_root = prefab.instantiate(&mut world).unwrap();
304
305        assert_ne!(new_root, entity, "new entity should differ");
306        let name = world.get::<Name>(new_root).unwrap();
307        assert_eq!(name.as_str(), "soldier");
308
309        let transform = world.get::<Transform2D>(new_root).unwrap();
310        assert!((transform.position.x - 1.0).abs() < f32::EPSILON);
311    }
312
313    // ----- instantiate 100 times ---------------------------------------------
314
315    #[test]
316    fn test_instantiate_100_times_isolation() {
317        let mut world = test_world();
318
319        let entity = world.spawn_empty();
320        world.insert(entity, Name::new("unit"));
321
322        let prefab = PrefabData::from_entity(&world, entity, "unit_prefab").unwrap();
323
324        let before = world.entity_count();
325
326        for _ in 0..100 {
327            prefab.instantiate(&mut world).unwrap();
328        }
329
330        assert_eq!(
331            world.entity_count(),
332            before + 100,
333            "should have 100 more entities"
334        );
335    }
336
337    // ----- nested prefab -----------------------------------------------------
338
339    #[test]
340    fn test_nested_prefab_instantiation() {
341        let mut world = test_world();
342
343        // Build prefab B (a simple entity).
344        let b_entity = world.spawn_empty();
345        world.insert(b_entity, Name::new("turret"));
346        let prefab_b = PrefabData::from_entity(&world, b_entity, "turret").unwrap();
347
348        // Build prefab A that references B via PrefabRef.
349        let a_entity = world.spawn_empty();
350        world.insert(a_entity, Name::new("tank"));
351        world.insert(a_entity, PrefabRef::new("turret"));
352        let prefab_a = PrefabData::from_entity(&world, a_entity, "tank").unwrap();
353
354        let mut registry = HashMap::new();
355        registry.insert("turret".to_string(), prefab_b);
356
357        let before = world.entity_count();
358
359        let root = instantiate_with_prefabs(&prefab_a, &mut world, &registry).unwrap();
360
361        // We should have spawned at least 2 new entities (tank + turret).
362        assert!(
363            world.entity_count() >= before + 2,
364            "both prefab entities should exist"
365        );
366
367        let name = world.get::<Name>(root).unwrap();
368        assert_eq!(name.as_str(), "tank");
369    }
370
371    // ----- cycle detection ---------------------------------------------------
372
373    #[test]
374    fn test_cycle_detection_errors() {
375        let mut world = test_world();
376
377        // Prefab A refs B.
378        let a_entity = world.spawn_empty();
379        world.insert(a_entity, Name::new("A"));
380        world.insert(a_entity, PrefabRef::new("B"));
381        let prefab_a = PrefabData::from_entity(&world, a_entity, "A").unwrap();
382
383        // Prefab B refs A.
384        let b_entity = world.spawn_empty();
385        world.insert(b_entity, Name::new("B"));
386        world.insert(b_entity, PrefabRef::new("A"));
387        let prefab_b = PrefabData::from_entity(&world, b_entity, "B").unwrap();
388
389        let mut registry = HashMap::new();
390        registry.insert("A".to_string(), prefab_a.clone());
391        registry.insert("B".to_string(), prefab_b);
392
393        let result = instantiate_with_prefabs(&prefab_a, &mut world, &registry);
394
395        assert!(result.is_err(), "should detect cycle");
396        let err_msg = format!("{:?}", result.unwrap_err());
397        assert!(
398            err_msg.contains("Cycle") || err_msg.contains("cycle"),
399            "error should mention cycle"
400        );
401    }
402
403    // ----- JSON roundtrip ----------------------------------------------------
404
405    #[test]
406    fn test_prefab_json_roundtrip() {
407        let mut world = test_world();
408
409        let entity = world.spawn_empty();
410        world.insert(entity, Name::new("archer"));
411        world.insert(
412            entity,
413            Transform2D::new(Vec2::new(5.0, 10.0), 0.0, Vec2::one()),
414        );
415
416        let prefab = PrefabData::from_entity(&world, entity, "archer_prefab").unwrap();
417
418        let json = prefab.to_json().unwrap();
419        assert!(json.contains("archer_prefab"));
420        assert!(json.contains("archer"));
421
422        let parsed = PrefabData::from_json(&json).unwrap();
423        assert_eq!(parsed.name, "archer_prefab");
424        assert_eq!(parsed.entities.len(), prefab.entities.len());
425    }
426
427    #[test]
428    fn test_prefab_from_invalid_json_errors() {
429        let result = PrefabData::from_json("{{invalid}}}");
430        assert!(result.is_err());
431    }
432}