Resource change tracking (#388)

* Add mutated tracker on resources and ChangedRes query for added or mutated resources.

* ResMut:::new() now takes a reference to a 'mutated' flag in its archetype.

* Change FetchResource so that get() returns an Option. Systems using Resources will only be called if all fetched Resources are Some(). This is done to implement ChangedRes, which is Some iff the Resource has been changed.

* Add OrRes for a logical or in tuples of Resource queries.

* Separate resource query get() in is_some() and get() methods for clarity

* Remove unneeded unsafe

* Change ResMut::new()
This commit is contained in:
BimDav 2020-09-10 22:15:02 +02:00 committed by GitHub
parent 12deb0bd91
commit 4ef18e2608
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 341 additions and 37 deletions

View file

@ -11,7 +11,7 @@ pub use world::*;
pub mod prelude {
pub use crate::{
resource::{FromResources, Local, Res, ResMut, Resource, Resources},
resource::{ChangedRes, FromResources, Local, OrRes, Res, ResMut, Resource, Resources},
system::{
Commands, IntoForEachSystem, IntoQuerySystem, IntoThreadLocalSystem, Query, System,
},

View file

@ -11,6 +11,41 @@ use core::{
};
use std::marker::PhantomData;
/// A shared borrow of a Resource
/// that will only return in a query if the Resource has been changed
pub struct ChangedRes<'a, T: Resource> {
value: &'a T,
}
impl<'a, T: Resource> ChangedRes<'a, T> {
/// Creates a reference cell to a Resource from a pointer
///
/// # Safety
/// The pointer must have correct lifetime / storage
pub unsafe fn new(value: NonNull<T>) -> Self {
Self {
value: &*value.as_ptr(),
}
}
}
impl<'a, T: Resource> UnsafeClone for ChangedRes<'a, T> {
unsafe fn unsafe_clone(&self) -> Self {
Self { value: self.value }
}
}
unsafe impl<T: Resource> Send for ChangedRes<'_, T> {}
unsafe impl<T: Resource> Sync for ChangedRes<'_, T> {}
impl<'a, T: Resource> Deref for ChangedRes<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.value
}
}
/// Shared borrow of a Resource
pub struct Res<'a, T: Resource> {
value: &'a T,
@ -55,6 +90,7 @@ impl<'a, T: Resource> Deref for Res<'a, T> {
pub struct ResMut<'a, T: Resource> {
_marker: PhantomData<&'a T>,
value: *mut T,
mutated: *mut bool,
}
impl<'a, T: Resource> ResMut<'a, T> {
@ -62,9 +98,10 @@ impl<'a, T: Resource> ResMut<'a, T> {
///
/// # Safety
/// The pointer must have correct lifetime / storage / ownership
pub unsafe fn new(value: NonNull<T>) -> Self {
pub unsafe fn new(value: NonNull<T>, mutated: NonNull<bool>) -> Self {
Self {
value: value.as_ptr(),
mutated: mutated.as_ptr(),
_marker: Default::default(),
}
}
@ -83,7 +120,10 @@ impl<'a, T: Resource> Deref for ResMut<'a, T> {
impl<'a, T: Resource> DerefMut for ResMut<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.value }
unsafe {
*self.mutated = true;
&mut *self.value
}
}
}
@ -91,6 +131,7 @@ impl<'a, T: Resource> UnsafeClone for ResMut<'a, T> {
unsafe fn unsafe_clone(&self) -> Self {
Self {
value: self.value,
mutated: self.mutated,
_marker: Default::default(),
}
}
@ -144,6 +185,11 @@ pub trait FetchResource<'a>: Sized {
#[allow(clippy::missing_safety_doc)]
unsafe fn get(resources: &'a Resources, system_id: Option<SystemId>) -> Self::Item;
#[allow(clippy::missing_safety_doc)]
unsafe fn is_some(_resources: &'a Resources, _system_id: Option<SystemId>) -> bool {
true
}
}
impl<'a, T: Resource> ResourceQuery for Res<'a, T> {
@ -175,6 +221,40 @@ impl<'a, T: Resource> FetchResource<'a> for FetchResourceRead<T> {
}
}
impl<'a, T: Resource> ResourceQuery for ChangedRes<'a, T> {
type Fetch = FetchResourceChanged<T>;
}
/// Fetches a shared resource reference
pub struct FetchResourceChanged<T>(NonNull<T>);
impl<'a, T: Resource> FetchResource<'a> for FetchResourceChanged<T> {
type Item = ChangedRes<'a, T>;
unsafe fn get(resources: &'a Resources, _system_id: Option<SystemId>) -> Self::Item {
ChangedRes::new(resources.get_unsafe_ref::<T>(ResourceIndex::Global))
}
unsafe fn is_some(resources: &'a Resources, _system_id: Option<SystemId>) -> bool {
let (added, mutated) = resources.get_unsafe_added_and_mutated::<T>(ResourceIndex::Global);
*added.as_ptr() || *mutated.as_ptr()
}
fn borrow(resources: &Resources) {
resources.borrow::<T>();
}
fn release(resources: &Resources) {
resources.release::<T>();
}
fn access() -> TypeAccess {
let mut access = TypeAccess::default();
access.immutable.insert(TypeId::of::<T>());
access
}
}
impl<'a, T: Resource> ResourceQuery for ResMut<'a, T> {
type Fetch = FetchResourceWrite<T>;
}
@ -186,7 +266,8 @@ impl<'a, T: Resource> FetchResource<'a> for FetchResourceWrite<T> {
type Item = ResMut<'a, T>;
unsafe fn get(resources: &'a Resources, _system_id: Option<SystemId>) -> Self::Item {
ResMut::new(resources.get_unsafe_ref::<T>(ResourceIndex::Global))
let (value, mutated) = resources.get_unsafe_ref_with_mutated::<T>(ResourceIndex::Global);
ResMut::new(value, mutated)
}
fn borrow(resources: &Resources) {
@ -265,6 +346,11 @@ macro_rules! tuple_impl {
($($name::get(resources, system_id),)*)
}
#[allow(unused_variables)]
unsafe fn is_some(resources: &'a Resources, system_id: Option<SystemId>) -> bool {
true $(&& $name::is_some(resources, system_id))*
}
#[allow(unused_mut)]
fn access() -> TypeAccess {
let mut access = TypeAccess::default();
@ -294,3 +380,113 @@ macro_rules! tuple_impl {
}
smaller_tuples_too!(tuple_impl, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A);
pub struct OrRes<T>(T);
pub struct FetchResourceOr<T>(NonNull<T>);
macro_rules! tuple_impl_or {
($($name: ident),*) => {
impl<'a, $($name: FetchResource<'a>),*> FetchResource<'a> for FetchResourceOr<($($name,)*)> {
type Item = OrRes<($($name::Item,)*)>;
#[allow(unused_variables)]
fn borrow(resources: &Resources) {
$($name::borrow(resources);)*
}
#[allow(unused_variables)]
fn release(resources: &Resources) {
$($name::release(resources);)*
}
#[allow(unused_variables)]
unsafe fn get(resources: &'a Resources, system_id: Option<SystemId>) -> Self::Item {
OrRes(($($name::get(resources, system_id),)*))
}
#[allow(unused_variables)]
unsafe fn is_some(resources: &'a Resources, system_id: Option<SystemId>) -> bool {
false $(|| $name::is_some(resources, system_id))*
}
#[allow(unused_mut)]
fn access() -> TypeAccess {
let mut access = TypeAccess::default();
$(access.union(&$name::access());)*
access
}
}
impl<$($name: ResourceQuery),*> ResourceQuery for OrRes<($($name,)*)> {
type Fetch = FetchResourceOr<($($name::Fetch,)*)>;
#[allow(unused_variables)]
fn initialize(resources: &mut Resources, system_id: Option<SystemId>) {
$($name::initialize(resources, system_id);)*
}
}
#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<$($name: UnsafeClone),*> UnsafeClone for OrRes<($($name,)*)> {
unsafe fn unsafe_clone(&self) -> Self {
let OrRes(($($name,)*)) = self;
OrRes(($($name.unsafe_clone(),)*))
}
}
impl<$($name,)*> Deref for OrRes<($($name,)*)> {
type Target = ($($name,)*);
fn deref(&self) -> &Self::Target {
&self.0
}
}
};
}
smaller_tuples_too!(tuple_impl_or, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn changed_resource() {
let mut resources = Resources::default();
resources.insert(123);
assert_eq!(
resources.query::<ChangedRes<i32>>().as_deref(),
Some(&(123 as i32))
);
resources.clear_trackers();
assert_eq!(resources.query::<ChangedRes<i32>>().as_deref(), None);
*resources.query::<ResMut<i32>>().unwrap() += 1;
assert_eq!(
resources.query::<ChangedRes<i32>>().as_deref(),
Some(&(124 as i32))
);
}
#[test]
fn or_changed_resource() {
let mut resources = Resources::default();
resources.insert(123);
resources.insert(0.2);
assert!(resources
.query::<OrRes<(ChangedRes<i32>, ChangedRes<f64>)>>()
.is_some(),);
resources.clear_trackers();
assert!(resources
.query::<OrRes<(ChangedRes<i32>, ChangedRes<f64>)>>()
.is_none(),);
*resources.query::<ResMut<i32>>().unwrap() += 1;
assert!(resources
.query::<OrRes<(ChangedRes<i32>, ChangedRes<f64>)>>()
.is_some(),);
assert!(resources
.query::<(ChangedRes<i32>, ChangedRes<f64>)>()
.is_none(),);
}
}

View file

@ -140,36 +140,93 @@ impl Resources {
})
}
pub fn query<Q: ResourceQuery>(&self) -> <Q::Fetch as FetchResource>::Item {
unsafe { Q::Fetch::get(&self, None) }
pub fn query<Q: ResourceQuery>(&self) -> Option<<Q::Fetch as FetchResource>::Item> {
unsafe {
if Q::Fetch::is_some(&self, None) {
Some(Q::Fetch::get(&self, None))
} else {
None
}
}
}
pub fn query_system<Q: ResourceQuery>(
&self,
id: SystemId,
) -> <Q::Fetch as FetchResource>::Item {
unsafe { Q::Fetch::get(&self, Some(id)) }
) -> Option<<Q::Fetch as FetchResource>::Item> {
unsafe {
if Q::Fetch::is_some(&self, Some(id)) {
Some(Q::Fetch::get(&self, Some(id)))
} else {
None
}
}
}
#[inline]
#[allow(clippy::missing_safety_doc)]
pub unsafe fn get_unsafe_ref<T: Resource>(&self, resource_index: ResourceIndex) -> NonNull<T> {
self.resource_data
.get(&TypeId::of::<T>())
.and_then(|data| {
let index = match resource_index {
ResourceIndex::Global => data.default_index?,
ResourceIndex::System(id) => {
data.system_id_to_archetype_index.get(&id.0).cloned()?
}
};
self.get_resource_data_index::<T>(resource_index)
.and_then(|(data, index)| {
Some(NonNull::new_unchecked(
data.archetype.get::<T>()?.as_ptr().add(index as usize),
data.archetype.get::<T>()?.as_ptr().add(index),
))
})
.unwrap_or_else(|| panic!("Resource does not exist {}", std::any::type_name::<T>()))
}
#[inline]
#[allow(clippy::missing_safety_doc)]
pub unsafe fn get_unsafe_ref_with_mutated<T: Resource>(
&self,
resource_index: ResourceIndex,
) -> (NonNull<T>, NonNull<bool>) {
self.get_resource_data_index::<T>(resource_index)
.and_then(|(data, index)| {
data.archetype
.get_with_mutated::<T>()
.map(|(resource, mutated)| {
(
NonNull::new_unchecked(resource.as_ptr().add(index)),
NonNull::new_unchecked(mutated.as_ptr().add(index)),
)
})
})
.unwrap_or_else(|| panic!("Resource does not exist {}", std::any::type_name::<T>()))
}
#[inline]
#[allow(clippy::missing_safety_doc)]
pub unsafe fn get_unsafe_added_and_mutated<T: Resource>(
&self,
resource_index: ResourceIndex,
) -> (NonNull<bool>, NonNull<bool>) {
self.get_resource_data_index::<T>(resource_index)
.and_then(|(data, index)| {
Some((
NonNull::new_unchecked(data.archetype.get_added::<T>()?.as_ptr().add(index)),
NonNull::new_unchecked(data.archetype.get_mutated::<T>()?.as_ptr().add(index)),
))
})
.unwrap_or_else(|| panic!("Resource does not exist {}", std::any::type_name::<T>()))
}
#[inline]
fn get_resource_data_index<T: Resource>(
&self,
resource_index: ResourceIndex,
) -> Option<(&ResourceData, usize)> {
self.resource_data.get(&TypeId::of::<T>()).and_then(|data| {
let index = match resource_index {
ResourceIndex::Global => data.default_index?,
ResourceIndex::System(id) => {
data.system_id_to_archetype_index.get(&id.0).cloned()?
}
};
Some((data, index as usize))
})
}
pub fn borrow<T: Resource>(&self) {
if let Some(data) = self.resource_data.get(&TypeId::of::<T>()) {
data.archetype.borrow::<T>();
@ -193,6 +250,14 @@ impl Resources {
data.archetype.release_mut::<T>();
}
}
/// Clears each resource's tracker state.
/// For example, each resource's component "mutated" state will be reset to `false`.
pub fn clear_trackers(&mut self) {
for (_, resource_data) in self.resource_data.iter_mut() {
resource_data.archetype.clear_trackers();
}
}
}
unsafe impl Send for Resources {}

View file

@ -60,6 +60,7 @@ impl ParallelExecutor {
if self.clear_trackers {
world.clear_trackers();
resources.clear_trackers();
}
self.last_schedule_generation = schedule_generation;

View file

@ -159,6 +159,7 @@ impl Schedule {
}
world.clear_trackers();
resources.clear_trackers();
}
// TODO: move this code to ParallelExecutor

View file

@ -106,9 +106,10 @@ macro_rules! impl_into_foreach_system {
func: move |world, resources, _archetype_access, state| {
<<($($resource,)*) as ResourceQuery>::Fetch as FetchResource>::borrow(&resources);
{
let ($($resource,)*) = resources.query_system::<($($resource,)*)>(id);
for ($($component,)*) in world.query::<($($component,)*)>().iter() {
fn_call!(self, ($($commands, state)*), ($($resource),*), ($($component),*))
if let Some(($($resource,)*)) = resources.query_system::<($($resource,)*)>(id) {
for ($($component,)*) in world.query::<($($component,)*)>().iter() {
fn_call!(self, ($($commands, state)*), ($($resource),*), ($($component),*))
}
}
}
<<($($resource,)*) as ResourceQuery>::Fetch as FetchResource>::release(&resources);
@ -175,15 +176,16 @@ macro_rules! impl_into_query_system {
func: move |world, resources, archetype_access, state| {
<<($($resource,)*) as ResourceQuery>::Fetch as FetchResource>::borrow(&resources);
{
let ($($resource,)*) = resources.query_system::<($($resource,)*)>(id);
let mut i = 0;
$(
let $query = Query::<$query>::new(world, &state.archetype_accesses[i]);
i += 1;
)*
if let Some(($($resource,)*)) = resources.query_system::<($($resource,)*)>(id) {
let mut i = 0;
$(
let $query = Query::<$query>::new(world, &state.archetype_accesses[i]);
i += 1;
)*
let commands = &state.commands;
fn_call!(self, ($($commands, commands)*), ($($resource),*), ($($query),*))
let commands = &state.commands;
fn_call!(self, ($($commands, commands)*), ($($resource),*), ($($query),*))
}
}
<<($($resource,)*) as ResourceQuery>::Fetch as FetchResource>::release(&resources);
},
@ -342,10 +344,11 @@ where
#[cfg(test)]
mod tests {
use super::{IntoQuerySystem, Query};
use super::{IntoForEachSystem, IntoQuerySystem, Query};
use crate::{
resource::{ResMut, Resources},
schedule::Schedule,
ChangedRes, Mut,
};
use bevy_hecs::{Entity, With, World};
@ -415,4 +418,30 @@ mod tests {
assert!(*resources.get::<bool>().unwrap(), "system ran");
}
#[test]
fn changed_resource_system() {
fn incr_e_on_flip(_run_on_flip: ChangedRes<bool>, mut i: Mut<i32>) {
*i += 1;
}
let mut world = World::default();
let mut resources = Resources::default();
resources.insert(false);
let ent = world.spawn((0,));
let mut schedule = Schedule::default();
schedule.add_stage("update");
schedule.add_system_to_stage("update", incr_e_on_flip.system());
schedule.run(&mut world, &mut resources);
assert_eq!(*(world.get::<i32>(ent).unwrap()), 1);
schedule.run(&mut world, &mut resources);
assert_eq!(*(world.get::<i32>(ent).unwrap()), 1);
*resources.get_mut::<bool>().unwrap() = true;
schedule.run(&mut world, &mut resources);
assert_eq!(*(world.get::<i32>(ent).unwrap()), 2);
}
}

View file

@ -176,14 +176,26 @@ impl<'a> FetchResource<'a> for FetchDrawContext {
}
unsafe fn get(resources: &'a Resources, _system_id: Option<SystemId>) -> Self::Item {
let pipelines = {
let (value, mutated) = resources
.get_unsafe_ref_with_mutated::<Assets<PipelineDescriptor>>(ResourceIndex::Global);
ResMut::new(value, mutated)
};
let shaders = {
let (value, mutated) =
resources.get_unsafe_ref_with_mutated::<Assets<Shader>>(ResourceIndex::Global);
ResMut::new(value, mutated)
};
let pipeline_compiler = {
let (value, mutated) =
resources.get_unsafe_ref_with_mutated::<PipelineCompiler>(ResourceIndex::Global);
ResMut::new(value, mutated)
};
DrawContext {
pipelines: ResMut::new(
resources.get_unsafe_ref::<Assets<PipelineDescriptor>>(ResourceIndex::Global),
),
shaders: ResMut::new(resources.get_unsafe_ref::<Assets<Shader>>(ResourceIndex::Global)),
pipeline_compiler: ResMut::new(
resources.get_unsafe_ref::<PipelineCompiler>(ResourceIndex::Global),
),
pipelines,
shaders,
pipeline_compiler,
render_resource_context: Res::new(
resources.get_unsafe_ref::<Box<dyn RenderResourceContext>>(ResourceIndex::Global),
),