ecs: implement change tracking for world resources

This commit is contained in:
SeanOMik 2024-07-19 16:07:03 -04:00
parent 4449172c2b
commit 54b47c2178
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
2 changed files with 100 additions and 16 deletions

View File

@ -2,6 +2,8 @@ use std::{any::{Any, TypeId}, sync::Arc};
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use crate::{Tick, TickTracker};
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
pub trait ResourceObject: Send + Sync + Any {
fn as_any(&self) -> &dyn Any;
@ -23,14 +25,17 @@ impl<T: Send + Sync + Any> ResourceObject for T {
pub struct ResourceData {
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
type_id: TypeId,
// use a tick tracker which has interior mutability
pub(crate) tick: TickTracker,
}
impl ResourceData {
pub fn new<T: ResourceObject>(data: T) -> Self {
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
Self {
data: Arc::new(AtomicRefCell::new(data)),
type_id: TypeId::of::<T>(),
tick: TickTracker::from(*tick),
}
}
@ -80,4 +85,8 @@ impl ResourceData {
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
.ok()
}
pub fn changed(&self, tick: Tick) -> bool {
self.tick.current() >= tick
}
}

View File

@ -374,33 +374,65 @@ impl World {
ViewOne::new(self, entity.id, T::Query::new())
}
//pub fn view_one(&self, entity: EntityId) ->
/// Add a resource to the world.
///
/// Ticks the world.
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data));
let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
}
/// Add the default value of a resource.
///
/// Ticks the world.
///
/// > Note: This will replace existing values.
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default()));
let tick = self.tick();
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), tick));
}
/// Add the default value of a resource if it does not already exist.
///
/// Returns a boolean indicating if the resource was added. Ticks the world if the resource
/// was added.
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
let id = TypeId::of::<T>();
if !self.resources.contains_key(&id) {
let tick = self.tick();
self.resources.insert(id, ResourceData::new(T::default(), tick));
true
} else {
false
}
}
/// Get a resource from the world, or insert it into the world with the provided
/// `fn` and return it.
///
/// Ticks the world.
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
where
F: Fn() -> T + 'static
{
self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(f()))
.get_mut()
let tick = self.tick();
let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(f(), tick));
res.tick.tick_to(&tick);
res.get_mut()
}
/// Get a resource from the world, or insert it into the world as its default.
///
/// Ticks the world.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
{
self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default()))
.get_mut()
let tick = self.tick();
let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default(), tick));
res.tick.tick_to(&tick);
res.get_mut()
}
/// Gets a resource from the World.
@ -413,6 +445,22 @@ impl World {
.get()
}
/// Returns a boolean indicating if the resource changed.
///
/// This will return false if the resource doesn't exist.
pub fn has_resource_changed<T: ResourceObject>(&self) -> bool {
let tick = self.current_tick();
self.resources.get(&TypeId::of::<T>())
.map(|r| r.changed(tick))
.unwrap_or(false)
}
/// Returns the [`Tick`] that the resource was last modified at.
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.tick.current())
}
/// Returns boolean indicating if the World contains a resource of type `T`.
pub fn has_resource<T: ResourceObject>(&self) -> bool {
self.resources.contains_key(&TypeId::of::<T>())
@ -430,20 +478,29 @@ impl World {
///
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
/// a function that returns an option.
///
/// Ticks the world.
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
.get_mut()
self.try_get_resource_mut::<T>()
.unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::<T>()))
}
/// Attempts to get a mutable borrow of a resource from the World.
///
/// Returns `None` if the resource was not found.
/// Returns `None` if the resource was not found. Ticks the world if the resource was found.
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.resources.get(&TypeId::of::<T>())
.and_then(|r| r.try_get_mut())
.and_then(|r| {
// now that the resource was retrieved, tick the world and the resource
let new_tick = self.tick();
r.tick.tick_to(&new_tick);
r.try_get_mut()
})
}
/// Get the corresponding [`ResourceData`].
///
/// > Note: If you borrow the resource mutably, the world and the resource will not be ticked.
pub fn try_get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.clone())
@ -693,4 +750,22 @@ mod tests {
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
assert_eq!(*pos, Vec2::new(5.0, 5.0));
}
/// Tests resource change checks
#[test]
fn resource_changed() {
let mut world = World::new();
world.add_resource(SimpleCounter(50));
assert!(world.has_resource_changed::<SimpleCounter>());
world.spawn(Vec2::new(50.0, 50.0));
assert!(!world.has_resource_changed::<SimpleCounter>());
let mut counter = world.get_resource_mut::<SimpleCounter>();
counter.0 += 100;
assert!(world.has_resource_changed::<SimpleCounter>());
}
}