//! Prefabs — named templates that spawn an entity already carrying a set of //! components. //! //! The engine deliberately has **no parallel "object type" system**: an entity //! *is* its set of components. A [`Prefab`] is therefore nothing more than a //! named bundle of **(component name, value)** specs, applied on spawn through //! the [`TypeRegistry`]. "Spawn a Cube" means "spawn an entity, then set its //! `MeshRenderer` to a cube" — the same name-keyed path the editor and scripts //! already use, so prefabs are pure data (serializable, dual-editable) rather //! than code. //! //! This is what makes the editor's add-menu **data-driven**: the menu lists the //! prefabs in a [`PrefabRegistry`] instead of hard-coding one button per type. //! //! ``` //! use oxide_engine::prelude::*; //! use oxide_engine::prefab::{ComponentSpec, Prefab, PrefabRegistry}; //! use oxide_engine::reflect::TypeRegistry; //! //! // A registry that knows how to round-trip MeshRenderer by name. //! let mut types = TypeRegistry::new(); //! types.register_reflected::("MeshRenderer"); //! //! // A "Cube" prefab: an entity carrying a default MeshRenderer (shape = Cube). //! let mut prefabs = PrefabRegistry::new(); //! prefabs.register( //! Prefab::new("Cube") //! .with(ComponentSpec::of("MeshRenderer", &MeshRenderer::default()).unwrap()), //! ); //! //! let mut scene = Scene::new(); //! let cube = prefabs.spawn("Cube", &mut scene, &types).unwrap(); //! assert!(types.has(scene.world(), cube, "MeshRenderer").unwrap()); //! ``` use std::collections::BTreeMap; use hecs::Entity; use serde::{Deserialize, Serialize}; use crate::math::Transform; use crate::reflect::TypeRegistry; use crate::scene::Scene; /// One component a prefab attaches: a registered type **name** plus its value /// serialized as **RON** — the same representation [`TypeRegistry::set_ron`] /// consumes. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ComponentSpec { /// The component's registered name in the [`TypeRegistry`]. pub type_name: String, /// The component value as RON. pub ron: String, } impl ComponentSpec { /// A spec from a name and an already-serialized RON string. pub fn new(type_name: impl Into, ron: impl Into) -> Self { Self { type_name: type_name.into(), ron: ron.into(), } } /// A spec built by serializing a concrete component `value`. Returns `None` /// if it cannot be serialized to RON. pub fn of(type_name: impl Into, value: &T) -> Option { ron::to_string(value) .ok() .map(|ron| Self::new(type_name, ron)) } } /// A named spawn template: a node name plus the components to attach beyond the /// node-baked ones. /// /// Every spawned entity already carries `Node`, `Transform`, and `Layer` /// (auto-attached by [`Scene::spawn`]); a prefab's [`components`](Self::components) /// are layered on top. A spec named `"Transform"` overrides the identity /// transform `spawn` starts with, so a prefab can place itself. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Prefab { /// The name given to the spawned node (also the registry key). pub name: String, /// Components attached on spawn, applied in order. pub components: Vec, } impl Prefab { /// An empty prefab (spawns a bare node with just the node-baked components). pub fn new(name: impl Into) -> Self { Self { name: name.into(), components: Vec::new(), } } /// Adds a component spec (builder style). pub fn with(mut self, spec: ComponentSpec) -> Self { self.components.push(spec); self } } /// A registry of prefabs keyed by name — the data-driven source for the /// editor's "add an entity that already carries these components" menu. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct PrefabRegistry { prefabs: BTreeMap, } impl PrefabRegistry { /// An empty registry. pub fn new() -> Self { Self::default() } /// Registers `prefab` under its [`name`](Prefab::name). Re-registering the /// same name replaces the entry. pub fn register(&mut self, prefab: Prefab) { self.prefabs.insert(prefab.name.clone(), prefab); } /// The prefab registered under `name`, if any. pub fn get(&self, name: &str) -> Option<&Prefab> { self.prefabs.get(name) } /// Whether a prefab is registered under `name`. pub fn contains(&self, name: &str) -> bool { self.prefabs.contains_key(name) } /// The registered prefab names, sorted — what an add-menu lists. pub fn names(&self) -> impl Iterator + '_ { self.prefabs.keys().map(String::as_str) } /// The number of registered prefabs. pub fn len(&self) -> usize { self.prefabs.len() } /// Whether no prefabs are registered. pub fn is_empty(&self) -> bool { self.prefabs.is_empty() } /// Spawns the named prefab as a **root** entity, applying its component /// specs through `registry`. Returns the new entity, or `None` if `name` /// isn't registered. /// /// Application is best-effort: a spec whose type isn't registered or whose /// RON doesn't parse is skipped (the entity is still created with whatever /// applied). Use [`unknown_specs`](Self::unknown_specs) to validate a prefab /// against a registry up front. pub fn spawn(&self, name: &str, scene: &mut Scene, registry: &TypeRegistry) -> Option { let prefab = self.prefabs.get(name)?; let entity = scene.spawn(prefab.name.clone(), Transform::IDENTITY); apply(prefab, entity, scene, registry); Some(entity) } /// Like [`spawn`](Self::spawn) but parents the new entity under `parent`. pub fn spawn_child( &self, name: &str, parent: Entity, scene: &mut Scene, registry: &TypeRegistry, ) -> Option { let prefab = self.prefabs.get(name)?; let entity = scene.spawn_child(parent, prefab.name.clone(), Transform::IDENTITY); apply(prefab, entity, scene, registry); Some(entity) } /// The type names a prefab references that `registry` doesn't know — empty /// when the prefab will spawn fully. Handy for surfacing authoring typos. pub fn unknown_specs(&self, name: &str, registry: &TypeRegistry) -> Vec { self.prefabs .get(name) .map(|p| { p.components .iter() .filter(|s| !registry.is_registered(&s.type_name)) .map(|s| s.type_name.clone()) .collect() }) .unwrap_or_default() } } /// Applies a prefab's component specs onto an already-spawned `entity`. fn apply(prefab: &Prefab, entity: Entity, scene: &mut Scene, registry: &TypeRegistry) { for spec in &prefab.components { // Best-effort: an unknown type or malformed RON simply doesn't apply, // leaving the rest of the prefab intact. let _ = registry.set_ron(scene.world_mut(), entity, &spec.type_name, &spec.ron); } } #[cfg(test)] mod tests { use super::*; use crate::render::{MeshRenderer, PrimitiveShape}; use crate::scene::Node; fn types() -> TypeRegistry { let mut r = TypeRegistry::new(); r.register_reflected::("Transform"); r.register_reflected::("MeshRenderer"); r } #[test] fn registry_lists_names_sorted_and_looks_up() { let mut prefabs = PrefabRegistry::new(); prefabs.register(Prefab::new("Sphere")); prefabs.register(Prefab::new("Cube")); assert_eq!(prefabs.names().collect::>(), vec!["Cube", "Sphere"]); assert!(prefabs.contains("Cube")); assert!(prefabs.get("Cube").is_some()); assert_eq!(prefabs.len(), 2); } #[test] fn spawn_attaches_specced_components() { let types = types(); let mut prefabs = PrefabRegistry::new(); let mesh = MeshRenderer { shape: PrimitiveShape::Sphere, ..MeshRenderer::default() }; prefabs .register(Prefab::new("Ball").with(ComponentSpec::of("MeshRenderer", &mesh).unwrap())); let mut scene = Scene::new(); let e = prefabs.spawn("Ball", &mut scene, &types).unwrap(); // Node name comes from the prefab; the spec'd component is attached. assert_eq!(scene.world().get::<&Node>(e).unwrap().name, "Ball"); let got = scene.world().get::<&MeshRenderer>(e).unwrap(); assert_eq!(got.shape, PrimitiveShape::Sphere); } #[test] fn spawn_child_parents_under_the_target() { let types = types(); let mut prefabs = PrefabRegistry::new(); prefabs.register(Prefab::new("Child")); let mut scene = Scene::new(); let parent = scene.spawn("parent", Transform::IDENTITY); let child = prefabs .spawn_child("Child", parent, &mut scene, &types) .unwrap(); assert_eq!(scene.parent(child), Some(parent)); } #[test] fn transform_spec_overrides_the_identity_spawn() { let types = types(); let mut prefabs = PrefabRegistry::new(); let placed = Transform::from_translation(crate::math::Vec3::new(1.0, 2.0, 3.0)); prefabs .register(Prefab::new("Placed").with(ComponentSpec::of("Transform", &placed).unwrap())); let mut scene = Scene::new(); let e = prefabs.spawn("Placed", &mut scene, &types).unwrap(); let t = scene.local_transform(e).unwrap(); assert!((t.translation - crate::math::Vec3::new(1.0, 2.0, 3.0)).length() < 1e-6); } #[test] fn unknown_prefab_name_spawns_nothing() { let types = types(); let prefabs = PrefabRegistry::new(); let mut scene = Scene::new(); assert!(prefabs.spawn("Nope", &mut scene, &types).is_none()); } #[test] fn unknown_specs_are_reported_and_skipped() { let types = types(); // knows Transform + MeshRenderer let mut prefabs = PrefabRegistry::new(); prefabs.register( Prefab::new("Mixed") .with(ComponentSpec::of("MeshRenderer", &MeshRenderer::default()).unwrap()) .with(ComponentSpec::new("Ghost", "()")), ); assert_eq!(prefabs.unknown_specs("Mixed", &types), vec!["Ghost"]); // Spawn still succeeds; the known component applies, the ghost is skipped. let mut scene = Scene::new(); let e = prefabs.spawn("Mixed", &mut scene, &types).unwrap(); assert!(types.has(scene.world(), e, "MeshRenderer").unwrap()); } #[test] fn prefab_round_trips_through_ron() { let mut prefabs = PrefabRegistry::new(); prefabs.register( Prefab::new("Cube") .with(ComponentSpec::of("MeshRenderer", &MeshRenderer::default()).unwrap()), ); let ron = ron::to_string(&prefabs).unwrap(); let back: PrefabRegistry = ron::from_str(&ron).unwrap(); assert_eq!(prefabs, back); } }