ecs: don't automatically tick the world, use Res and ResMut anywhere ecs resources are requested to track changes better

now the user must manually tick the world. The engine will do this before every update
This commit is contained in:
SeanOMik 2024-09-27 21:03:27 -04:00
parent 9b1cc8c364
commit f5aca87ede
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
10 changed files with 217 additions and 151 deletions

View File

@ -2,7 +2,7 @@ use std::marker::PhantomData;
use atomic_refcell::{AtomicRef, AtomicRefMut}; use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{World, resource::ResourceObject}; use crate::{resource::ResourceObject, Tick, TrackedResource, World};
use super::{Query, Fetch, AsQuery}; use super::{Query, Fetch, AsQuery};
@ -22,12 +22,18 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResource<'a, T> {
} }
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool { fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
true let w = self.world.unwrap();
w.has_resource::<T>()
} }
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap(); let w = self.world.unwrap();
Res(w.get_resource::<T>()) Res {
// this unwrap is safe since `can_visit_item` ensures the resource exists
inner: w.get_tracked_resource::<T>().unwrap(),
world_tick: w.current_tick(),
_marker: PhantomData::<T>,
}
} }
} }
@ -81,13 +87,37 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
} }
/// A struct used for querying resources from the World. /// A struct used for querying resources from the World.
pub struct Res<'a, T: ResourceObject>(pub(crate) AtomicRef<'a, T>); pub struct Res<'a, T: ResourceObject> {
pub(crate) inner: AtomicRef<'a, TrackedResource<dyn ResourceObject>>,
pub(crate) world_tick: Tick,
pub(crate) _marker: PhantomData<T>,
}
impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> { impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.0.deref() self.inner.res.as_any().downcast_ref::<T>().unwrap()
}
}
impl<'a, T: ResourceObject> Res<'a, T> {
/// Get the inner [`AtomicRef`].
///
/// This inner type does not have change tracking. If you `DerefMut` it, the change will not be tracked!
#[inline]
pub fn get_inner(self) -> atomic_refcell::AtomicRef<'a, T> {
atomic_refcell::AtomicRef::map(self.inner, |r| r.res.as_any().downcast_ref().unwrap())
}
/// Returns a boolean indicating if the resource changed.
pub fn changed(&self) -> bool {
self.inner.tick >= self.world_tick
}
/// The tick that this resource was last modified at
pub fn tick(&self) -> Tick {
self.inner.tick
} }
} }
@ -111,12 +141,18 @@ impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResourceMut<'a, T> {
} }
fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool { fn can_visit_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> bool {
true let w = self.world.unwrap();
w.has_resource::<T>()
} }
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item { unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
let w = self.world.unwrap(); let w = self.world.unwrap();
ResMut(w.get_resource_mut::<T>()) ResMut {
// this is safe since `can_visit_item` ensures that the resource exists.
inner: w.get_tracked_resource_mut::<T>().unwrap(),
world_tick: w.current_tick(),
_marker: PhantomData::<T>,
}
} }
} }
@ -169,20 +205,51 @@ impl<R: ResourceObject> AsQuery for QueryResourceMut<R> {
} }
/// A struct used for querying resources from the World. /// A struct used for querying resources from the World.
pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>); //pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>);
pub struct ResMut<'a, T: ResourceObject> {
pub(crate) inner: AtomicRefMut<'a, TrackedResource<dyn ResourceObject>>,
pub(crate) world_tick: Tick,
pub(crate) _marker: PhantomData<T>,
}
impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> { impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
self.0.deref() self.inner.res.as_any().downcast_ref::<T>().unwrap()
} }
} }
impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> { impl<'a, T: ResourceObject> std::ops::DerefMut for ResMut<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
self.0.deref_mut() self.mark_changed();
self.inner.res.as_any_mut().downcast_mut::<T>().unwrap()
}
}
impl<'a, T: ResourceObject> ResMut<'a, T> {
/// Get the inner [`AtomicRefMut`].
///
/// This inner type does not have change tracking. If you `DerefMut` it, the change will not be tracked!
pub fn get_inner(self) -> atomic_refcell::AtomicRefMut<'a, T> {
atomic_refcell::AtomicRefMut::map(self.inner, |r| r.res.as_any_mut().downcast_mut().unwrap())
}
pub fn changed(&self) -> bool {
self.inner.tick > self.world_tick
}
/// The tick that this resource was last modified at
pub fn tick(&self) -> Tick {
self.inner.tick
}
/// Manually mark the resource as changed.
///
/// This is useful if you have a resource with interior mutability and you want to
/// mark this resource as changed.
pub fn mark_changed(&mut self) {
self.inner.tick = self.world_tick
} }
} }

