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}