goud_engine/context_registry/scene/
prefab.rs1use 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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
28pub struct PrefabRef {
29 pub name: String,
31}
32
33impl PrefabRef {
34 pub fn new(name: &str) -> Self {
36 Self {
37 name: name.to_string(),
38 }
39 }
40}
41
42impl Component for PrefabRef {}
43
44#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
54pub struct PrefabData {
55 pub name: String,
57 pub entities: Vec<EntityData>,
59}
60
61impl PrefabData {
62 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 pub fn instantiate(&self, world: &mut World) -> Result<Entity, GoudError> {
92 let (root, _) = self.instantiate_with_entities(world)?;
93 Ok(root)
94 }
95
96 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 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 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 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 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
198pub 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
226fn 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 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 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#[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 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 #[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 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 #[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 #[test]
340 fn test_nested_prefab_instantiation() {
341 let mut world = test_world();
342
343 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 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, ®istry).unwrap();
360
361 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 #[test]
374 fn test_cycle_detection_errors() {
375 let mut world = test_world();
376
377 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 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, ®istry);
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 #[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}