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}