bones_ecs/
resources.rs

1//! World resource storage.
2
3use std::{fmt::Debug, marker::PhantomData, sync::Arc};
4
5use once_map::OnceMap;
6
7use crate::prelude::*;
8
9/// An untyped, atomic resource cell.
10pub type AtomicUntypedResource = Arc<UntypedResource>;
11
12/// An untyped resource that may be inserted into [`UntypedResources`].
13///
14/// This is fundamentally a [`Arc<AtomicCell<Option<SchemaBox>>>`] and thus represents
15/// a cell that may or may not contain a resource of it's schema.
16pub struct UntypedResource {
17    cell: AtomicCell<Option<SchemaBox>>,
18    schema: &'static Schema,
19}
20
21impl std::fmt::Debug for UntypedResource {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        f.debug_struct("UntypedResource").finish_non_exhaustive()
24    }
25}
26
27impl UntypedResource {
28    /// Initialize a new, empty [`UntypedResource`].
29    pub fn empty(schema: &'static Schema) -> Self {
30        Self {
31            cell: AtomicCell::new(None),
32            schema,
33        }
34    }
35
36    /// Creates a new [`UntypedResource`] storing the given data.
37    pub fn new(resource: SchemaBox) -> Self {
38        Self {
39            schema: resource.schema(),
40            cell: AtomicCell::new(Some(resource)),
41        }
42    }
43
44    /// Create a new [`UntypedResource`] for the given schema, initially populated with the default
45    /// value for the schema.
46    pub fn from_default(schema: &'static Schema) -> Self {
47        Self {
48            cell: AtomicCell::new(Some(SchemaBox::default(schema))),
49            schema,
50        }
51    }
52
53    /// Clone the inner data, creating a new copy instead of returning another handle the the same
54    /// data, as the normal `clone()` implementation does.
55    pub fn clone_data(&self) -> Option<SchemaBox> {
56        (*self.cell.borrow()).clone()
57    }
58
59    /// Insert resource data into the cell, returning the previous data.
60    /// # Errors
61    /// Errors if the schema of the data does not match that of this cell.
62    pub fn insert(&self, data: SchemaBox) -> Result<Option<SchemaBox>, SchemaMismatchError> {
63        self.schema.ensure_match(data.schema())?;
64        let mut data = Some(data);
65        std::mem::swap(&mut data, &mut *self.cell.borrow_mut());
66        Ok(data)
67    }
68
69    /// Remove the resource data, returning what was stored in it.
70    pub fn remove(&self) -> Option<SchemaBox> {
71        let mut data = None;
72        std::mem::swap(&mut data, &mut *self.cell.borrow_mut());
73        data
74    }
75
76    /// Borrow the resource.
77    #[track_caller]
78    pub fn borrow(&self) -> Ref<'_, Option<SchemaBox>> {
79        self.cell.borrow()
80    }
81
82    /// Mutably borrow the resource.
83    #[track_caller]
84    pub fn borrow_mut(&self) -> RefMut<'_, Option<SchemaBox>> {
85        self.cell.borrow_mut()
86    }
87
88    /// Get the schema of the resource.
89    pub fn schema(&self) -> &'static Schema {
90        self.schema
91    }
92}
93
94/// Storage for un-typed resources.
95///
96/// This is the backing data store used by [`Resources`].
97///
98/// Unless you are intending to do modding or otherwise need raw pointers to your resource data, you
99/// should use [`Resources`] instead.
100#[derive(Default)]
101pub struct UntypedResources {
102    resources: OnceMap<SchemaId, AtomicUntypedResource>,
103    shared_resources: OnceMap<SchemaId, Box<()>>,
104}
105
106impl Clone for UntypedResources {
107    fn clone(&self) -> Self {
108        let binding = self.resources.read_only_view();
109        let resources = binding.iter().map(|(_, v)| (v.schema, v));
110
111        let new_resources = OnceMap::default();
112        let new_shared_resources = OnceMap::default();
113        for (schema, resource_cell) in resources {
114            let is_shared = self.shared_resources.contains_key(&schema.id());
115
116            if !is_shared {
117                let resource = resource_cell.clone_data();
118                new_resources.map_insert(
119                    schema.id(),
120                    |_| Arc::new(UntypedResource::empty(schema)),
121                    |_, cell| {
122                        if let Some(resource) = resource {
123                            cell.insert(resource).unwrap();
124                        }
125                    },
126                );
127            } else {
128                new_shared_resources.insert(schema.id(), |_| Box::new(()));
129                new_resources.insert(schema.id(), |_| resource_cell.clone());
130            }
131        }
132        Self {
133            resources: new_resources,
134            shared_resources: new_shared_resources,
135        }
136    }
137}
138
139/// Error thrown when a resource cell cannot be inserted because it already exists.
140#[derive(Debug, Clone, Copy)]
141pub struct CellAlreadyPresentError;
142impl std::fmt::Display for CellAlreadyPresentError {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        f.write_str("Resource cell already present")
145    }
146}
147impl std::error::Error for CellAlreadyPresentError {}
148
149impl UntypedResources {
150    /// Create an empty [`UntypedResources`].
151    pub fn new() -> Self {
152        Self::default()
153    }
154
155    /// Check whether or not a cell for the given resource has been initialized yet.
156    pub fn contains_cell(&self, id: SchemaId) -> bool {
157        self.resources.contains_key(&id)
158    }
159
160    /// Check whether or not the resource with the given ID is present.
161    pub fn contains(&self, id: SchemaId) -> bool {
162        self.resources
163            .map_get(&id, |_, cell| cell.borrow().is_some())
164            .unwrap_or_default()
165    }
166
167    /// This is an advanced use-case function that allows you to insert a resource cell directly.
168    ///
169    /// Normally this is completely unnecessary, because cells are automatically inserted lazily as
170    /// requested.
171    ///
172    /// Inserting this manually is used internally for shared resources, by inserting the same
173    /// cell into multiple worlds.
174    ///
175    /// # Errors
176    /// This will error if there is already a cell for the resource present. You cannot add a new
177    /// cell once one has already been inserted.
178    pub fn insert_cell(&self, cell: AtomicUntypedResource) -> Result<(), CellAlreadyPresentError> {
179        let schema = cell.schema;
180        if self.resources.contains_key(&schema.id()) {
181            Err(CellAlreadyPresentError)
182        } else {
183            self.resources.insert(schema.id(), |_| cell);
184            self.shared_resources.insert(schema.id(), |_| Box::new(()));
185            Ok(())
186        }
187    }
188
189    /// Borrow the resource for the given schema.
190    pub fn get(&self, schema: &'static Schema) -> &UntypedResource {
191        self.resources
192            .insert(schema.id(), |_| Arc::new(UntypedResource::empty(schema)))
193    }
194
195    /// Get a cell for the resource with the given schema.
196    pub fn get_cell(&self, schema: &'static Schema) -> AtomicUntypedResource {
197        self.resources.map_insert(
198            schema.id(),
199            |_| Arc::new(UntypedResource::empty(schema)),
200            |_, cell| cell.clone(),
201        )
202    }
203
204    /// Removes all resourcse that are not shared resources.
205    pub fn clear_owned_resources(&mut self) {
206        for (schema_id, resource_cell) in self.resources.iter_mut() {
207            let is_shared = self.shared_resources.contains_key(schema_id);
208            if !is_shared {
209                resource_cell.remove();
210            }
211        }
212    }
213}
214
215/// A collection of resources.
216///
217/// [`Resources`] is essentially a type-map
218#[derive(Clone, Default)]
219pub struct Resources {
220    untyped: UntypedResources,
221}
222
223impl Resources {
224    /// Create an empty [`Resources`].
225    pub fn new() -> Self {
226        Self::default()
227    }
228
229    /// Insert a resource.
230    pub fn insert<T: HasSchema>(&self, resource: T) -> Option<T> {
231        self.untyped
232            .get(T::schema())
233            .insert(SchemaBox::new(resource))
234            .unwrap()
235            .map(|x| x.cast_into())
236    }
237
238    /// Check whether or not a resource is in the store.
239    ///
240    /// See [get()][Self::get]
241    pub fn contains<T: HasSchema>(&self) -> bool {
242        self.untyped.contains(T::schema().id())
243    }
244
245    /// Remove a resource from the store, if it is present.
246    pub fn remove<T: HasSchema>(&self) -> Option<T> {
247        self.untyped
248            .get(T::schema())
249            .remove()
250            // SOUND: we know the type matches because we retrieve it by it's schema.
251            .map(|x| unsafe { x.cast_into_unchecked() })
252    }
253
254    /// Borrow a resource.
255    #[track_caller]
256    pub fn get<T: HasSchema>(&self) -> Option<Ref<'_, T>> {
257        let b = self.untyped.get(T::schema()).borrow();
258        if b.is_some() {
259            Some(Ref::map(b, |b| unsafe {
260                b.as_ref().unwrap().as_ref().cast_into_unchecked()
261            }))
262        } else {
263            None
264        }
265    }
266
267    /// Borrow a resource.
268    #[track_caller]
269    pub fn get_mut<T: HasSchema>(&self) -> Option<RefMut<'_, T>> {
270        let b = self.untyped.get(T::schema()).borrow_mut();
271        if b.is_some() {
272            Some(RefMut::map(b, |b| unsafe {
273                b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
274            }))
275        } else {
276            None
277        }
278    }
279
280    /// Gets a clone of the resource cell for the resource of the given type.
281    pub fn get_cell<T: HasSchema>(&self) -> AtomicResource<T> {
282        let untyped = self.untyped.get_cell(T::schema()).clone();
283        AtomicResource {
284            untyped,
285            _phantom: PhantomData,
286        }
287    }
288
289    /// Borrow the underlying [`UntypedResources`] store.
290    pub fn untyped(&self) -> &UntypedResources {
291        &self.untyped
292    }
293
294    /// Consume [`Resources`] and extract the underlying [`UntypedResources`].
295    pub fn into_untyped(self) -> UntypedResources {
296        self.untyped
297    }
298
299    /// Removes all resources that are not shared. Shared resources are preserved.
300    pub fn clear_owned_resources(&mut self) {
301        self.untyped.clear_owned_resources();
302    }
303}
304
305/// A handle to a resource from a [`Resources`] collection.
306///
307/// This is not the resource itself, but a cheaply clonable handle to it.
308///
309/// To access the resource you must borrow it with either [`borrow()`][Self::borrow] or
310/// [`borrow_mut()`][Self::borrow_mut].
311#[derive(Clone)]
312pub struct AtomicResource<T: HasSchema> {
313    untyped: AtomicUntypedResource,
314    _phantom: PhantomData<T>,
315}
316impl<T: HasSchema + Debug> Debug for AtomicResource<T> {
317    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318        f.write_str("AtomicResource(")?;
319        self.untyped
320            .cell
321            .borrow()
322            .as_ref()
323            .map(|x| x.cast_ref::<T>())
324            .fmt(f)?;
325        f.write_str(")")?;
326        Ok(())
327    }
328}
329
330impl<T: HasSchema + Default> Default for AtomicResource<T> {
331    fn default() -> Self {
332        Self {
333            untyped: Arc::new(UntypedResource::new(SchemaBox::new(T::default()))),
334            _phantom: Default::default(),
335        }
336    }
337}
338
339impl<T: HasSchema> AtomicResource<T> {
340    /// Create a new, empty resource cell.
341    pub fn empty() -> Self {
342        Self {
343            untyped: Arc::new(UntypedResource::empty(T::schema())),
344            _phantom: PhantomData,
345        }
346    }
347
348    /// Create a new resource cell with the given data.
349    pub fn new(data: T) -> Self {
350        AtomicResource {
351            untyped: Arc::new(UntypedResource::new(SchemaBox::new(data))),
352            _phantom: PhantomData,
353        }
354    }
355
356    /// Create from an [`UntypedResource`].
357    pub fn from_untyped(untyped: AtomicUntypedResource) -> Result<Self, SchemaMismatchError> {
358        T::schema().ensure_match(untyped.schema)?;
359        Ok(AtomicResource {
360            untyped,
361            _phantom: PhantomData,
362        })
363    }
364
365    /// Remove the resource from the cell, leaving the cell empty.
366    pub fn remove(&self) -> Option<T> {
367        self.untyped
368            .remove()
369            // SOUND: The untyped data of an atomic resource must always be `T`.
370            .map(|x| unsafe { x.cast_into_unchecked() })
371    }
372
373    /// Lock the resource for reading.
374    ///
375    /// This returns a read guard, very similar to an [`RwLock`][std::sync::RwLock].
376    pub fn borrow(&self) -> Option<Ref<'_, T>> {
377        let borrow = self.untyped.borrow();
378        if borrow.is_some() {
379            Some(Ref::map(borrow, |r| unsafe {
380                r.as_ref().unwrap().as_ref().cast_into_unchecked()
381            }))
382        } else {
383            None
384        }
385    }
386
387    /// Lock the resource for read-writing.
388    ///
389    /// This returns a write guard, very similar to an [`RwLock`][std::sync::RwLock].
390    pub fn borrow_mut(&self) -> Option<RefMut<'_, T>> {
391        let borrow = self.untyped.borrow_mut();
392        if borrow.is_some() {
393            Some(RefMut::map(borrow, |r| unsafe {
394                r.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
395            }))
396        } else {
397            None
398        }
399    }
400
401    /// Convert into an untyped resource.
402    pub fn into_untyped(self) -> AtomicUntypedResource {
403        self.untyped
404    }
405}
406
407impl<T: HasSchema + FromWorld> AtomicResource<T> {
408    /// Initialize the resource using it's [`FromWorld`] implementation, if it is not present.
409    pub fn init(&self, world: &World) {
410        let mut borrow = self.untyped.borrow_mut();
411        if unlikely(borrow.is_none()) {
412            *borrow = Some(SchemaBox::new(T::from_world(world)))
413        }
414    }
415
416    /// Borrow the resource, initializing it if it doesn't exist.
417    #[track_caller]
418    pub fn init_borrow(&self, world: &World) -> Ref<'_, T> {
419        let map_borrow = |borrow| {
420            // SOUND: we know the schema matches.
421            Ref::map(borrow, |b: &Option<SchemaBox>| unsafe {
422                b.as_ref().unwrap().as_ref().cast_into_unchecked()
423            })
424        };
425        let borrow = self.untyped.borrow();
426        if unlikely(borrow.is_none()) {
427            drop(borrow);
428            {
429                let mut borrow_mut = self.untyped.borrow_mut();
430                *borrow_mut = Some(SchemaBox::new(T::from_world(world)));
431            }
432
433            map_borrow(self.untyped.borrow())
434        } else {
435            map_borrow(borrow)
436        }
437    }
438
439    /// Borrow the resource, initializing it if it doesn't exist.
440    #[track_caller]
441    pub fn init_borrow_mut(&self, world: &World) -> RefMut<'_, T> {
442        let mut borrow = self.untyped.borrow_mut();
443        if unlikely(borrow.is_none()) {
444            *borrow = Some(SchemaBox::new(T::from_world(world)));
445        }
446        RefMut::map(borrow, |b| unsafe {
447            b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
448        })
449    }
450}
451
452/// Utility container for storing set of [`UntypedResource`].
453/// Cloning
454#[derive(Default)]
455pub struct UntypedResourceSet {
456    resources: Vec<UntypedResource>,
457}
458
459impl Clone for UntypedResourceSet {
460    fn clone(&self) -> Self {
461        Self {
462            resources: self
463                .resources
464                .iter()
465                .map(|res| {
466                    if let Some(clone) = res.clone_data() {
467                        UntypedResource::new(clone)
468                    } else {
469                        UntypedResource::empty(res.schema())
470                    }
471                })
472                .collect(),
473        }
474    }
475}
476
477impl UntypedResourceSet {
478    /// Insert a startup resource. On stage / session startup (first step), will be inserted into [`World`].
479    ///
480    /// If already exists, will be overwritten.
481    pub fn insert_resource<T: HasSchema>(&mut self, resource: T) {
482        // Update an existing resource of the same type.
483        for r in &mut self.resources {
484            if r.schema() == T::schema() {
485                let mut borrow = r.borrow_mut();
486
487                if let Some(b) = borrow.as_mut() {
488                    *b.cast_mut() = resource;
489                } else {
490                    *borrow = Some(SchemaBox::new(resource))
491                }
492                return;
493            }
494        }
495
496        // Or insert a new resource if we couldn't find one
497        self.resources
498            .push(UntypedResource::new(SchemaBox::new(resource)))
499    }
500
501    /// Init resource with default, and return mutable ref for modification.
502    /// If already exists, returns mutable ref to existing resource.
503    pub fn init_resource<T: HasSchema + Default>(&mut self) -> RefMut<'_, T> {
504        if !self.resources.iter().any(|x| x.schema() == T::schema()) {
505            self.insert_resource(T::default());
506        }
507        self.resource_mut::<T>().unwrap()
508    }
509
510    /// Get mutable reference to startup resource if found.
511    #[track_caller]
512    pub fn resource_mut<T: HasSchema>(&self) -> Option<RefMut<'_, T>> {
513        let res = self.resources.iter().find(|x| x.schema() == T::schema())?;
514        let borrow = res.borrow_mut();
515
516        if borrow.is_some() {
517            // SOUND: We know the type matches T
518            Some(RefMut::map(borrow, |b| unsafe {
519                b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
520            }))
521        } else {
522            None
523        }
524    }
525
526    /// Insert an [`UntypedResource`] with empty cell for [`Schema`] of `T`.
527    /// If resource already exists for this schema, overwrite it with empty.
528    pub fn insert_empty<T: HasSchema>(&mut self) {
529        for r in &mut self.resources {
530            if r.schema() == T::schema() {
531                let mut borrow = r.borrow_mut();
532                *borrow = None;
533                return;
534            }
535        }
536
537        // Or insert a new empty resource if we couldn't find one
538        self.resources.push(UntypedResource::empty(T::schema()));
539    }
540
541    /// Get immutable ref to Vec of resources
542    pub fn resources(&self) -> &Vec<UntypedResource> {
543        &self.resources
544    }
545
546    /// Insert resources in world. If resource already exists, overwrites it.
547    ///
548    /// If `remove_empty` is set, if resource cell is empty, it will remove the
549    /// resource from cell on world.
550    pub fn insert_on_world(&self, world: &mut World, remove_empty: bool) {
551        for resource in self.resources.iter() {
552            let resource_cell = world.resources.untyped().get_cell(resource.schema());
553
554            // Deep copy resource and insert into world.
555            if let Some(resource_copy) = resource.clone_data() {
556                resource_cell
557                    .insert(resource_copy)
558                    .expect("Schema mismatch error");
559            } else if remove_empty {
560                // Remove the resource on world
561                resource_cell.remove();
562            }
563        }
564    }
565}
566
567#[cfg(test)]
568mod test {
569    use std::sync::Arc;
570
571    use crate::prelude::*;
572    #[derive(HasSchema, Clone, Debug, Default)]
573    #[repr(C)]
574    struct A(String);
575
576    #[derive(HasSchema, Clone, Debug, Default)]
577    #[repr(C)]
578    struct B(u32);
579
580    #[test]
581    fn sanity_check() {
582        let r1 = Resources::new();
583
584        r1.insert(A(String::from("hi")));
585        let r1a = r1.get_cell::<A>();
586        assert_eq!(r1a.borrow().unwrap().0, "hi");
587
588        let r2 = r1.clone();
589
590        r1.insert(A(String::from("bye")));
591        r1.insert(A(String::from("world")));
592        assert_eq!(r1a.borrow().unwrap().0, "world");
593
594        let r2a = r2.get_cell::<A>();
595        assert_eq!(r2a.borrow().unwrap().0, "hi");
596
597        r1.insert(B(1));
598        let r1b = r1.get_cell::<B>();
599        assert_eq!(r1b.borrow().unwrap().0, 1);
600        r1.insert(B(2));
601        assert_eq!(r1b.borrow().unwrap().0, 2);
602        assert_eq!(r1a.borrow().unwrap().0, "world");
603    }
604
605    #[test]
606    fn resources_clear_owned_values() {
607        let mut r = Resources::new();
608
609        // insert A as non-shared resource
610        r.insert(A(String::from("foo")));
611
612        // Insert B as shared resource
613        r.untyped
614            .insert_cell(Arc::new(UntypedResource::new(SchemaBox::new(B(1)))))
615            .unwrap();
616
617        // Should only clear non-shared resources
618        r.clear_owned_resources();
619
620        // Verify non-shared was removed
621        let res_a = r.get::<A>();
622        assert!(res_a.is_none());
623
624        // Verify shared is still present
625        let res_b = r.get::<B>().unwrap();
626        assert_eq!(res_b.0, 1);
627    }
628}