From 54b47c21781246f0cd63f8d2e7141e3edf261daf Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 19 Jul 2024 16:07:03 -0400 Subject: [PATCH] ecs: implement change tracking for world resources --- lyra-ecs/src/resource.rs | 11 +++- lyra-ecs/src/world.rs | 105 +++++++++++++++++++++++++++++++++------ 2 files changed, 100 insertions(+), 16 deletions(-) diff --git a/lyra-ecs/src/resource.rs b/lyra-ecs/src/resource.rs index 187f2a9..3497b4d 100644 --- a/lyra-ecs/src/resource.rs +++ b/lyra-ecs/src/resource.rs @@ -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 ResourceObject for T { pub struct ResourceData { pub(crate) data: Arc>, type_id: TypeId, + // use a tick tracker which has interior mutability + pub(crate) tick: TickTracker, } impl ResourceData { - pub fn new(data: T) -> Self { + pub fn new(data: T, tick: Tick) -> Self { Self { data: Arc::new(AtomicRefCell::new(data)), type_id: TypeId::of::(), + 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 + } } \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 41e16e9..746a06e 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -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(&mut self, data: T) { - self.resources.insert(TypeId::of::(), ResourceData::new(data)); + let tick = self.tick(); + self.resources.insert(TypeId::of::(), 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(&mut self) { - self.resources.insert(TypeId::of::(), ResourceData::new(T::default())); + let tick = self.tick(); + self.resources.insert(TypeId::of::(), 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(&mut self) -> bool { + let id = TypeId::of::(); + 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(&mut self, f: F) -> AtomicRefMut where F: Fn() -> T + 'static { - self.resources.entry(TypeId::of::()) - .or_insert_with(|| ResourceData::new(f())) - .get_mut() + let tick = self.tick(); + let res = self.resources.entry(TypeId::of::()) + .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(&mut self) -> AtomicRefMut { - self.resources.entry(TypeId::of::()) - .or_insert_with(|| ResourceData::new(T::default())) - .get_mut() + let tick = self.tick(); + let res = self.resources.entry(TypeId::of::()) + .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(&self) -> bool { + let tick = self.current_tick(); + self.resources.get(&TypeId::of::()) + .map(|r| r.changed(tick)) + .unwrap_or(false) + } + + /// Returns the [`Tick`] that the resource was last modified at. + pub fn resource_tick(&self) -> Option { + self.resources.get(&TypeId::of::()) + .map(|r| r.tick.current()) + } + /// Returns boolean indicating if the World contains a resource of type `T`. pub fn has_resource(&self) -> bool { self.resources.contains_key(&TypeId::of::()) @@ -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(&self) -> AtomicRefMut { - self.resources.get(&TypeId::of::()) - .expect(&format!("World is missing resource of type '{}'", std::any::type_name::())) - .get_mut() + self.try_get_resource_mut::() + .unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::())) } /// 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(&self) -> Option> { self.resources.get(&TypeId::of::()) - .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(&self) -> Option { self.resources.get(&TypeId::of::()) .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::()); + + world.spawn(Vec2::new(50.0, 50.0)); + + assert!(!world.has_resource_changed::()); + + let mut counter = world.get_resource_mut::(); + counter.0 += 100; + + assert!(world.has_resource_changed::()); + } } \ No newline at end of file