hecs/
bundle.rs

1use crate::alloc::vec::Vec;
2use core::any::{type_name, TypeId};
3use core::ptr::NonNull;
4use core::{fmt, mem};
5
6use crate::archetype::TypeInfo;
7use crate::Component;
8
9/// Checks if a query is satisfied by a bundle. This is primarily useful for unit tests.
10pub fn bundle_satisfies_query<B: Bundle, Q: crate::Query>() -> bool {
11    use crate::Fetch;
12
13    let arch = B::with_static_type_info(|info| crate::Archetype::new(info.into()));
14    Q::Fetch::access(&arch).is_some()
15}
16
17/// Checks if a query is satisfied by a dynamic bundle. For static bundles, see [bundle_satisfies_query].
18/// This is primarily useful for unit tests.
19pub fn dynamic_bundle_satisfies_query<B: DynamicBundle, Q: crate::Query>(b: &B) -> bool {
20    use crate::Fetch;
21
22    let arch = crate::Archetype::new(b.type_info());
23    Q::Fetch::access(&arch).is_some()
24}
25
26/// A dynamically typed collection of components
27///
28/// Bundles composed of exactly the same types are semantically equivalent, regardless of order. The
29/// interface of this trait, except `has` is a private implementation detail.
30#[allow(clippy::missing_safety_doc)]
31pub unsafe trait DynamicBundle {
32    /// Returns a `TypeId` uniquely identifying the set of components, if known
33    #[doc(hidden)]
34    fn key(&self) -> Option<TypeId> {
35        None
36    }
37
38    /// Checks if the Bundle contains the given `T`:
39    ///
40    /// ```
41    /// # use hecs::DynamicBundle;
42    ///
43    /// let my_bundle = (0i32, 10.0f32);
44    /// assert!(my_bundle.has::<i32>());
45    /// assert!(my_bundle.has::<f32>());
46    /// assert!(!my_bundle.has::<usize>());
47    /// ```
48    fn has<T: Component>(&self) -> bool {
49        self.with_ids(|types| types.contains(&TypeId::of::<T>()))
50    }
51
52    /// Invoke a callback on the fields' type IDs, sorted by descending alignment then id
53    #[doc(hidden)]
54    fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T;
55
56    /// Obtain the fields' TypeInfos, sorted by descending alignment then id
57    #[doc(hidden)]
58    fn type_info(&self) -> Vec<TypeInfo>;
59    /// Allow a callback to move all components out of the bundle
60    ///
61    /// Must invoke `f` only with a valid pointer and the pointee's type and size.
62    #[doc(hidden)]
63    unsafe fn put(self, f: impl FnMut(*mut u8, TypeInfo));
64}
65
66/// A statically typed collection of components
67///
68/// Bundles composed of exactly the same types are semantically equivalent, regardless of order. The
69/// interface of this trait is a private implementation detail.
70#[allow(clippy::missing_safety_doc)]
71pub unsafe trait Bundle: DynamicBundle {
72    #[doc(hidden)]
73    fn with_static_ids<T>(f: impl FnOnce(&[TypeId]) -> T) -> T;
74
75    /// Obtain the fields' TypeInfos, sorted by descending alignment then id
76    #[doc(hidden)]
77    fn with_static_type_info<T>(f: impl FnOnce(&[TypeInfo]) -> T) -> T;
78
79    /// Construct `Self` by moving components out of pointers fetched by `f`
80    ///
81    /// # Safety
82    ///
83    /// `f` must produce pointers to the expected fields. The implementation must not read from any
84    /// pointers if any call to `f` returns `None`.
85    #[doc(hidden)]
86    unsafe fn get(f: impl FnMut(TypeInfo) -> Option<NonNull<u8>>) -> Result<Self, MissingComponent>
87    where
88        Self: Sized;
89}
90
91/// A dynamically typed collection of cloneable components
92#[allow(clippy::missing_safety_doc)]
93pub unsafe trait DynamicBundleClone: DynamicBundle {
94    /// Allow a callback to move all components out of the bundle
95    ///
96    /// Must invoke `f` only with a valid pointer, the pointee's type and size, and a `DynamicClone`
97    /// constructed for the pointee's type.
98    #[doc(hidden)]
99    unsafe fn put_with_clone(self, f: impl FnMut(*mut u8, TypeInfo, DynamicClone));
100}
101
102#[derive(Copy, Clone)]
103/// Type-erased [`Clone`] implementation
104pub struct DynamicClone {
105    pub(crate) func: unsafe fn(*const u8, &mut dyn FnMut(*mut u8, TypeInfo)),
106}
107
108impl DynamicClone {
109    /// Create a new type ereased cloner for the type T
110    pub fn new<T: Component + Clone>() -> Self {
111        Self {
112            func: |src, f| {
113                let mut tmp = unsafe { (*src.cast::<T>()).clone() };
114                f((&mut tmp as *mut T).cast(), TypeInfo::of::<T>());
115                core::mem::forget(tmp);
116            },
117        }
118    }
119}
120
121/// Error indicating that an entity did not have a required component
122#[derive(Debug, Clone, Eq, PartialEq, Hash)]
123pub struct MissingComponent(&'static str);
124
125impl MissingComponent {
126    /// Construct an error representing a missing `T`
127    pub fn new<T: Component>() -> Self {
128        Self(type_name::<T>())
129    }
130}
131
132impl fmt::Display for MissingComponent {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        write!(f, "missing {} component", self.0)
135    }
136}
137
138#[cfg(feature = "std")]
139impl std::error::Error for MissingComponent {}
140
141macro_rules! tuple_impl {
142    ($($name: ident),*) => {
143        unsafe impl<$($name: Component),*> DynamicBundle for ($($name,)*) {
144            fn has<T: Component>(&self) -> bool {
145                false $(|| TypeId::of::<$name>() == TypeId::of::<T>())*
146            }
147
148            fn key(&self) -> Option<TypeId> {
149                Some(TypeId::of::<Self>())
150            }
151
152            fn with_ids<T>(&self, f: impl FnOnce(&[TypeId]) -> T) -> T {
153                Self::with_static_ids(f)
154            }
155
156            fn type_info(&self) -> Vec<TypeInfo> {
157                Self::with_static_type_info(|info| info.to_vec())
158            }
159
160            #[allow(unused_variables, unused_mut)]
161            unsafe fn put(self, mut f: impl FnMut(*mut u8, TypeInfo)) {
162                #[allow(non_snake_case)]
163                let ($(mut $name,)*) = self;
164                $(
165                    f(
166                        (&mut $name as *mut $name).cast::<u8>(),
167                        TypeInfo::of::<$name>()
168                    );
169                    mem::forget($name);
170                )*
171            }
172        }
173
174        unsafe impl<$($name: Component + Clone),*> DynamicBundleClone for ($($name,)*) {
175            // Compiler false positive warnings
176            #[allow(unused_variables, unused_mut)]
177            unsafe fn put_with_clone(self, mut f: impl FnMut(*mut u8, TypeInfo, DynamicClone)) {
178                #[allow(non_snake_case)]
179                let ($(mut $name,)*) = self;
180                $(
181                    f(
182                        (&mut $name as *mut $name).cast::<u8>(),
183                        TypeInfo::of::<$name>(),
184                        DynamicClone::new::<$name>()
185                    );
186                    mem::forget($name);
187                )*
188            }
189        }
190
191        #[allow(clippy::zero_repeat_side_effects)]
192        unsafe impl<$($name: Component),*> Bundle for ($($name,)*) {
193            fn with_static_ids<T>(f: impl FnOnce(&[TypeId]) -> T) -> T {
194                const N: usize = count!($($name),*);
195                let mut xs: [(usize, TypeId); N] = [$((mem::align_of::<$name>(), TypeId::of::<$name>())),*];
196                xs.sort_unstable_by(|x, y| x.0.cmp(&y.0).reverse().then(x.1.cmp(&y.1)));
197                let mut ids = [TypeId::of::<()>(); N];
198                for (slot, &(_, id)) in ids.iter_mut().zip(xs.iter()) {
199                    *slot = id;
200                }
201                f(&ids)
202            }
203
204            fn with_static_type_info<T>(f: impl FnOnce(&[TypeInfo]) -> T) -> T {
205                const N: usize = count!($($name),*);
206                let mut xs: [TypeInfo; N] = [$(TypeInfo::of::<$name>()),*];
207                xs.sort_unstable();
208                f(&xs)
209            }
210
211            #[allow(unused_variables, unused_mut)]
212            unsafe fn get(mut f: impl FnMut(TypeInfo) -> Option<NonNull<u8>>) -> Result<Self, MissingComponent> {
213                #[allow(non_snake_case)]
214                let ($(mut $name,)*) = ($(
215                    f(TypeInfo::of::<$name>()).ok_or_else(MissingComponent::new::<$name>)?
216                        .as_ptr()
217                        .cast::<$name>(),)*
218                );
219                Ok(($($name.read(),)*))
220            }
221        }
222    }
223}
224
225macro_rules! count {
226    () => { 0 };
227    ($x: ident $(, $rest: ident)*) => { 1 + count!($($rest),*) };
228}
229
230smaller_tuples_too!(tuple_impl, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A);