View File

@ -58,7 +58,6 @@ where
ViewIter { ViewIter {
world: self.world, world: self.world,
tick: self.world.current_tick(), tick: self.world.current_tick(),
has_ticked: false,
query: self.query, query: self.query,
filter: self.filter, filter: self.filter,
fetcher: None, fetcher: None,
@ -73,7 +72,6 @@ where
pub struct ViewIter<'a, Q: Query, F: Filter> { pub struct ViewIter<'a, Q: Query, F: Filter> {
world: &'a World, world: &'a World,
tick: Tick, tick: Tick,
has_ticked: bool,
query: Q, query: Q,
filter: F, filter: F,
fetcher: Option<Q::Fetch<'a>>, fetcher: Option<Q::Fetch<'a>>,
@ -112,13 +110,6 @@ where
let entity_index = ArchetypeEntityId(entity_index); let entity_index = ArchetypeEntityId(entity_index);
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) { if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
// only tick the world if the filter has fetched, and when the world hasn't
// been ticked yet.
if Q::MUTATES && !self.has_ticked {
self.has_ticked = true;
self.tick = self.world.tick();
}
let i = unsafe { fetcher.get_item(entity_index) }; let i = unsafe { fetcher.get_item(entity_index) };
return Some(i); return Some(i);
} }
@ -174,9 +165,6 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
if self.query.can_visit_archetype(arch) { if self.query.can_visit_archetype(arch) {
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) }; let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
if fetch.can_visit_item(record.index) { if fetch.can_visit_item(record.index) {
// only tick the world when something is actually fetched.
self.world.tick();
return Some(unsafe { fetch.get_item(record.index) }); return Some(unsafe { fetch.get_item(record.index) });
} }
} }

View File

@ -20,22 +20,24 @@ impl<T: Send + Sync + Any> ResourceObject for T {
} }
} }
pub struct TrackedResource<T: ?Sized> {
pub tick: Tick,
pub res: T,
}
/// A type erased storage for a Resource. /// A type erased storage for a Resource.
#[derive(Clone)] #[derive(Clone)]
pub struct ResourceData { pub struct ResourceData {
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>, pub(crate) data: Arc<AtomicRefCell<TrackedResource<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, tick: Tick) -> Self { pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
Self { Self {
data: Arc::new(AtomicRefCell::new(data)), data: Arc::new(AtomicRefCell::new(TrackedResource { tick, res: data })),
type_id: TypeId::of::<T>(), type_id: TypeId::of::<T>(),
tick: TickTracker::from(*tick),
} }
} }
@ -51,7 +53,7 @@ impl ResourceData {
/// * If the data is already borrowed mutably, this will panic. /// * If the data is already borrowed mutably, this will panic.
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> { pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> {
AtomicRef::map(self.data.borrow(), |a| a.as_any().downcast_ref().unwrap()) AtomicRef::map(self.data.borrow(), |a| a.res.as_any().downcast_ref().unwrap())
} }
/// Mutably borrow the data inside of the resource. /// Mutably borrow the data inside of the resource.
@ -61,7 +63,7 @@ impl ResourceData {
/// * If the data is already borrowed mutably, this will panic. /// * If the data is already borrowed mutably, this will panic.
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> { pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
AtomicRefMut::map(self.data.borrow_mut(), |a| a.as_any_mut().downcast_mut().unwrap()) AtomicRefMut::map(self.data.borrow_mut(), |a| a.res.as_any_mut().downcast_mut().unwrap())
} }
/// Borrow the data inside of the resource. /// Borrow the data inside of the resource.
@ -71,7 +73,7 @@ impl ResourceData {
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> { pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.data.try_borrow() self.data.try_borrow()
.map(|r| AtomicRef::map(r, |a| a.as_any().downcast_ref().unwrap())) .map(|r| AtomicRef::map(r, |a| a.res.as_any().downcast_ref().unwrap()))
.ok() .ok()
} }
@ -82,11 +84,11 @@ impl ResourceData {
/// * If the type of `T` is not the same as the resource type. /// * If the type of `T` is not the same as the resource type.
pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> { pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
self.data.try_borrow_mut() self.data.try_borrow_mut()
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap())) .map(|r| AtomicRefMut::map(r, |a| a.res.as_any_mut().downcast_mut().unwrap()))
.ok() .ok()
} }
pub fn changed(&self, tick: Tick) -> bool { pub fn changed(&self, tick: Tick) -> bool {
self.tick.current() >= tick self.data.borrow().tick >= tick
} }
} }

