Skip to main content

khora_data/ecs/
component.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//! # Component
16//!
17//! Components are data structures that are attached to entities in the ECS.
18//! They are used to store data that is associated with an entity.
19
20use crate::ecs::page::AnyVec;
21
22/// A marker trait for types that can be used as components in the ECS.
23///
24/// This trait must be implemented for any struct you wish to attach to an entity.
25/// The `'static` lifetime ensures that the component type does not contain any
26/// non-static references, and `Send + Sync` are required to allow the component
27/// data to be safely accessed from multiple threads.
28/// `Clone` is required to allow component data to be moved between pages
29/// during structural changes (like adding or removing components).
30///
31/// ## Physical layout (AGDF)
32///
33/// The three storage hooks below let a component declare *how* its column is
34/// physically laid out — the data-layer twin of GORNA's "adapt the HOW, not the
35/// WHAT". They all default to the canonical **Array-of-Structures** column
36/// (`Vec<Self>`), so existing components are bit-identical to before. A component
37/// that opts into a field-split **Structure-of-Arrays** layout (via
38/// `#[component(layout = "soa")]`) overrides all three to route through its
39/// generated SoA column instead. CRPECS storage stays a `Box<dyn AnyVec>` either
40/// way — only the concrete column type behind it changes.
41pub trait Component: Clone + 'static + Send + Sync {
42    /// Creates this component's empty storage column. Default: an AoS `Vec<Self>`.
43    fn make_column() -> Box<dyn AnyVec>
44    where
45        Self: Sized,
46    {
47        Box::new(Vec::<Self>::new())
48    }
49
50    /// Appends `self` as a new row into its column.
51    ///
52    /// # Panics
53    /// Panics if `column` is not this component's column type — an internal
54    /// invariant the registry/bundle paths uphold (columns are keyed by `TypeId`).
55    fn push_into_column(self, column: &mut dyn AnyVec)
56    where
57        Self: Sized,
58    {
59        column
60            .as_any_mut()
61            .downcast_mut::<Vec<Self>>()
62            .expect("AoS column type mismatch")
63            .push(self);
64    }
65
66    /// Clones row `src_row` from `src` into `dst` (used when an entity migrates
67    /// between pages on a structural change). Both columns are this component's
68    /// column type.
69    ///
70    /// # Panics
71    /// Panics on a column type mismatch (same invariant as [`push_into_column`]).
72    ///
73    /// [`push_into_column`]: Component::push_into_column
74    fn copy_row_between(src: &dyn AnyVec, src_row: usize, dst: &mut dyn AnyVec)
75    where
76        Self: Sized,
77    {
78        let s = src
79            .as_any()
80            .downcast_ref::<Vec<Self>>()
81            .expect("AoS column type mismatch");
82        let dst = dst
83            .as_any_mut()
84            .downcast_mut::<Vec<Self>>()
85            .expect("AoS column type mismatch");
86        dst.push(s[src_row].clone());
87    }
88
89    /// Reads row `row` of `column` as an owned value — the uniform by-value
90    /// access path that works for both AoS (clone the `&T`) and field-SoA
91    /// (gather the fields) layouts. Used by `World::clone_component`, the
92    /// `Soa<T>` query, and the serialization recipe.
93    ///
94    /// # Panics
95    /// Panics on a column type mismatch.
96    fn clone_from_column(column: &dyn AnyVec, row: usize) -> Self
97    where
98        Self: Sized,
99    {
100        column
101            .as_any()
102            .downcast_ref::<Vec<Self>>()
103            .expect("AoS column type mismatch")[row]
104            .clone()
105    }
106
107    /// Overwrites row `row` of `column` with `self` — the uniform by-value write
108    /// path for both layouts (AoS assigns the slot; field-SoA scatters into the
109    /// lanes). Used by `World::set_component`.
110    ///
111    /// # Panics
112    /// Panics on a column type mismatch.
113    fn set_in_column(self, column: &mut dyn AnyVec, row: usize)
114    where
115        Self: Sized,
116    {
117        let v = column
118            .as_any_mut()
119            .downcast_mut::<Vec<Self>>()
120            .expect("AoS column type mismatch");
121        v[row] = self;
122    }
123}