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}