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 /// For components related to physics simulation.
42 Physics,
43}
44
45/// Stores the set of type-erased functions for a registered component.
46#[derive(Debug)]
47struct ComponentVTable {
48 /// The semantic domain this component belongs to.
49 domain: SemanticDomain,
50 /// Creates a new, empty `Box<dyn AnyVec>` for this component type.
51 create_column: fn() -> Box<dyn AnyVec>,
52 /// Copies a single element from a source column to a destination column.
53 copy_row: RowCopyFn,
54}
55
56/// A registry that maps component types to their semantic domains.
57///
58/// This is a critical internal part of the `World`. It provides a single source
59/// of truth for determining which semantic group a component's data belongs to,
60/// enabling the `World` to correctly store and retrieve component data from pages.
61#[derive(Debug, Default)]
62pub struct ComponentRegistry {
63 /// Maps a component's `TypeId` to its VTable of operations.
64 mapping: HashMap<TypeId, ComponentVTable>,
65}
66
67impl ComponentRegistry {
68 /// Registers a component type with its domain and lifecycle functions.
69 pub(crate) fn register<T: Component>(&mut self, domain: SemanticDomain) {
70 self.mapping.insert(
71 TypeId::of::<T>(),
72 ComponentVTable {
73 domain,
74 create_column: || Box::new(Vec::<T>::new()),
75 copy_row: |src_col, src_row, dest_col| unsafe {
76 let src_vec = src_col.as_any().downcast_ref::<Vec<T>>().unwrap();
77 let dest_vec = dest_col.as_any_mut().downcast_mut::<Vec<T>>().unwrap();
78 dest_vec.push(src_vec.get_unchecked(src_row).clone());
79 },
80 },
81 );
82 }
83
84 /// Looks up the `SemanticDomain` for a given `TypeId`.
85 pub fn get_domain(&self, type_id: TypeId) -> Option<SemanticDomain> {
86 self.mapping.get(&type_id).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 /// (Internal) Creates empty columns for a given type signature.
103 pub(crate) fn create_columns_for_signature(
104 &self,
105 signature: &[TypeId],
106 ) -> HashMap<TypeId, Box<dyn AnyVec>> {
107 let mut columns = HashMap::new();
108 for type_id in signature {
109 let constructor = self.get_column_constructor(type_id).unwrap();
110 columns.insert(*type_id, constructor());
111 }
112 columns
113 }
114}
115
116/// A registry that provides reflection data, like type names.
117#[derive(Debug, Default)]
118pub struct TypeRegistry {
119 /// Maps a component's `TypeId` to its string name.
120 id_to_name: HashMap<TypeId, String>,
121 /// Maps a component's string name to its `TypeId`.
122 name_to_id: HashMap<String, TypeId>,
123}
124
125impl TypeRegistry {
126 /// Registers a component type, storing its name and TypeId.
127 pub(crate) fn register<T: Component>(&mut self) {
128 let type_id = TypeId::of::<T>();
129 let type_name = any::type_name::<T>().to_string();
130 self.id_to_name.insert(type_id, type_name.clone());
131 self.name_to_id.insert(type_name, type_id);
132 }
133
134 /// Gets the string name for a given TypeId.
135 pub(crate) fn get_name_of(&self, type_id: &TypeId) -> Option<&str> {
136 self.id_to_name.get(type_id).map(|s| s.as_str())
137 }
138
139 /// Gets the TypeId for a given string name.
140 pub(crate) fn get_id_of(&self, type_name: &str) -> Option<TypeId> {
141 self.name_to_id.get(type_name).copied()
142 }
143}