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}