legion_systems/
resource.rs

1use downcast_rs::{impl_downcast, Downcast};
2use fxhash::FxHashMap;
3use legion_core::borrow::{AtomicRefCell, Ref, RefMut};
4use legion_core::query::{Read, ReadOnly, Write};
5use std::{
6    any::TypeId,
7    marker::PhantomData,
8    ops::{Deref, DerefMut},
9};
10
11#[cfg(not(feature = "ffi"))]
12/// A type ID identifying a component type.
13#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
14pub struct ResourceTypeId(TypeId);
15
16#[cfg(not(feature = "ffi"))]
17impl ResourceTypeId {
18    /// Gets the component type ID that represents type `T`.
19    pub fn of<T: Resource>() -> Self { Self(TypeId::of::<T>()) }
20}
21
22#[cfg(feature = "ffi")]
23/// A type ID identifying a component type.
24#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
25pub struct ResourceTypeId(TypeId, u32);
26
27#[cfg(feature = "ffi")]
28impl ResourceTypeId {
29    /// Gets the component type ID that represents type `T`.
30    pub fn of<T: Resource>() -> Self { Self(TypeId::of::<T>(), 0) }
31}
32
33/// Trait which is implemented for tuples of resources and singular resources. This abstracts
34/// fetching resources to allow for ergonomic fetching.
35///
36/// # Example:
37/// ```
38///
39/// struct TypeA(usize);
40/// struct TypeB(usize);
41///
42/// use legion_core::prelude::*;
43/// use legion_systems::prelude::*;
44/// let mut resources = Resources::default();
45/// resources.insert(TypeA(55));
46/// resources.insert(TypeB(12));
47///
48/// {
49///     let (a, mut b) = <(Read<TypeA>, Write<TypeB>)>::fetch_mut(&mut resources);
50///     assert_ne!(a.0, b.0);
51///     b.0 = a.0;
52/// }
53///
54/// {
55///     let (a, b) = <(Read<TypeA>, Read<TypeB>)>::fetch(&resources);
56///     assert_eq!(a.0, b.0);
57/// }
58///
59/// ```
60pub trait ResourceSet: Send + Sync {
61    type PreparedResources;
62
63    /// Fetches all defined resources, without checking mutability.
64    ///
65    /// # Safety
66    /// It is up to the end user to validate proper mutability rules across the resources being accessed.
67    ///
68    unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources;
69
70    fn fetch_mut(resources: &mut Resources) -> Self::PreparedResources {
71        // safe because mutable borrow ensures exclusivity
72        unsafe { Self::fetch_unchecked(resources) }
73    }
74
75    fn fetch(resources: &Resources) -> Self::PreparedResources
76    where
77        Self: ReadOnly,
78    {
79        unsafe { Self::fetch_unchecked(resources) }
80    }
81}
82
83/// Blanket trait for resource types.
84pub trait Resource: 'static + Downcast + Send + Sync {}
85impl<T> Resource for T where T: 'static + Send + Sync {}
86impl_downcast!(Resource);
87
88/// Wrapper type for safe, lifetime-garunteed immutable access to a resource of type `T'. This
89/// is the wrapper type which is provided to the closure in a `System`, meaning it is only scoped
90/// to that system execution.
91///
92/// # Safety
93///
94/// This type contains an immutable pointer to `T`, and must not outlive its lifetime
95pub struct PreparedRead<T: Resource> {
96    resource: *const T,
97}
98impl<T: Resource> PreparedRead<T> {
99    pub(crate) unsafe fn new(resource: *const T) -> Self { Self { resource } }
100}
101impl<T: Resource> Deref for PreparedRead<T> {
102    type Target = T;
103
104    fn deref(&self) -> &Self::Target { unsafe { &*self.resource } }
105}
106unsafe impl<T: Resource> Send for PreparedRead<T> {}
107unsafe impl<T: Resource> Sync for PreparedRead<T> {}
108
109/// Wrapper type for safe, lifetime-garunteed mutable access to a resource of type `T'. This
110/// is the wrapper type which is provided to the closure in a `System`, meaning it is only scoped
111/// to that system execution.
112///
113/// # Safety
114///
115/// This type contains an mutable pointer to `T`, and must not outlive its lifetime
116pub struct PreparedWrite<T: Resource> {
117    resource: *mut T,
118}
119impl<T: Resource> Deref for PreparedWrite<T> {
120    type Target = T;
121
122    fn deref(&self) -> &Self::Target { unsafe { &*self.resource } }
123}
124
125impl<T: Resource> DerefMut for PreparedWrite<T> {
126    fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.resource } }
127}
128impl<T: Resource> PreparedWrite<T> {
129    pub(crate) unsafe fn new(resource: *mut T) -> Self { Self { resource } }
130}
131unsafe impl<T: Resource> Send for PreparedWrite<T> {}
132unsafe impl<T: Resource> Sync for PreparedWrite<T> {}
133
134/// Ergonomic wrapper type which contains a `Ref` type.
135pub struct Fetch<'a, T: 'a + Resource> {
136    inner: Ref<'a, Box<dyn Resource>>,
137    _marker: PhantomData<T>,
138}
139impl<'a, T: Resource> Deref for Fetch<'a, T> {
140    type Target = T;
141
142    #[inline]
143    fn deref(&self) -> &Self::Target {
144        self.inner.downcast_ref::<T>().unwrap_or_else(|| {
145            panic!(
146                "Unable to downcast the resource!: {}",
147                std::any::type_name::<T>()
148            )
149        })
150    }
151}
152
153impl<'a, T: 'a + Resource + std::fmt::Debug> std::fmt::Debug for Fetch<'a, T> {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        write!(f, "{:?}", self.deref())
156    }
157}
158
159/// Ergonomic wrapper type which contains a `RefMut` type.
160pub struct FetchMut<'a, T: Resource> {
161    inner: RefMut<'a, Box<dyn Resource>>,
162    _marker: PhantomData<T>,
163}
164impl<'a, T: 'a + Resource> Deref for FetchMut<'a, T> {
165    type Target = T;
166
167    #[inline]
168    fn deref(&self) -> &Self::Target {
169        self.inner.downcast_ref::<T>().unwrap_or_else(|| {
170            panic!(
171                "Unable to downcast the resource!: {}",
172                std::any::type_name::<T>()
173            )
174        })
175    }
176}
177
178impl<'a, T: 'a + Resource> DerefMut for FetchMut<'a, T> {
179    #[inline]
180    fn deref_mut(&mut self) -> &mut T {
181        self.inner.downcast_mut::<T>().unwrap_or_else(|| {
182            panic!(
183                "Unable to downcast the resource!: {}",
184                std::any::type_name::<T>()
185            )
186        })
187    }
188}
189
190impl<'a, T: 'a + Resource + std::fmt::Debug> std::fmt::Debug for FetchMut<'a, T> {
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        write!(f, "{:?}", self.deref())
193    }
194}
195
196/// Resources container. This container stores its underlying resources in a `FxHashMap` keyed on
197/// `ResourceTypeId`. This means that the ID's used in this storage will not persist between recompiles.
198#[derive(Default)]
199pub struct Resources {
200    storage: FxHashMap<ResourceTypeId, AtomicRefCell<Box<dyn Resource>>>,
201}
202
203impl Resources {
204    /// Returns `true` if type `T` exists in the store. Otherwise, returns `false`
205    pub fn contains<T: Resource>(&self) -> bool {
206        self.storage.contains_key(&ResourceTypeId::of::<T>())
207    }
208
209    /// Inserts the instance of `T` into the store. If the type already exists, it will be silently
210    /// overwritten. If you would like to retain the instance of the resource that already exists,
211    /// call `remove` first to retrieve it.
212    pub fn insert<T: Resource>(&mut self, value: T) {
213        self.storage.insert(
214            ResourceTypeId::of::<T>(),
215            AtomicRefCell::new(Box::new(value)),
216        );
217    }
218
219    /// Removes the type `T` from this store if it exists.
220    ///
221    /// # Returns
222    /// If the type `T` was stored, the inner instance of `T is returned. Otherwise, `None`
223    pub fn remove<T: Resource>(&mut self) -> Option<T> {
224        Some(
225            *self
226                .storage
227                .remove(&ResourceTypeId::of::<T>())?
228                .into_inner()
229                .downcast::<T>()
230                .ok()?,
231        )
232    }
233
234    /// Retrieve an immutable reference to  `T` from the store if it exists. Otherwise, return `None`
235    pub fn get<T: Resource>(&self) -> Option<Fetch<'_, T>> {
236        Some(Fetch {
237            inner: self.storage.get(&ResourceTypeId::of::<T>())?.get(),
238            _marker: Default::default(),
239        })
240    }
241
242    /// Retrieve a mutable reference to  `T` from the store if it exists. Otherwise, return `None`
243    pub fn get_mut<T: Resource>(&self) -> Option<FetchMut<'_, T>> {
244        Some(FetchMut {
245            inner: self.storage.get(&ResourceTypeId::of::<T>())?.get_mut(),
246            _marker: Default::default(),
247        })
248    }
249
250    /// Attempts to retrieve an immutable reference to `T` from the store. If it does not exist,
251    /// the closure `f` is called to construct the object and it is then inserted into the store.
252    pub fn get_or_insert_with<T: Resource, F: FnOnce() -> T>(
253        &mut self,
254        f: F,
255    ) -> Option<Fetch<'_, T>> {
256        self.get_or_insert((f)())
257    }
258
259    /// Attempts to retrieve a mutable reference to `T` from the store. If it does not exist,
260    /// the closure `f` is called to construct the object and it is then inserted into the store.
261    pub fn get_mut_or_insert_with<T: Resource, F: FnOnce() -> T>(
262        &mut self,
263        f: F,
264    ) -> Option<FetchMut<'_, T>> {
265        self.get_mut_or_insert((f)())
266    }
267
268    /// Attempts to retrieve an immutable reference to `T` from the store. If it does not exist,
269    /// the provided value is inserted and then a reference to it is returned.
270    pub fn get_or_insert<T: Resource>(&mut self, value: T) -> Option<Fetch<'_, T>> {
271        Some(Fetch {
272            inner: self
273                .storage
274                .entry(ResourceTypeId::of::<T>())
275                .or_insert_with(|| AtomicRefCell::new(Box::new(value)))
276                .get(),
277            _marker: Default::default(),
278        })
279    }
280
281    /// Attempts to retrieve a mutable reference to `T` from the store. If it does not exist,
282    /// the provided value is inserted and then a reference to it is returned.
283    pub fn get_mut_or_insert<T: Resource>(&mut self, value: T) -> Option<FetchMut<'_, T>> {
284        Some(FetchMut {
285            inner: self
286                .storage
287                .entry(ResourceTypeId::of::<T>())
288                .or_insert_with(|| AtomicRefCell::new(Box::new(value)))
289                .get_mut(),
290            _marker: Default::default(),
291        })
292    }
293
294    /// Attempts to retrieve an immutable reference to `T` from the store. If it does not exist,
295    /// the default constructor for `T` is called.
296    ///
297    /// `T` must implement `Default` for this method.
298    pub fn get_or_default<T: Resource + Default>(&mut self) -> Option<Fetch<'_, T>> {
299        Some(Fetch {
300            inner: self
301                .storage
302                .entry(ResourceTypeId::of::<T>())
303                .or_insert_with(|| AtomicRefCell::new(Box::new(T::default())))
304                .get(),
305            _marker: Default::default(),
306        })
307    }
308
309    /// Attempts to retrieve a mutable reference to `T` from the store. If it does not exist,
310    /// the default constructor for `T` is called.
311    ///
312    /// `T` must implement `Default` for this method.
313    pub fn get_mut_or_default<T: Resource + Default>(&mut self) -> Option<FetchMut<'_, T>> {
314        Some(FetchMut {
315            inner: self
316                .storage
317                .entry(ResourceTypeId::of::<T>())
318                .or_insert_with(|| AtomicRefCell::new(Box::new(T::default())))
319                .get_mut(),
320            _marker: Default::default(),
321        })
322    }
323
324    /// Performs merging of two resource storages, which occurs during a world merge.
325    /// This merge will retain any already-existant resources in the local world, while moving any
326    /// new resources from the source world into this one, consuming the resources.
327    pub fn merge(&mut self, mut other: Resources) {
328        // Merge resources, retaining our local ones but moving in any non-existant ones
329        for resource in other.storage.drain() {
330            self.storage.entry(resource.0).or_insert(resource.1);
331        }
332    }
333}
334
335impl ResourceSet for () {
336    type PreparedResources = ();
337
338    unsafe fn fetch_unchecked(_: &Resources) {}
339}
340
341impl<T: Resource> ResourceSet for Read<T> {
342    type PreparedResources = PreparedRead<T>;
343
344    unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
345        let resource = resources
346            .get::<T>()
347            .unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
348        PreparedRead::new(resource.deref() as *const T)
349    }
350}
351impl<T: Resource> ResourceSet for Write<T> {
352    type PreparedResources = PreparedWrite<T>;
353
354    unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
355        let mut resource = resources
356            .get_mut::<T>()
357            .unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
358        PreparedWrite::new(resource.deref_mut() as *mut T)
359    }
360}
361
362macro_rules! impl_resource_tuple {
363    ( $( $ty: ident ),* ) => {
364        #[allow(unused_parens, non_snake_case)]
365        impl<$( $ty: ResourceSet ),*> ResourceSet for ($( $ty, )*)
366        {
367            type PreparedResources = ($( $ty::PreparedResources, )*);
368
369            unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
370                ($( $ty::fetch_unchecked(resources), )*)
371            }
372        }
373    };
374}
375//($( $ty, )*)
376
377impl_resource_tuple!(A);
378impl_resource_tuple!(A, B);
379impl_resource_tuple!(A, B, C);
380impl_resource_tuple!(A, B, C, D);
381impl_resource_tuple!(A, B, C, D, E);
382impl_resource_tuple!(A, B, C, D, E, F);
383impl_resource_tuple!(A, B, C, D, E, F, G);
384impl_resource_tuple!(A, B, C, D, E, F, G, H);
385impl_resource_tuple!(A, B, C, D, E, F, G, H, I);
386impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J);
387impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K);
388impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
389impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
390impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
391impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
392impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
393impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
394impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
395impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
396impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
397impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
398impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
399impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
400impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
401impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
402impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    #[test]
409    fn simple_read_write_test() {
410        let _ = tracing_subscriber::fmt::try_init();
411
412        struct TestOne {
413            value: String,
414        }
415
416        struct TestTwo {
417            value: String,
418        }
419
420        let mut resources = Resources::default();
421        resources.insert(TestOne {
422            value: "poop".to_string(),
423        });
424
425        resources.insert(TestTwo {
426            value: "balls".to_string(),
427        });
428
429        assert_eq!(resources.get::<TestOne>().unwrap().value, "poop");
430        assert_eq!(resources.get::<TestTwo>().unwrap().value, "balls");
431
432        // test re-ownership
433        let owned = resources.remove::<TestTwo>();
434        assert_eq!(owned.unwrap().value, "balls")
435    }
436}