ecs: implement change tracking for world resources
This commit is contained in:
parent
4449172c2b
commit
54b47c2178
|
@ -2,6 +2,8 @@ use std::{any::{Any, TypeId}, sync::Arc};
|
||||||
|
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||||
|
|
||||||
|
use crate::{Tick, TickTracker};
|
||||||
|
|
||||||
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
|
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
|
||||||
pub trait ResourceObject: Send + Sync + Any {
|
pub trait ResourceObject: Send + Sync + Any {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
@ -23,14 +25,17 @@ impl<T: Send + Sync + Any> ResourceObject for T {
|
||||||
pub struct ResourceData {
|
pub struct ResourceData {
|
||||||
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
|
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
|
// use a tick tracker which has interior mutability
|
||||||
|
pub(crate) tick: TickTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceData {
|
impl ResourceData {
|
||||||
pub fn new<T: ResourceObject>(data: T) -> Self {
|
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data: Arc::new(AtomicRefCell::new(data)),
|
data: Arc::new(AtomicRefCell::new(data)),
|
||||||
type_id: TypeId::of::<T>(),
|
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()))
|
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn changed(&self, tick: Tick) -> bool {
|
||||||
|
self.tick.current() >= tick
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -374,33 +374,65 @@ impl World {
|
||||||
ViewOne::new(self, entity.id, T::Query::new())
|
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) {
|
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) {
|
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
|
/// Get a resource from the world, or insert it into the world with the provided
|
||||||
/// `fn` and return it.
|
/// `fn` and return it.
|
||||||
|
///
|
||||||
|
/// Ticks the world.
|
||||||
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
|
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
|
||||||
where
|
where
|
||||||
F: Fn() -> T + 'static
|
F: Fn() -> T + 'static
|
||||||
{
|
{
|
||||||
self.resources.entry(TypeId::of::<T>())
|
let tick = self.tick();
|
||||||
.or_insert_with(|| ResourceData::new(f()))
|
let res = self.resources.entry(TypeId::of::<T>())
|
||||||
.get_mut()
|
.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.
|
/// 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>
|
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
|
||||||
{
|
{
|
||||||
self.resources.entry(TypeId::of::<T>())
|
let tick = self.tick();
|
||||||
.or_insert_with(|| ResourceData::new(T::default()))
|
let res = self.resources.entry(TypeId::of::<T>())
|
||||||
.get_mut()
|
.or_insert_with(|| ResourceData::new(T::default(), tick));
|
||||||
|
res.tick.tick_to(&tick);
|
||||||
|
res.get_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a resource from the World.
|
/// Gets a resource from the World.
|
||||||
|
@ -413,6 +445,22 @@ impl World {
|
||||||
.get()
|
.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`.
|
/// Returns boolean indicating if the World contains a resource of type `T`.
|
||||||
pub fn has_resource<T: ResourceObject>(&self) -> bool {
|
pub fn has_resource<T: ResourceObject>(&self) -> bool {
|
||||||
self.resources.contains_key(&TypeId::of::<T>())
|
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
|
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
|
||||||
/// a function that returns an option.
|
/// a function that returns an option.
|
||||||
|
///
|
||||||
|
/// Ticks the world.
|
||||||
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||||
self.resources.get(&TypeId::of::<T>())
|
self.try_get_resource_mut::<T>()
|
||||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
.unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||||
.get_mut()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to get a mutable borrow of a resource from the World.
|
/// 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>> {
|
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
|
||||||
self.resources.get(&TypeId::of::<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> {
|
pub fn try_get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
|
||||||
self.resources.get(&TypeId::of::<T>())
|
self.resources.get(&TypeId::of::<T>())
|
||||||
.map(|r| r.clone())
|
.map(|r| r.clone())
|
||||||
|
@ -693,4 +750,22 @@ mod tests {
|
||||||
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
|
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
|
||||||
assert_eq!(*pos, Vec2::new(5.0, 5.0));
|
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>());
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue