khora_data/ecs/registry.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Defines the `ComponentRegistry` and `SemanticDomain` for the CRPECS.
16
17use bincode::{Decode, Encode};
18
19use crate::ecs::{AnyVec, Component};
20use std::{
21 any::{self, TypeId},
22 collections::HashMap,
23};
24
25/// Type alias for the row copy function pointer.
26type RowCopyFn = unsafe fn(&dyn AnyVec, usize, &mut dyn AnyVec);
27
28/// Defines the semantic domains a component can belong to.
29///
30/// This is used by the [`ComponentRegistry`] to map a component type to its
31/// corresponding `ComponentPage` group. This grouping is the core principle that
32/// allows the CRPECS to have fast, domain-specific queries.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
34pub enum SemanticDomain {
35 /// For components related to position, physics, and the scene graph.
36 Spatial,
37 /// For components related to rendering, such as mesh and material handles.
38 Render,
39 /// For components related to audio, such as audio sources and listeners.
40 Audio,
41}
42
43/// Stores the set of type-erased functions for a registered component.
44#[derive(Debug)]
45struct ComponentVTable {
46 /// The semantic domain this component belongs to.
47 domain: SemanticDomain,
48 /// Creates a new, empty `Box<dyn AnyVec>` for this component type.
49 create_column: fn() -> Box<dyn AnyVec>,
50 /// Copies a single element from a source column to a destination column.
51 copy_row: RowCopyFn,
52}
53
54/// A registry that maps component types to their semantic domains.
55///
56/// This is a critical internal part of the `World`. It provides a single source
57/// of truth for determining which semantic group a component's data belongs to,
58/// enabling the `World` to correctly store and retrieve component data from pages.
59#[derive(Debug, Default)]
60pub struct ComponentRegistry {
61 /// Maps a component's `TypeId` to its VTable of operations.
62 mapping: HashMap<TypeId, ComponentVTable>,
63}
64
65impl ComponentRegistry {
66 /// Registers a component type with its domain and lifecycle functions.
67 pub(crate) fn register<T: Component>(&mut self, domain: SemanticDomain) {
68 self.mapping.insert(
69 TypeId::of::<T>(),
70 ComponentVTable {
71 domain,
72 create_column: || Box::new(Vec::<T>::new()),
73 copy_row: |src_col, src_row, dest_col| unsafe {
74 let src_vec = src_col.as_any().downcast_ref::<Vec<T>>().unwrap();
75 let dest_vec = dest_col.as_any_mut().downcast_mut::<Vec<T>>().unwrap();
76 dest_vec.push(src_vec.get_unchecked(src_row).clone());
77 },
78 },
79 );
80 }
81
82 /// (Internal) Looks up the `SemanticDomain` for a given component type.
83 pub fn domain_of<T: Component>(&self) -> Option<SemanticDomain> {
84 self.mapping
85 .get(&TypeId::of::<T>())
86 .map(|vtable| vtable.domain)
87 }
88
89 /// (Internal) Gets the column constructor function for a given TypeId.
90 pub(crate) fn get_column_constructor(
91 &self,
92 type_id: &TypeId,
93 ) -> Option<fn() -> Box<dyn AnyVec>> {
94 self.mapping.get(type_id).map(|vtable| vtable.create_column)
95 }
96
97 /// (Internal) Gets the row copy function for a given TypeId.
98 pub(crate) fn get_row_copier(&self, type_id: &TypeId) -> Option<RowCopyFn> {
99 self.mapping.get(type_id).map(|vtable| vtable.copy_row)
100 }
101}
102
103/// A registry that provides reflection data, like type names.
104#[derive(Debug, Default)]
105pub struct TypeRegistry {
106 /// Maps a component's `TypeId` to its string name.
107 id_to_name: HashMap<TypeId, String>,
108 /// Maps a component's string name to its `TypeId`.
109 name_to_id: HashMap<String, TypeId>,
110}
111
112impl TypeRegistry {
113 /// Registers a component type, storing its name and TypeId.
114 pub(crate) fn register<T: Component>(&mut self) {
115 let type_id = TypeId::of::<T>();
116 let type_name = any::type_name::<T>().to_string();
117 self.id_to_name.insert(type_id, type_name.clone());
118 self.name_to_id.insert(type_name, type_id);
119 }
120
121 /// Gets the string name for a given TypeId.
122 pub(crate) fn get_name_of(&self, type_id: &TypeId) -> Option<&str> {
123 self.id_to_name.get(type_id).map(|s| s.as_str())
124 }
125
126 /// Gets the TypeId for a given string name.
127 pub(crate) fn get_id_of(&self, type_name: &str) -> Option<TypeId> {
128 self.name_to_id.get(type_name).copied()
129 }
130}