diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index ae1582b547e25..cf9d9155dfaf4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,5 +1,6 @@ //! Types for declaring and storing [`Component`]s. +use crate::storage::non_unique_resource::NonUniqueResourceEntry; use crate::{ self as bevy_ecs, change_detection::MAX_CHANGE_AGE, @@ -415,6 +416,18 @@ impl ComponentDescriptor { } } + fn new_non_unique_resource() -> Self { + Self { + name: Cow::Borrowed(std::any::type_name::>()), + storage_type: StorageType::Table, + is_send_and_sync: true, + type_id: Some(TypeId::of::>()), + layout: Layout::new::>(), + drop: needs_drop::>() + .then_some(Self::drop_ptr::>), + } + } + /// Returns a value indicating the storage strategy for the current component. #[inline] pub fn storage_type(&self) -> StorageType { @@ -647,6 +660,16 @@ impl Components { } } + #[inline] + pub(crate) fn init_non_unique_resource(&mut self) -> ComponentId { + // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] + unsafe { + self.get_or_insert_resource_with(TypeId::of::>(), || { + ComponentDescriptor::new_non_unique_resource::() + }) + } + } + /// # Safety /// /// The [`ComponentDescriptor`] must match the [`TypeId`] diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 69ce32e8a8806..7e31082a98025 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -10,6 +10,7 @@ pub mod change_detection; pub mod component; pub mod entity; pub mod event; +pub mod non_unique_resource; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; diff --git a/crates/bevy_ecs/src/non_unique_resource.rs b/crates/bevy_ecs/src/non_unique_resource.rs new file mode 100644 index 0000000000000..e3f23d5ec9d8d --- /dev/null +++ b/crates/bevy_ecs/src/non_unique_resource.rs @@ -0,0 +1,343 @@ +//! Non-unique resource. +//! +//! See [`NonUniqueResourceRef`] for details. + +use crate::archetype::ArchetypeComponentId; +use crate::component::{ComponentId, Tick}; +use crate::prelude::World; +use crate::query::Access; +use crate::storage::non_unique_resource::NonUniqueResourceEntry; +use crate::storage::TableRow; +use crate::system::{check_system_change_tick, System, SystemMeta}; +use crate::system::{In, IntoSystem}; +use crate::world::unsafe_world_cell::UnsafeWorldCell; +use bevy_ptr::Ptr; +use std::any; +use std::any::TypeId; +use std::borrow::Cow; +use std::marker::PhantomData; + +/// Non-unique resource (multiple instances of the same type are stored in the world). +/// +/// Resource is allocated with [`World::new_non_unique_resource()`]. +pub struct NonUniqueResourceRef { + /// Unique per `NonUniqueResourceRef` instance. + component_id: ComponentId, + /// Index in table. + index: TableRow, + /// Allow concurrent access to different instances of `NonUniqueResourceRef`. + archetype_component_id: ArchetypeComponentId, + /// We secretly store a `T` here. + _phantom: PhantomData, +} + +impl Clone for NonUniqueResourceRef { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for NonUniqueResourceRef {} + +struct NonUniqueResourceSystem { + non_unique_resource_ref: NonUniqueResourceRef, + system_meta: SystemMeta, + function: F, + _phantom: PhantomData<(In, Out)>, +} + +impl NonUniqueResourceSystem +where + T: Sync + Send + 'static, + In: Send + Sync + 'static, + Out: Send + Sync + 'static, + F: Fn(In, Ptr) -> Out + Sync + Send + 'static, +{ + pub fn new( + non_unique_resource_ref: NonUniqueResourceRef, + function: F, + ) -> NonUniqueResourceSystem { + NonUniqueResourceSystem { + non_unique_resource_ref, + system_meta: SystemMeta::new::(), + function, + _phantom: PhantomData, + } + } +} + +impl System for NonUniqueResourceSystem +where + T: Sync + Send + 'static, + In: Send + Sync + 'static, + Out: Send + Sync + 'static, + F: Fn(In, Ptr) -> Out + Sync + Send + 'static, +{ + type In = In; + type Out = Out; + + fn name(&self) -> Cow<'static, str> { + self.system_meta.name.clone() + } + + fn type_id(&self) -> TypeId { + TypeId::of::() + } + + fn component_access(&self) -> &Access { + self.system_meta.component_access_set.combined_access() + } + + fn archetype_component_access(&self) -> &Access { + &self.system_meta.archetype_component_access + } + + fn is_send(&self) -> bool { + true + } + + fn is_exclusive(&self) -> bool { + WRITE + } + + unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out { + let ptr = world.get_non_unique_resource_by_id( + self.non_unique_resource_ref.component_id, + self.non_unique_resource_ref.index, + ); + (self.function)(input, ptr) + } + + fn apply_deferred(&mut self, _world: &mut World) {} + + fn initialize(&mut self, _world: &mut World) {} + + fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) { + // TODO: is it correct? + // TODO: panic somewhere if the same system (this and combined with this) + // accesses the same resource incompatibly. + if WRITE { + self.system_meta + .component_access_set + .add_unfiltered_write(self.non_unique_resource_ref.component_id); + + self.system_meta + .archetype_component_access + .add_write(self.non_unique_resource_ref.archetype_component_id); + } else { + self.system_meta + .component_access_set + .add_unfiltered_read(self.non_unique_resource_ref.component_id); + + self.system_meta + .archetype_component_access + .add_read(self.non_unique_resource_ref.archetype_component_id); + } + } + + fn check_change_tick(&mut self, change_tick: Tick) { + check_system_change_tick( + &mut self.system_meta.last_run, + change_tick, + self.system_meta.name.as_ref(), + ); + } + + fn get_last_run(&self) -> Tick { + self.system_meta.last_run + } + + fn set_last_run(&mut self, last_run: Tick) { + self.system_meta.last_run = last_run; + } +} + +impl NonUniqueResourceRef { + // Technically this function is unsafe because argument must match. + pub(crate) fn new( + component_id: ComponentId, + index: TableRow, + archetype_component_id: ArchetypeComponentId, + ) -> Self { + NonUniqueResourceRef { + component_id, + index, + archetype_component_id, + _phantom: PhantomData, + } + } + + /// Read the value if it is set, return `None` otherwise. + pub fn read_opt_system(&self) -> impl System> { + // SAFETY: `NonUniqueResourceSystem` guarantees that the pointer is correct. + NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |(), ptr| unsafe { + ptr.assert_unique() + .deref_mut::>() + .value + .take() + }) + } + + /// Read the value if it is set, panic otherwise. + pub fn read_system(&self) -> impl System { + // Slightly inefficient: we store index twice in the resulting system. + let index = self.index.index(); + self.read_opt_system().map(move |opt| match opt { + Some(v) => v, + None => panic!( + "Non-unique resource {}.{} is not set", + any::type_name::(), + index + ), + }) + } + + /// Read the value if it is set, return `None` otherwise. + /// + /// Keeps the value in the resource. + pub fn read_opt_clone_system(&self) -> impl System> + where + T: Clone, + { + // SAFETY: `NonUniqueResourceSystem` guarantees that the pointer is correct. + NonUniqueResourceSystem::<_, _, _, _, false>::new(*self, |(), ptr| unsafe { + ptr.deref::>().value.clone() + }) + } + + /// Read the value if it is set, panic otherwise. + /// + /// Keeps the value in the resource. + pub fn read_clone_system(&self) -> impl System + where + T: Clone, + { + // Slightly inefficient: we store index twice in the resulting system. + let index = self.index.index(); + self.read_opt_clone_system().map(move |opt| match opt { + Some(v) => v, + None => panic!( + "Non-unique resource {}.{} is not set", + any::type_name::(), + index + ), + }) + } + + /// Write the value overwriting the previous one. + pub fn write_opt_system(&self) -> impl System, Out = ()> { + // SAFETY: `NonUniqueResourceSystem` guarantees that the pointer is correct. + NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |value, ptr| unsafe { + ptr.assert_unique() + .deref_mut::>() + .value = value; + }) + } + + /// Write the value overwriting the previous one. + pub fn write_system(&self) -> impl System { + (|In(value)| Some(value)).pipe(self.write_opt_system()) + } + + /// Write the given value into the resource. + pub fn write_value_system(&self, value: T) -> impl System + where + T: Clone, + { + (move || value.clone()).pipe(self.write_system()) + } + + /// Unset the resource. + pub fn remove_system(&self) -> impl System { + (|| None).pipe(self.write_opt_system()) + } +} + +#[cfg(test)] +mod tests { + use crate as bevy_ecs; + use crate::schedule::IntoSystemConfigs; + use crate::schedule::Schedule; + use crate::schedule::SystemSet; + use crate::system::Resource; + use crate::system::{In, IntoSystem, ResMut}; + use crate::world::World; + + #[test] + fn test_write_read() { + let mut world = World::default(); + + let res = world.new_non_unique_resource::(); + + #[derive(Resource, Default)] + struct TestState(bool); + + world.init_resource::(); + + let a = res.write_value_system("a".to_owned()); + + let b = res + .read_system() + .pipe(|In(v), mut result: ResMut| { + assert_eq!("a", v); + assert!(!result.0); + result.0 = true; + }); + + #[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)] + struct Between; + + let mut schedule = Schedule::default(); + schedule.add_systems(a.before(Between)); + schedule.add_systems(b.after(Between)); + + schedule.run(&mut world); + + assert!(world.get_resource::().unwrap().0); + } + + #[test] + fn test_write_read_clone() { + let mut world = World::default(); + + let res = world.new_non_unique_resource::(); + + #[derive(Resource, Default)] + struct TestState { + b_read: bool, + c_read: bool, + } + + world.init_resource::(); + + let a = res.write_value_system("a".to_owned()); + + let b = res + .read_clone_system() + .pipe(|In(v): In, mut result: ResMut| { + assert_eq!("a", v); + assert!(!result.b_read); + result.b_read = true; + }); + let c = res + .read_clone_system() + .pipe(|In(v): In, mut result: ResMut| { + assert_eq!("a", v); + assert!(!result.c_read); + result.c_read = true; + }); + + #[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)] + struct Between; + + let mut schedule = Schedule::default(); + schedule.add_systems(a.before(Between)); + schedule.add_systems(b.after(Between)); + schedule.add_systems(c.after(Between)); + + schedule.run(&mut world); + + assert!(world.get_resource::().unwrap().b_read); + assert!(world.get_resource::().unwrap().c_read); + } +} diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 72b1dced7daf8..705cf5b5be37d 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -21,10 +21,12 @@ //! [`World::storages`]: crate::world::World::storages mod blob_vec; +pub(crate) mod non_unique_resource; mod resource; mod sparse_set; mod table; +use crate::storage::non_unique_resource::NonUniqueResources; pub use resource::*; pub use sparse_set::*; pub use table::*; @@ -40,4 +42,6 @@ pub struct Storages { pub resources: Resources, /// Backing storage for `!Send` resources. pub non_send_resources: Resources, + /// Backing storage for non-unique resources. + pub(crate) non_unique_resources: NonUniqueResources, } diff --git a/crates/bevy_ecs/src/storage/non_unique_resource.rs b/crates/bevy_ecs/src/storage/non_unique_resource.rs new file mode 100644 index 0000000000000..18d3e98cc898a --- /dev/null +++ b/crates/bevy_ecs/src/storage/non_unique_resource.rs @@ -0,0 +1,62 @@ +use bevy_ptr::{OwningPtr, Ptr}; + +use crate::archetype::ArchetypeComponentId; +use crate::component::{ComponentId, ComponentTicks, Components, Tick}; +use crate::non_unique_resource::NonUniqueResourceRef; +use crate::storage::{Column, SparseSet, TableRow}; + +pub(crate) struct NonUniqueResourceEntry { + pub(crate) value: Option, +} + +struct NonUniqueResourceData { + /// Of `NonUniqueResourceEntry`. + table: Column, +} + +#[derive(Default)] +pub(crate) struct NonUniqueResources { + resources: SparseSet, +} + +impl NonUniqueResources { + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { + for resource in self.resources.values_mut() { + resource.table.check_change_ticks(change_tick); + } + } + + pub(crate) fn new_with( + &mut self, + component_id: ComponentId, + components: &Components, + archetype_component_id: ArchetypeComponentId, + ) -> NonUniqueResourceRef { + let component = components.get_info(component_id).unwrap(); + let resource_data = + self.resources + .get_or_insert_with(component_id, || NonUniqueResourceData { + table: Column::with_capacity(component, 1), + }); + let index = TableRow::new(resource_data.table.len()); + OwningPtr::make(NonUniqueResourceEntry:: { value: None }, |ptr| { + // SAFETY: assuming `new_with` is called with matching `T` and `component_id`. + unsafe { + resource_data + .table + .push(ptr, ComponentTicks::new(Tick::new(0))); + }; + }); + NonUniqueResourceRef::new(component_id, index, archetype_component_id) + } + + pub(crate) unsafe fn get(&self, component_id: ComponentId, index: TableRow) -> Ptr<'_> { + self.resources + .get(component_id) + .unwrap() + .table + .get(index) + .unwrap() + .0 + } +} diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 4bd91ea692a76..7d7f5ae875580 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -145,7 +145,7 @@ use crate::world::World; // This trait has to be generic because we have potentially overlapping impls, in particular // because Rust thinks a type could impl multiple different `FnMut` combinations // even though none can currently -pub trait IntoSystem: Sized { +pub trait IntoSystem: Sized + 'static { /// The type of [`System`] that this instance converts into. type System: System; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 397f38bc795f3..aa4b57f72b44e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -35,6 +35,7 @@ use std::{ }; mod identifier; +use crate::non_unique_resource::NonUniqueResourceRef; pub use identifier::WorldId; use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; @@ -1396,6 +1397,19 @@ impl World { Some(resource.id()) } + /// Allocate new non-unique resource of given type. The resource is initially unset. + pub fn new_non_unique_resource(&mut self) -> NonUniqueResourceRef { + let component_id = self.components.init_non_unique_resource::(); + let archetype_component_count = &mut self.archetypes.archetype_component_count; + let archetype_component_id = ArchetypeComponentId::new(*archetype_component_count); + *archetype_component_count += 1; + self.storages.non_unique_resources.new_with( + component_id, + &self.components, + archetype_component_id, + ) + } + /// For a given batch of ([`Entity`], [`Bundle`]) pairs, either spawns each [`Entity`] with the given /// bundle (if the entity does not exist), or inserts the [`Bundle`] (if the entity already exists). /// This is faster than doing equivalent operations one-by-one. @@ -1790,6 +1804,7 @@ impl World { ref mut sparse_sets, ref mut resources, ref mut non_send_resources, + ref mut non_unique_resources, } = self.storages; #[cfg(feature = "trace")] @@ -1798,6 +1813,7 @@ impl World { sparse_sets.check_change_ticks(change_tick); resources.check_change_ticks(change_tick); non_send_resources.check_change_ticks(change_tick); + non_unique_resources.check_change_ticks(change_tick); if let Some(mut schedules) = self.get_resource_mut::() { schedules.check_change_ticks(change_tick); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 25d5ac62c0305..fc6605280563e 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -2,7 +2,11 @@ #![warn(unsafe_op_in_unsafe_fn)] -use super::{Mut, Ref, World, WorldId}; +use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; + +use bevy_ptr::Ptr; + +use crate::storage::TableRow; use crate::{ archetype::{Archetype, ArchetypeComponentId, Archetypes}, bundle::Bundles, @@ -16,8 +20,8 @@ use crate::{ storage::{Column, ComponentSparseSet, Storages}, system::Resource, }; -use bevy_ptr::Ptr; -use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData}; + +use super::{Mut, Ref, World, WorldId}; /// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid /// aliasing violations are given to the caller instead of being checked at compile-time by rust's unique XOR shared rule. @@ -566,6 +570,21 @@ impl<'w> UnsafeWorldCell<'w> { .get(component_id)? .get_with_ticks() } + + #[inline] + pub(crate) unsafe fn get_non_unique_resource_by_id( + self, + component_id: ComponentId, + index: TableRow, + ) -> Ptr<'w> { + // SAFETY: we only access data that the caller has ensured is unaliased and `self` + // has permission to access. + unsafe { + self.storages() + .non_unique_resources + .get(component_id, index) + } + } } impl Debug for UnsafeWorldCell<'_> {