View File

@ -20,6 +20,7 @@ pub trait FnArgFetcher {
Access::Read Access::Read
} }
// TODO: check if the fetcher can fetch before getting.
/// Get the arg from the world /// Get the arg from the world
/// ///
/// # Safety /// # Safety
@ -214,7 +215,9 @@ impl<R: ResourceObject> FnArgFetcher for Res<'_, R> {
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref(); let world = world.as_ref();
Res(world.get_resource::<R>()) world.get_resource::<R>()
// TODO: check if the resource exists before attempting to fetch
.unwrap_or_else(|| panic!("world is missing resource: {}", std::any::type_name::<R>()))
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { } fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -228,7 +231,9 @@ impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> { unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref(); let world = world.as_ref();
ResMut(world.get_resource_mut::<R>()) world.get_resource_mut::<R>()
// TODO: check if the resource exists before attempting to fetch
.unwrap_or_else(|| panic!("world is missing resource: {}", std::any::type_name::<R>()))
} }
fn apply_deferred(_: Self::State, _: NonNull<World>) { } fn apply_deferred(_: Self::State, _: NonNull<World>) { }
@ -307,7 +312,8 @@ mod tests {
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> { let test_system = |world: &World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>(); let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10; counter.0 += 10;
Ok(()) Ok(())
@ -316,7 +322,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); test_system.into_system().execute(NonNull::from(&world)).unwrap();
let test_system = |world: &mut World| -> anyhow::Result<()> { let test_system = |world: &mut World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>(); let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10; counter.0 += 10;
Ok(()) Ok(())
@ -324,7 +331,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>(); let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 20); assert_eq!(counter.0, 20);
} }
@ -336,7 +344,8 @@ mod tests {
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |world: &World| -> anyhow::Result<()> { let test_system = |world: &World| -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>(); let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10; counter.0 += 10;
Ok(()) Ok(())
@ -346,7 +355,8 @@ mod tests {
#[allow(dead_code)] #[allow(dead_code)]
fn test_system(world: &mut World) -> anyhow::Result<()> { fn test_system(world: &mut World) -> anyhow::Result<()> {
let mut counter = world.get_resource_mut::<SomeCounter>(); let mut counter = world.get_resource_mut::<SomeCounter>()
.expect("Counter resource is missing");
counter.0 += 10; counter.0 += 10;
Ok(()) Ok(())
@ -354,7 +364,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>(); let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 20); assert_eq!(counter.0, 20);
} }
@ -366,16 +377,15 @@ mod tests {
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> { let test_system = |mut counter: ResMut<SomeCounter>| -> anyhow::Result<()> {
// .0 is twice here since ResMut's tuple field is pub(crate). counter.0 += 10;
// Users wont need to do this
counter.0.0 += 10;
Ok(()) Ok(())
}; };
test_system.into_system().execute(NonNull::from(&world)).unwrap(); test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>(); let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 10); assert_eq!(counter.0, 10);
} }
@ -389,9 +399,7 @@ mod tests {
let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> { let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
for v2 in view.into_iter() { for v2 in view.into_iter() {
println!("Got v2 at '{:?}'", v2); println!("Got v2 at '{:?}'", v2);
// .0 is twice here since ResMut's tuple field is pub(crate). counter.0 += 1;
// Users wont need to do this
counter.0.0 += 1;
} }
Ok(()) Ok(())
@ -399,7 +407,8 @@ mod tests {
test_system.into_system().execute(NonNull::from(&world)).unwrap(); test_system.into_system().execute(NonNull::from(&world)).unwrap();
let counter = world.get_resource::<SomeCounter>(); let counter = world.get_resource::<SomeCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 2); assert_eq!(counter.0, 2);
} }
} }

