engine/game_objects/game_object.rs
1// SPDX-FileCopyrightText: 2025 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5use core::{
6 any::{Any, TypeId},
7 fmt::Debug,
8};
9
10use super::ComponentVec;
11
12/// Type description for allocation and type comparison of components. Generated
13/// by [`impl_game_object`](super::impl_game_object).
14#[derive(Clone, Copy)]
15pub struct ComponentInfo {
16 /// The type of the component. Eventually passed into a
17 /// [`ComponentColumn`](super::ComponentColumn), and returned from
18 /// [`ComponentColumn::component_type`](super::ComponentColumn::component_type).
19 pub type_id: TypeId,
20 /// The [size_of] the component type.
21 pub size: usize,
22 /// The [align_of] the component type.
23 pub alignment: usize,
24}
25
26/// Trait that game object types implement to be able to be added to a
27/// [`Scene`](super::Scene). Impl generated with
28/// [`impl_game_object`](super::impl_game_object).
29pub trait GameObject: Any + Debug {
30 /// Returns the allocation and type comparison details for the components of
31 /// this game object type.
32 ///
33 /// The order of the infos is the same as [`GameObject::components`].
34 fn component_infos() -> ComponentVec<ComponentInfo>;
35 /// Returns a single game object's components as anonymous byte slices, with
36 /// the type id for component type detection.
37 ///
38 /// The order of the components is the same as
39 /// [`GameObject::component_infos`].
40 fn components(&self) -> ComponentVec<(TypeId, &[u8])>;
41}
42
43/// Generates a [`GameObject`] impl block for a type.
44///
45/// This takes a list of the struct's field names and types to be used as the
46/// components of this game object. Note that component types must be
47/// [`bytemuck::Pod`].
48///
49/// The `using components` part is intended to signal that it's not a regular
50/// impl block, taking a list of field names and types similar to a struct
51/// definition, instead of trait function implementations.
52///
53/// ### Example
54///
55/// ```
56/// use engine::impl_game_object;
57///
58/// // NOTE: Zeroable and Pod are manually implemented here to avoid
59/// // the engine depending on proc macros. They should generally be
60/// // derived, if compile times allow, as Pod has a lot of
61/// // requirements that are easy to forget.
62///
63/// #[derive(Debug, Clone, Copy)]
64/// #[repr(C)]
65/// struct Position { pub x: i32, pub y: i32 }
66/// unsafe impl bytemuck::Zeroable for Position {}
67/// unsafe impl bytemuck::Pod for Position {}
68///
69/// #[derive(Debug, Clone, Copy)]
70/// #[repr(C)]
71/// struct Velocity { pub x: i32, pub y: i32 }
72/// unsafe impl bytemuck::Zeroable for Velocity {}
73/// unsafe impl bytemuck::Pod for Velocity {}
74///
75/// #[derive(Debug)]
76/// struct Foo {
77/// pub position: Position,
78/// pub velocity: Velocity,
79/// }
80///
81/// impl_game_object! {
82/// impl GameObject for Foo using components {
83/// position: Position,
84/// velocity: Velocity,
85/// }
86/// }
87/// ```
88///
89/// For a more fully featured example for using these game objects, see the
90/// documentation for [`Scene`](super::Scene).
91#[macro_export]
92macro_rules! impl_game_object {
93 // Generators for the GameObject::component_infos implementation
94 (/push_info $infos:ident/ $field_type:ty) => {
95 $infos.push($crate::game_objects::ComponentInfo {
96 type_id: core::any::TypeId::of::<$field_type>(),
97 size: core::mem::size_of::<$field_type>(),
98 alignment: core::mem::align_of::<$field_type>(),
99 });
100 };
101 (/push_info $infos:ident/ $field_type:ty, $($field_types:ty),+) => {
102 $crate::impl_game_object!(/push_info $infos/ $field_type);
103 $crate::impl_game_object!(/push_info $infos/ $($field_types),+);
104 };
105
106 // Generators for the GameObject::components implementation
107 (/push_component $components:ident, $self:ident/ $field_name:ident: $field_type:ty) => {
108 $components.push((
109 core::any::TypeId::of::<$field_type>(),
110 bytemuck::cast_ref::<$field_type, [u8; size_of::<$field_type>()]>(&$self.$field_name),
111 ));
112 };
113 (/push_component $components:ident, $self:ident/ $field_name:ident: $field_type:ty, $($field_names:ident: $field_types:ty),+) => {
114 $crate::impl_game_object!(/push_component $components, $self/ $field_name: $field_type);
115 $crate::impl_game_object!(/push_component $components, $self/ $($field_names: $field_types),+);
116 };
117
118 // The main impl-block generator
119 (impl GameObject for $struct_name:ident using components {
120 $($field_names:ident: $field_types:ty),+$(,)?
121 }) => {
122 impl $crate::game_objects::GameObject for $struct_name {
123 fn component_infos(
124 ) -> $crate::game_objects::ComponentVec<$crate::game_objects::ComponentInfo>
125 {
126 let mut infos = $crate::game_objects::ComponentVec::new();
127 $crate::impl_game_object!(/push_info infos/ $($field_types),+);
128 infos
129 }
130
131 fn components(
132 &self,
133 ) -> $crate::game_objects::ComponentVec<(core::any::TypeId, &[u8])>
134 {
135 let mut components: $crate::game_objects::ComponentVec::<
136 (core::any::TypeId, &[u8]),
137 > = $crate::game_objects::ComponentVec::new();
138 $crate::impl_game_object!(/push_component components, self/ $($field_names: $field_types),+);
139 components
140 }
141 }
142 };
143}
144
145pub use impl_game_object;
146
147#[cfg(test)]
148mod tests {
149 use core::any::TypeId;
150
151 use bytemuck::from_bytes;
152
153 #[test]
154 fn define_game_object_works() {
155 #[derive(Debug)]
156 struct TestGameObject {
157 pub component_a: i32,
158 pub component_b: i64,
159 }
160
161 impl_game_object! {
162 impl GameObject for TestGameObject using components {
163 component_a: i32,
164 component_b: i64,
165 }
166 }
167
168 let expected_i32 = i32::MIN;
169 let expected_i64 = i64::MAX;
170 let game_object = TestGameObject {
171 component_a: expected_i32,
172 component_b: expected_i64,
173 };
174
175 use super::GameObject;
176 for (type_id, bytes) in game_object.components() {
177 if type_id == TypeId::of::<i32>() {
178 let val: &i32 = from_bytes(bytes);
179 assert_eq!(expected_i32, *val);
180 } else if type_id == TypeId::of::<i64>() {
181 let val: &i64 = from_bytes(bytes);
182 assert_eq!(expected_i64, *val);
183 } else {
184 panic!("unrecognized type id from TestGameObject::components");
185 }
186 }
187 }
188}