Skip to main content

kvas_engine/
registry.rs

1//! Type-erased component table. The editor and scene loader work through this and
2//! never name a concrete component, so adding a component = one `info::<T>()` line.
3
4use crate::components::*;
5use crate::reflect::Field;
6use kvas_ecs::{Entity, World};
7use serde_json::Value;
8
9/// Type-erased editor/persistence operations for a registered component.
10/// Callback functions are host-owned; they must outlive every registry using them.
11pub struct ComponentInfo {
12    /// Stable component name, matching the ECS storage name.
13    pub name: &'static str,
14    /// Whether user-facing removal should be permitted.
15    pub removable: bool,
16    /// Creates/checks the component storage in a fresh world using permanent host code.
17    pub register: fn(&mut World),
18    /// Inserts a default value, replacing an existing one.
19    pub add: fn(&mut World, Entity),
20    /// Removes the component if present (does not enforce `removable`).
21    pub remove: fn(&mut World, Entity),
22    /// Checks presence on a living entity.
23    pub has: fn(&World, Entity) -> bool,
24    /// Serializes a present component; missing/unserializable values yield `None`.
25    pub to_json: fn(&World, Entity) -> Option<Value>,
26    /// Deserializes and inserts a component, reporting malformed values.
27    pub from_json: fn(&mut World, Entity, Value) -> Result<(), String>,
28    /// Checks that a present value can round-trip through its persisted serde representation.
29    /// This rejects non-finite built-in floats that JSON would otherwise replace with null.
30    pub validate: fn(&World, Entity) -> Result<(), String>,
31    /// Visits editable borrowed fields if the component exists.
32    pub edit: for<'a> fn(&'a mut World, Entity, &mut dyn FnMut(Vec<Field<'a>>)),
33}
34
35fn info<T: Component>() -> ComponentInfo {
36    ComponentInfo {
37        name: <T as kvas_ecs::Component>::NAME,
38        removable: T::REMOVABLE,
39        register: |world| world.register::<T>(),
40        add: |w, e| w.insert(e, T::default()),
41        remove: |w, e| {
42            w.remove::<T>(e);
43        },
44        has: |w, e| w.has::<T>(e),
45        to_json: |w, e| w.get::<T>(e).and_then(|c| serde_json::to_value(c).ok()),
46        from_json: |w, e, v| {
47            w.insert(e, serde_json::from_value::<T>(v).map_err(|err| format!("{}: {err}", <T as kvas_ecs::Component>::NAME))?);
48            Ok(())
49        },
50        validate: |world, entity| {
51            let Some(component) = world.get::<T>(entity) else {
52                return Ok(());
53            };
54            let json = serde_json::to_value(component).map_err(|e| e.to_string())?;
55            serde_json::from_value::<T>(json).map(|_| ()).map_err(|e| format!("{}: {e}", <T as kvas_ecs::Component>::NAME))
56        },
57        edit: |w, e, f| {
58            if let Some(c) = w.get_mut::<T>(e) {
59                f(c.fields());
60            }
61        },
62    }
63}
64
65/// Ordered built-in component reflection/serialization table.
66/// This is distinct from ECS registration: use [`Self::register`] for host extensions.
67pub struct Registry {
68    /// Component metadata in Inspector/serialization order.
69    pub components: Vec<ComponentInfo>,
70}
71
72impl Default for Registry {
73    fn default() -> Self {
74        Self {
75            components: vec![
76                info::<Name>(),
77                info::<Tags>(),
78                info::<UiElement>(),
79                info::<Lifetime>(),
80                info::<Visibility>(),
81                info::<MoveTween>(),
82                info::<TransformAnimation>(),
83                info::<NeuralAgent>(),
84                info::<Transform>(),
85                info::<MeshRenderer>(),
86                info::<EditableMesh>(),
87                info::<Shape>(),
88                info::<Camera>(),
89                info::<Light>(),
90                info::<RigidBody>(),
91                info::<Collider>(),
92                info::<Joint>(),
93                info::<Scripts>(),
94                info::<Spin>(),
95                info::<Bob>(),
96                info::<Pulse>(),
97                info::<Wobble>(),
98                info::<ColorCycle>(),
99                info::<Cloth>(),
100                info::<Grass>(),
101                info::<Tree>(),
102                info::<Decal>(),
103                info::<VoxelSmoke>(),
104                info::<VoxelClouds>(),
105                info::<VoxelWater>(),
106                info::<Switch>(),
107                info::<Slider>(),
108                info::<AudioSource>(),
109                info::<MeshModifiers>(),
110                info::<TexturePaint>(),
111                info::<ParticleEmitter>(),
112                info::<Animator>(),
113                info::<CharacterIk>(),
114                info::<Ragdoll>(),
115            ],
116        }
117    }
118}
119
120impl Registry {
121    /// Registers every metadata type in a fresh world through host-owned callbacks.
122    /// Needed when loading scenes containing permanent host extension components.
123    pub fn register_world(&self, world: &mut World) {
124        for component in &self.components { (component.register)(world); }
125    }
126
127    /// Finds metadata by exact component name.
128    pub fn get(&self, name: &str) -> Option<&ComponentInfo> {
129        self.components.iter().find(|c| c.name == name)
130    }
131
132    /// Registers an editor-visible component in this table and a host world.
133    /// Re-registering the same type is idempotent. Call only in permanent host code,
134    /// because native library unloading would invalidate storage/callback vtables.
135    pub fn register<T: Component>(&mut self, world: &mut World) {
136        world.register::<T>();
137        let name = <T as kvas_ecs::Component>::NAME;
138        if self.get(name).is_none() {
139            self.components.push(info::<T>());
140        }
141    }
142
143    /// Metadata for components present on this entity, preserving registry order.
144    pub fn of_entity<'a>(&'a self, world: &World, entity: Entity) -> Vec<&'a ComponentInfo> {
145        self.components.iter().filter(|c| (c.has)(world, entity)).collect()
146    }
147
148    /// Reports registered component values that cannot round-trip through JSON.
149    /// Unknown/runtime-only storages are ignored; stale handles return one diagnostic.
150    pub fn validate_entity(&self, world: &World, entity: Entity) -> Vec<String> {
151        if !world.is_alive(entity) {
152            return vec!["entity is not alive".into()];
153        }
154        self.components.iter().filter_map(|component| (component.validate)(world, entity).err()).collect()
155    }
156
157    /// Inserts a default named component only when it is absent.
158    /// Returns whether insertion occurred; errors report unknown types or stale handles.
159    pub fn add_named(&self, world: &mut World, entity: Entity, name: &str) -> Result<bool, String> {
160        if !world.is_alive(entity) {
161            return Err("entity is not alive".into());
162        }
163        let info = self.get(name).ok_or_else(|| format!("unknown component '{name}'"))?;
164        if (info.has)(world, entity) {
165            return Ok(false);
166        }
167        (info.add)(world, entity);
168        Ok(true)
169    }
170
171    /// Removes a named component, enforcing its Inspector removal policy.
172    /// Returns whether a value existed. Unknown/stale/non-removable requests fail.
173    pub fn remove_named(&self, world: &mut World, entity: Entity, name: &str) -> Result<bool, String> {
174        if !world.is_alive(entity) {
175            return Err("entity is not alive".into());
176        }
177        let info = self.get(name).ok_or_else(|| format!("unknown component '{name}'"))?;
178        if !info.removable {
179            return Err(format!("component '{name}' cannot be removed"));
180        }
181        let present = (info.has)(world, entity);
182        (info.remove)(world, entity);
183        Ok(present)
184    }
185
186    /// Copies every registered component from one entity to another (used for duplicate).
187    pub fn copy_entity(&self, world: &mut World, from: Entity, to: Entity) {
188        for c in &self.components {
189            if let Some(v) = (c.to_json)(world, from) {
190                let _ = (c.from_json)(world, to, v);
191            }
192        }
193    }
194}