View File

@ -103,7 +103,7 @@ impl GraphExecutor {
} }
let world = unsafe { world_ptr.as_mut() }; let world = unsafe { world_ptr.as_mut() };
if let Some(mut queue) = world.try_get_resource_mut::<CommandQueue>() { if let Some(mut queue) = world.get_resource_mut::<CommandQueue>() {
// Safety: Commands only borrows world.entities when adding commands // Safety: Commands only borrows world.entities when adding commands
let world = unsafe { world_ptr.as_mut() }; let world = unsafe { world_ptr.as_mut() };
let mut commands = Commands::new(&mut queue, world); let mut commands = Commands::new(&mut queue, world);
@ -189,7 +189,8 @@ mod tests {
exec.execute(NonNull::from(&world), true).unwrap(); exec.execute(NonNull::from(&world), true).unwrap();
println!("Executed systems"); println!("Executed systems");
let order = world.get_resource::<Vec<String>>(); let order = world.get_resource::<Vec<String>>()
.expect("missing Vec<String> resource");
let mut order_iter = order.iter(); let mut order_iter = order.iter();
// ... but still executed in order // ... but still executed in order

View File

@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
/// TickTracker is used for tracking changes of [`Component`](crate::Component)s and entities. /// TickTracker is used for tracking changes of [`Component`](crate::Component)s and entities.
/// ///
/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well. /// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well.
/// Note that [`Tick::clone`] only clones the inner value of atomic, and not the atomic itself. /// Note that [`TickTracker::clone`] only clones the inner value of atomic, and not the atomic itself.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TickTracker { pub struct TickTracker {
tick: AtomicU64, tick: AtomicU64,

View File

@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use atomic_refcell::{AtomicRef, AtomicRefMut}; use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, Res, ResMut, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker, TrackedResource};
/// The id of the entity for the Archetype. /// The id of the entity for the Archetype.
/// ///
@ -65,10 +65,9 @@ impl World {
where where
B: Bundle B: Bundle
{ {
let tick = self.current_tick();
let bundle_types = bundle.type_ids(); let bundle_types = bundle.type_ids();
let tick = self.tick();
// try to find an archetype // try to find an archetype
let archetype = self.archetypes let archetype = self.archetypes
.values_mut() .values_mut()
@ -112,14 +111,8 @@ impl World {
/// Despawn an entity from the World /// Despawn an entity from the World
pub fn despawn(&mut self, entity: Entity) { pub fn despawn(&mut self, entity: Entity) {
// Tick the tracker if the entity is spawned. This is done here instead of the `if let` let tick = self.current_tick();
// below due to the borrow checker complaining about multiple mutable borrows to self.
let tick = if self.entities.arch_index.contains_key(&entity.id) {
Some(self.tick())
} else { None };
if let Some(record) = self.entities.arch_index.get_mut(&entity.id) { if let Some(record) = self.entities.arch_index.get_mut(&entity.id) {
let tick = tick.unwrap();
let arch = self.archetypes.get_mut(&record.id).unwrap(); let arch = self.archetypes.get_mut(&record.id).unwrap();
if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) {
@ -138,8 +131,7 @@ impl World {
where where
B: Bundle B: Bundle
{ {
let tick = self.tick(); let tick = self.current_tick();
let record = self.entities.entity_record(entity).unwrap(); let record = self.entities.entity_record(entity).unwrap();
let current_arch = self.archetypes.get(&record.id).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap();
let current_arch_len = current_arch.len(); let current_arch_len = current_arch.len();
@ -375,32 +367,24 @@ impl World {
} }
/// Add a resource to the world. /// 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) {
let tick = self.tick(); self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, self.current_tick()));
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
} }
/// Add the default value of a resource. /// Add the default value of a resource.
/// ///
/// Ticks the world.
///
/// > Note: This will replace existing values. /// > 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) {
let tick = self.tick(); self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), self.current_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. /// 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 /// Returns a boolean indicating if the resource was added.
/// was added.
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool { pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
let id = TypeId::of::<T>(); let id = TypeId::of::<T>();
if !self.resources.contains_key(&id) { if !self.resources.contains_key(&id) {
let tick = self.tick(); self.resources.insert(id, ResourceData::new(T::default(), self.current_tick()));
self.resources.insert(id, ResourceData::new(T::default(), tick));
true true
} else { } else {
@ -410,39 +394,60 @@ impl World {
/// 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.
/// pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> ResMut<T>
/// Ticks the world.
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
{ {
let tick = self.tick(); let tick = self.current_tick();
let res = self.resources.entry(TypeId::of::<T>()) let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(f(), tick)); .or_insert_with(|| ResourceData::new(f(), tick));
res.tick.tick_to(&tick);
res.get_mut() ResMut {
inner: res.data.borrow_mut(),
world_tick: tick,
_marker: std::marker::PhantomData::<T>,
}
} }
/// Get a resource from the world, or insert it into the world as its default. /// Get a resource from the world, or insert its default value.
/// pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> ResMut<T>
/// Ticks the world.
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
{ {
let tick = self.tick(); let tick = self.current_tick();
let res = self.resources.entry(TypeId::of::<T>()) let res = self.resources.entry(TypeId::of::<T>())
.or_insert_with(|| ResourceData::new(T::default(), tick)); .or_insert_with(|| ResourceData::new(T::default(), tick));
res.tick.tick_to(&tick);
res.get_mut() ResMut {
inner: res.data.borrow_mut(),
world_tick: tick,
_marker: std::marker::PhantomData::<T>,
}
} }
/// Gets a resource from the World. /// Gets a resource from the World.
pub fn get_resource<T: ResourceObject>(&self) -> Option<Res<T>> {
self.get_tracked_resource::<T>().map(|r| Res {
inner: r,
world_tick: self.current_tick(),
_marker: std::marker::PhantomData::<T>,
})
}
/// Gets a reference to a change tracked resource.
/// ///
/// Will panic if the resource is not in the world. See [`World::try_get_resource`] for /// You will have to manually downcast the inner resource. Most people don't need this, see
/// a function that returns an option. /// [`World::get_resource`].
pub fn get_resource<T: ResourceObject>(&self) -> AtomicRef<T> { pub fn get_tracked_resource<T: ResourceObject>(&self) -> Option<AtomicRef<TrackedResource<dyn ResourceObject>>> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>())) .map(|r| r.data.borrow())
.get() }
/// Gets a mutable borrow to a change tracked resource.
///
/// You will have to manually downcast the inner resource. Most people don't need this, see
/// [`World::get_resource_mut`].
pub fn get_tracked_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<TrackedResource<dyn ResourceObject>>> {
self.resources.get(&TypeId::of::<T>())
.map(|r| r.data.borrow_mut())
} }
/// Returns a boolean indicating if the resource changed. /// Returns a boolean indicating if the resource changed.
@ -458,7 +463,7 @@ impl World {
/// Returns the [`Tick`] that the resource was last modified at. /// Returns the [`Tick`] that the resource was last modified at.
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> { pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.map(|r| r.tick.current()) .map(|r| r.data.borrow().tick)
} }
/// Returns boolean indicating if the World contains a resource of type `T`. /// Returns boolean indicating if the World contains a resource of type `T`.
@ -466,56 +471,33 @@ impl World {
self.resources.contains_key(&TypeId::of::<T>()) self.resources.contains_key(&TypeId::of::<T>())
} }
/// Attempts to get a resource from the World.
///
/// Returns `None` if the resource was not found.
pub fn try_get_resource<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
self.resources.get(&TypeId::of::<T>())
.and_then(|r| r.try_get())
}
/// Gets a mutable borrow of a resource from the World. /// Gets a mutable borrow of a resource from the World.
/// pub fn get_resource_mut<T: ResourceObject>(&self) -> Option<ResMut<T>> {
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for self.get_tracked_resource_mut::<T>().map(|r| ResMut {
/// a function that returns an option. inner: r,
/// world_tick: self.current_tick(),
/// Ticks the world. _marker: std::marker::PhantomData::<T>,
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
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. 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| {
// 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`]. /// Get the corresponding [`ResourceData`].
/// pub fn get_resource_data<T: ResourceObject>(&self) -> Option<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>()) self.resources.get(&TypeId::of::<T>())
.map(|r| r.clone()) .map(|r| r.clone())
} }
/// Increments the TickTracker which is used for tracking changes to components. /// Increments the world current tick for tracking changes to components and resources.
/// ///
/// Most users wont need to call this manually, its done for you through queries and views. /// # Note:
/// For change tracking to work correctly, this must be ran each loop before you run world
/// systems.
pub fn tick(&self) -> Tick { pub fn tick(&self) -> Tick {
self.tracker.tick() self.tracker.tick()
} }
/// Gets the current tick that the world is at. /// Gets the current tick that the world is at.
/// ///
/// See [`TickTracker`] /// See [`World::tick`].
pub fn current_tick(&self) -> Tick { pub fn current_tick(&self) -> Tick {
self.tracker.current() self.tracker.current()
} }
@ -525,7 +507,7 @@ impl World {
} }
/// Attempts to find a resource in the world and returns a NonNull pointer to it /// Attempts to find a resource in the world and returns a NonNull pointer to it
pub unsafe fn try_get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> { pub unsafe fn get_resource_ptr<T: ResourceObject>(&self) -> Option<NonNull<T>> {
self.resources.get(&TypeId::of::<T>()) self.resources.get(&TypeId::of::<T>())
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) }) .map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
} }
@ -601,15 +583,17 @@ mod tests {
world.add_resource(counter); world.add_resource(counter);
} }
let counter = world.get_resource::<SimpleCounter>(); let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 0); assert_eq!(counter.0, 0);
drop(counter); drop(counter);
let mut counter = world.get_resource_mut::<SimpleCounter>(); let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
counter.0 += 4582; counter.0 += 4582;
drop(counter); drop(counter);
assert!(world.try_get_resource::<u32>().is_none()); assert!(world.get_resource::<u32>().is_none());
} }
#[test] #[test]
@ -619,9 +603,11 @@ mod tests {
world.add_resource(counter); world.add_resource(counter);
// test multiple borrows at the same time // test multiple borrows at the same time
let counter = world.get_resource::<SimpleCounter>(); let counter = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582); assert_eq!(counter.0, 4582);
let counter2 = world.get_resource::<SimpleCounter>(); let counter2 = world.get_resource::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582); assert_eq!(counter.0, 4582);
assert_eq!(counter2.0, 4582); assert_eq!(counter2.0, 4582);
} }
@ -635,9 +621,10 @@ mod tests {
} }
// test that its only possible to get a single mutable borrow // test that its only possible to get a single mutable borrow
let counter = world.get_resource_mut::<SimpleCounter>(); let counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
assert_eq!(counter.0, 4582); assert_eq!(counter.0, 4582);
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none()); assert!(world.get_resource_mut::<SimpleCounter>().is_none());
assert_eq!(counter.0, 4582); assert_eq!(counter.0, 4582);
} }
@ -763,7 +750,8 @@ mod tests {
assert!(!world.has_resource_changed::<SimpleCounter>()); assert!(!world.has_resource_changed::<SimpleCounter>());
let mut counter = world.get_resource_mut::<SimpleCounter>(); let mut counter = world.get_resource_mut::<SimpleCounter>()
.expect("Counter resource is missing");
counter.0 += 100; counter.0 += 100;
assert!(world.has_resource_changed::<SimpleCounter>()); assert!(world.has_resource_changed::<SimpleCounter>());

View File

@ -2,7 +2,7 @@
use std::{any::TypeId, any::Any}; use std::{any::TypeId, any::Any};
use lyra_ecs::{AtomicRef, AtomicRefMut, World}; use lyra_ecs::{query::{Res, ResMut}, AtomicRef, AtomicRefMut, World};
extern crate self as lyra_reflect; extern crate self as lyra_reflect;
@ -250,9 +250,9 @@ pub trait FromType<T> {
pub trait ReflectWorldExt { pub trait ReflectWorldExt {
/// Retrieves the type registry from the world. /// Retrieves the type registry from the world.
fn get_type_registry(&self) -> AtomicRef<TypeRegistry>; fn get_type_registry(&self) -> Option<Res<TypeRegistry>>;
/// Retrieves the type registry mutably from the world. /// Retrieves the type registry mutably from the world.
fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry>; fn get_type_registry_mut(&self) -> Option<ResMut<TypeRegistry>>;
/// Get a registered type from the type registry. Returns `None` if the type is not registered /// Get a registered type from the type registry. Returns `None` if the type is not registered
fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>>; fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>>;
/// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered. /// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered.
@ -262,17 +262,19 @@ pub trait ReflectWorldExt {
} }
impl ReflectWorldExt for World { impl ReflectWorldExt for World {
fn get_type_registry(&self) -> AtomicRef<TypeRegistry> { fn get_type_registry(&self) -> Option<Res<TypeRegistry>> {
self.get_resource::<TypeRegistry>() self.get_resource::<TypeRegistry>()
} }
fn get_type_registry_mut(&self) -> AtomicRefMut<TypeRegistry> { fn get_type_registry_mut(&self) -> Option<ResMut<TypeRegistry>> {
self.get_resource_mut::<TypeRegistry>() self.get_resource_mut::<TypeRegistry>()
} }
fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>> { fn get_type<T>(&self, type_id: TypeId) -> Option<AtomicRef<RegisteredType>> {
let r = self.get_resource::<TypeRegistry>(); let r = self.get_resource::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
if r.has_type(type_id) { if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap())) Some(AtomicRef::map(r, |tr| tr.get_type(type_id).unwrap()))
} else { } else {
None None
@ -280,8 +282,10 @@ impl ReflectWorldExt for World {
} }
fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> { fn get_type_mut<T>(&self, type_id: TypeId) -> Option<AtomicRefMut<RegisteredType>> {
let r = self.get_resource_mut::<TypeRegistry>(); let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
if r.has_type(type_id) { if r.has_type(type_id) {
let r = r.get_inner();
Some(AtomicRefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap())) Some(AtomicRefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap()))
} else { } else {
None None
@ -289,7 +293,9 @@ impl ReflectWorldExt for World {
} }
fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> { fn get_type_or_default<T>(&self, type_id: TypeId) -> AtomicRefMut<RegisteredType> {
let r = self.get_resource_mut::<TypeRegistry>(); let r = self.get_resource_mut::<TypeRegistry>()
.expect("world is missing TypeRegistry resource");
let r = r.get_inner();
AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id)) AtomicRefMut::map(r, |tr| tr.get_type_or_default(type_id))
} }
} }

View File

@ -40,19 +40,23 @@ impl<T: ResourceObject + Reflect> FromType<T> for ReflectedResource {
Self { Self {
type_id: TypeId::of::<T>(), type_id: TypeId::of::<T>(),
fn_reflect: |world: &World| { fn_reflect: |world: &World| {
world.try_get_resource::<T>() world.get_resource::<T>()
.map(|r| { .map(|r| {
// TODO: figure out change tracking for reflected resource
let r = r.get_inner();
AtomicRef::map(r, |r| r as &dyn Reflect) AtomicRef::map(r, |r| r as &dyn Reflect)
}) })
}, },
fn_reflect_mut: |world: &mut World| { fn_reflect_mut: |world: &mut World| {
world.try_get_resource_mut::<T>() world.get_resource_mut::<T>()
.map(|r| { .map(|r| {
// TODO: figure out change tracking for reflected resource
let r = r.get_inner();
AtomicRefMut::map(r, |r| r as &mut dyn Reflect) AtomicRefMut::map(r, |r| r as &mut dyn Reflect)
}) })
}, },
fn_reflect_ptr: |world: &mut World| unsafe { fn_reflect_ptr: |world: &mut World| unsafe {
world.try_get_resource_ptr::<T>() world.get_resource_ptr::<T>()
.map(|ptr| ptr.cast::<u8>()) .map(|ptr| ptr.cast::<u8>())
}, },
fn_refl_insert: |world: &mut World, this: Box<dyn Reflect>| { fn_refl_insert: |world: &mut World, this: Box<dyn Reflect>| {

View File

@ -59,7 +59,8 @@ impl WorldAssetExt for World {
} }
fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> { fn res_watcher_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let man = self.get_resource::<ResourceManager>(); let man = self.get_resource::<ResourceManager>()
.expect("world is missing ResourceManager resource");
man.watcher_event_recv(path) man.watcher_event_recv(path)
} }