diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index e4cca41a4d7aa..25bc966372af9 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -521,6 +521,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { #(#fields: #field_locals,)* } } + + fn world_access_level() -> #path::system::WorldAccessLevel { + <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::world_access_level() + } } // Safety: Each field is `ReadOnlySystemParam`, so this can only read from the `World` diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs index c81a5cfa1eb2c..dbe22ce6bdf3f 100644 --- a/crates/bevy_ecs/src/removal_detection.rs +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -7,7 +7,7 @@ use crate::{ event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events}, prelude::Local, storage::SparseSet, - system::{ReadOnlySystemParam, SystemMeta, SystemParam}, + system::{ReadOnlySystemParam, SystemMeta, SystemParam, WorldAccessLevel}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; @@ -266,4 +266,8 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents { ) -> Self::Item<'w, 's> { world.removed_components() } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } diff --git a/crates/bevy_ecs/src/schedule/set.rs b/crates/bevy_ecs/src/schedule/set.rs index 010e9b5303848..1c498d1ee8b54 100644 --- a/crates/bevy_ecs/src/schedule/set.rs +++ b/crates/bevy_ecs/src/schedule/set.rs @@ -12,10 +12,7 @@ pub use bevy_ecs_macros::{ScheduleLabel, SystemSet}; use crate::{ define_label, intern::Interned, - system::{ - ExclusiveFunctionSystem, ExclusiveSystemParamFunction, FunctionSystem, - IsExclusiveFunctionSystem, IsFunctionSystem, SystemParamFunction, - }, + system::{FunctionSystem, IsFunctionSystem, SystemParamFunction}, }; define_label!( @@ -200,20 +197,6 @@ where } } -// exclusive systems -impl IntoSystemSet<(IsExclusiveFunctionSystem, Marker)> for F -where - Marker: 'static, - F: ExclusiveSystemParamFunction, -{ - type Set = SystemTypeSet>; - - #[inline] - fn into_system_set(self) -> Self::Set { - SystemTypeSet::>::new() - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index c7605764e98f7..355d8fa1814cd 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -8,6 +8,7 @@ use crate::{ resource::Resource, system::{ DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, + WorldAccessLevel, }, world::{ FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut, @@ -513,6 +514,11 @@ impl<'a> DynParamBuilder<'a> { /// Creates a new [`DynParamBuilder`] by wrapping a [`SystemParamBuilder`] of any type. /// The built [`DynSystemParam`] can be downcast to `T`. pub fn new(builder: impl SystemParamBuilder + 'a) -> Self { + assert_ne!( + T::world_access_level(), + WorldAccessLevel::Exclusive, + "DynSystemParam cannot hold exclusive system parameters" + ); Self(Box::new(|world, meta| { DynSystemParamState::new::(builder.build(world, meta)) })) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 5694df4a49451..cf116d19b4b3f 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -29,7 +29,7 @@ use crate::{ schedule::ScheduleLabel, system::{ command::HandleError, entity_command::CommandWithEntity, input::SystemInput, Deferred, - IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, + IntoObserverSystem, IntoSystem, RegisteredSystem, SystemId, WorldAccessLevel, }, world::{ command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue, @@ -202,6 +202,10 @@ const _: () = { entities: f1, } } + + fn world_access_level() -> WorldAccessLevel { + <(Deferred, &Entities) as bevy_ecs::system::SystemParam>::world_access_level() + } } // SAFETY: Only reads Entities unsafe impl<'w, 's> bevy_ecs::system::ReadOnlySystemParam for Commands<'w, 's> diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs deleted file mode 100644 index 2b1c081d51db0..0000000000000 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ /dev/null @@ -1,342 +0,0 @@ -use crate::{ - archetype::ArchetypeComponentId, - component::{ComponentId, Tick}, - query::Access, - schedule::{InternedSystemSet, SystemSet}, - system::{ - check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem, - System, SystemIn, SystemInput, SystemMeta, - }, - world::{unsafe_world_cell::UnsafeWorldCell, World}, -}; - -use alloc::{borrow::Cow, vec, vec::Vec}; -use core::marker::PhantomData; -use variadics_please::all_tuples; - -/// A function system that runs with exclusive [`World`] access. -/// -/// You get this by calling [`IntoSystem::into_system`] on a function that only accepts -/// [`ExclusiveSystemParam`]s. -/// -/// [`ExclusiveFunctionSystem`] must be `.initialized` before they can be run. -pub struct ExclusiveFunctionSystem -where - F: ExclusiveSystemParamFunction, -{ - func: F, - param_state: Option<::State>, - system_meta: SystemMeta, - // NOTE: PhantomData T> gives this safe Send/Sync impls - marker: PhantomData Marker>, -} - -impl ExclusiveFunctionSystem -where - F: ExclusiveSystemParamFunction, -{ - /// Return this system with a new name. - /// - /// Useful to give closure systems more readable and unique names for debugging and tracing. - pub fn with_name(mut self, new_name: impl Into>) -> Self { - self.system_meta.set_name(new_name.into()); - self - } -} - -/// A marker type used to distinguish exclusive function systems from regular function systems. -#[doc(hidden)] -pub struct IsExclusiveFunctionSystem; - -impl IntoSystem for F -where - Marker: 'static, - F: ExclusiveSystemParamFunction, -{ - type System = ExclusiveFunctionSystem; - fn into_system(func: Self) -> Self::System { - ExclusiveFunctionSystem { - func, - param_state: None, - system_meta: SystemMeta::new::(), - marker: PhantomData, - } - } -} - -const PARAM_MESSAGE: &str = "System's param_state was not found. Did you forget to initialize this system before running it?"; - -impl System for ExclusiveFunctionSystem -where - Marker: 'static, - F: ExclusiveSystemParamFunction, -{ - type In = F::In; - type Out = F::Out; - - #[inline] - fn name(&self) -> Cow<'static, str> { - self.system_meta.name.clone() - } - - #[inline] - fn component_access(&self) -> &Access { - self.system_meta.component_access_set.combined_access() - } - - #[inline] - fn archetype_component_access(&self) -> &Access { - &self.system_meta.archetype_component_access - } - - #[inline] - fn is_send(&self) -> bool { - // exclusive systems should have access to non-send resources - // the executor runs exclusive systems on the main thread, so this - // field reflects that constraint - false - } - - #[inline] - fn is_exclusive(&self) -> bool { - true - } - - #[inline] - fn has_deferred(&self) -> bool { - // exclusive systems have no deferred system params - false - } - - #[inline] - unsafe fn run_unsafe( - &mut self, - _input: SystemIn<'_, Self>, - _world: UnsafeWorldCell, - ) -> Self::Out { - panic!("Cannot run exclusive systems with a shared World reference"); - } - - fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out { - world.last_change_tick_scope(self.system_meta.last_run, |world| { - #[cfg(feature = "trace")] - let _span_guard = self.system_meta.system_span.enter(); - - let params = F::Param::get_param( - self.param_state.as_mut().expect(PARAM_MESSAGE), - &self.system_meta, - ); - let out = self.func.run(world, input, params); - - world.flush(); - self.system_meta.last_run = world.increment_change_tick(); - - out - }) - } - - #[inline] - fn apply_deferred(&mut self, _world: &mut World) { - // "pure" exclusive systems do not have any buffers to apply. - // Systems made by piping a normal system with an exclusive system - // might have buffers to apply, but this is handled by `PipeSystem`. - } - - #[inline] - fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) { - // "pure" exclusive systems do not have any buffers to apply. - // Systems made by piping a normal system with an exclusive system - // might have buffers to apply, but this is handled by `PipeSystem`. - } - - #[inline] - unsafe fn validate_param_unsafe(&mut self, _world: UnsafeWorldCell) -> bool { - // All exclusive system params are always available. - true - } - - #[inline] - fn initialize(&mut self, world: &mut World) { - self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX); - self.param_state = Some(F::Param::init(world, &mut self.system_meta)); - } - - fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {} - - #[inline] - 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 default_system_sets(&self) -> Vec { - let set = crate::schedule::SystemTypeSet::::new(); - vec![set.intern()] - } - - 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; - } -} - -/// A trait implemented for all exclusive system functions that can be used as [`System`]s. -/// -/// This trait can be useful for making your own systems which accept other systems, -/// sometimes called higher order systems. -#[diagnostic::on_unimplemented( - message = "`{Self}` is not an exclusive system", - label = "invalid system" -)] -pub trait ExclusiveSystemParamFunction: Send + Sync + 'static { - /// The input type to this system. See [`System::In`]. - type In: SystemInput; - - /// The return type of this system. See [`System::Out`]. - type Out; - - /// The [`ExclusiveSystemParam`]'s defined by this system's `fn` parameters. - type Param: ExclusiveSystemParam; - - /// Executes this system once. See [`System::run`]. - fn run( - &mut self, - world: &mut World, - input: ::Inner<'_>, - param_value: ExclusiveSystemParamItem, - ) -> Self::Out; -} - -/// A marker type used to distinguish exclusive function systems with and without input. -#[doc(hidden)] -pub struct HasExclusiveSystemInput; - -macro_rules! impl_exclusive_system_function { - ($($param: ident),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - impl ExclusiveSystemParamFunction Out> for Func - where - Func: Send + Sync + 'static, - for <'a> &'a mut Func: - FnMut(&mut World, $($param),*) -> Out + - FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out, - Out: 'static, - { - type In = (); - type Out = Out; - type Param = ($($param,)*); - #[inline] - fn run(&mut self, world: &mut World, _in: (), param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out { - // Yes, this is strange, but `rustc` fails to compile this impl - // without using this function. It fails to recognize that `func` - // is a function, potentially because of the multiple impls of `FnMut` - fn call_inner( - mut f: impl FnMut(&mut World, $($param,)*) -> Out, - world: &mut World, - $($param: $param,)* - ) -> Out { - f(world, $($param,)*) - } - let ($($param,)*) = param_value; - call_inner(self, world, $($param),*) - } - } - - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - impl ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func - where - Func: Send + Sync + 'static, - for <'a> &'a mut Func: - FnMut(In, &mut World, $($param),*) -> Out + - FnMut(In::Param<'_>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out, - In: SystemInput + 'static, - Out: 'static, - { - type In = In; - type Out = Out; - type Param = ($($param,)*); - #[inline] - fn run(&mut self, world: &mut World, input: In::Inner<'_>, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out { - // Yes, this is strange, but `rustc` fails to compile this impl - // without using this function. It fails to recognize that `func` - // is a function, potentially because of the multiple impls of `FnMut` - fn call_inner( - mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out, - input: In::Inner<'_>, - world: &mut World, - $($param: $param,)* - ) -> Out { - f(In::wrap(input), world, $($param,)*) - } - let ($($param,)*) = param_value; - call_inner(self, input, world, $($param),*) - } - } - }; -} -// Note that we rely on the highest impl to be <= the highest order of the tuple impls -// of `SystemParam` created. -all_tuples!(impl_exclusive_system_function, 0, 16, F); - -#[cfg(test)] -mod tests { - use crate::system::input::SystemInput; - - use super::*; - - #[test] - fn into_system_type_id_consistency() { - fn test(function: T) - where - T: IntoSystem + Copy, - { - fn reference_system(_world: &mut World) {} - - use core::any::TypeId; - - let system = IntoSystem::into_system(function); - - assert_eq!( - system.type_id(), - function.system_type_id(), - "System::type_id should be consistent with IntoSystem::system_type_id" - ); - - assert_eq!( - system.type_id(), - TypeId::of::(), - "System::type_id should be consistent with TypeId::of::()" - ); - - assert_ne!( - system.type_id(), - IntoSystem::into_system(reference_system).type_id(), - "Different systems should have different TypeIds" - ); - } - - fn exclusive_function_system(_world: &mut World) {} - - test(exclusive_function_system); - } -} diff --git a/crates/bevy_ecs/src/system/exclusive_system_param.rs b/crates/bevy_ecs/src/system/exclusive_system_param.rs deleted file mode 100644 index e36b7ea759bdd..0000000000000 --- a/crates/bevy_ecs/src/system/exclusive_system_param.rs +++ /dev/null @@ -1,169 +0,0 @@ -use crate::{ - prelude::{FromWorld, QueryState}, - query::{QueryData, QueryFilter}, - system::{Local, SystemMeta, SystemParam, SystemState}, - world::World, -}; -use bevy_utils::synccell::SyncCell; -use core::marker::PhantomData; -use variadics_please::all_tuples; - -/// A parameter that can be used in an exclusive system (a system with an `&mut World` parameter). -/// Any parameters implementing this trait must come after the `&mut World` parameter. -#[diagnostic::on_unimplemented( - message = "`{Self}` can not be used as a parameter for an exclusive system", - label = "invalid system parameter" -)] -pub trait ExclusiveSystemParam: Sized { - /// Used to store data which persists across invocations of a system. - type State: Send + Sync + 'static; - /// The item type returned when constructing this system param. - /// See [`SystemParam::Item`]. - type Item<'s>: ExclusiveSystemParam; - - /// Creates a new instance of this param's [`State`](Self::State). - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; - - /// Creates a parameter to be passed into an [`ExclusiveSystemParamFunction`]. - /// - /// [`ExclusiveSystemParamFunction`]: super::ExclusiveSystemParamFunction - fn get_param<'s>(state: &'s mut Self::State, system_meta: &SystemMeta) -> Self::Item<'s>; -} - -/// Shorthand way of accessing the associated type [`ExclusiveSystemParam::Item`] -/// for a given [`ExclusiveSystemParam`]. -pub type ExclusiveSystemParamItem<'s, P> =

::Item<'s>; - -impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> ExclusiveSystemParam - for &'a mut QueryState -{ - type State = QueryState; - type Item<'s> = &'s mut QueryState; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - QueryState::new(world) - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - state - } -} - -impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState

{ - type State = SystemState

; - type Item<'s> = &'s mut SystemState

; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - SystemState::new(world) - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - state - } -} - -impl<'_s, T: FromWorld + Send + 'static> ExclusiveSystemParam for Local<'_s, T> { - type State = SyncCell; - type Item<'s> = Local<'s, T>; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - SyncCell::new(T::from_world(world)) - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - Local(state.get()) - } -} - -impl ExclusiveSystemParam for PhantomData { - type State = (); - type Item<'s> = PhantomData; - - fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {} - - fn get_param<'s>(_state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - PhantomData - } -} - -macro_rules! impl_exclusive_system_param_tuple { - ($(#[$meta:meta])* $($param: ident),*) => { - #[expect( - clippy::allow_attributes, - reason = "This is within a macro, and as such, the below lints may not always apply." - )] - #[allow( - non_snake_case, - reason = "Certain variable names are provided by the caller, not by us." - )] - #[allow( - unused_variables, - reason = "Zero-length tuples won't use any of the parameters." - )] - $(#[$meta])* - impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) { - type State = ($($param::State,)*); - type Item<'s> = ($($param::Item<'s>,)*); - - #[inline] - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - (($($param::init(world, system_meta),)*)) - } - - #[inline] - fn get_param<'s>( - state: &'s mut Self::State, - system_meta: &SystemMeta, - ) -> Self::Item<'s> { - let ($($param,)*) = state; - #[allow( - clippy::unused_unit, - reason = "Zero-length tuples won't have any params to get." - )] - ($($param::get_param($param, system_meta),)*) - } - } - }; -} - -all_tuples!( - #[doc(fake_variadic)] - impl_exclusive_system_param_tuple, - 0, - 16, - P -); - -#[cfg(test)] -mod tests { - use crate as bevy_ecs; - use crate::{schedule::Schedule, system::Local, world::World}; - use alloc::vec::Vec; - use bevy_ecs_macros::Resource; - use core::marker::PhantomData; - - #[test] - fn test_exclusive_system_params() { - #[derive(Resource, Default)] - struct Res { - test_value: u32, - } - - fn my_system(world: &mut World, mut local: Local, _phantom: PhantomData>) { - assert_eq!(world.resource::().test_value, *local); - *local += 1; - world.resource_mut::().test_value += 1; - } - - let mut schedule = Schedule::default(); - schedule.add_systems(my_system); - - let mut world = World::default(); - world.init_resource::(); - - schedule.run(&mut world); - schedule.run(&mut world); - - assert_eq!(2, world.get_resource::().unwrap().test_value); - } -} diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 0f3950d1d4ba6..1282e81d03540 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -5,8 +5,8 @@ use crate::{ query::{Access, FilteredAccessSet}, schedule::{InternedSystemSet, SystemSet}, system::{ - check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam, - SystemParamItem, + assert_valid_world_access_level, check_system_change_tick, ReadOnlySystemParam, System, + SystemIn, SystemInput, SystemParam, SystemParamItem, WorldAccessLevel, }, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId}, }; @@ -425,6 +425,7 @@ impl SystemState { /// `new` does not cache any of the world's archetypes, so you must call [`SystemState::update_archetypes`] /// manually before calling `get_manual{_mut}`. pub fn new(world: &mut World) -> Self { + assert_valid_world_access_level::(); let mut meta = SystemMeta::new::(); meta.last_run = world.change_tick().relative_to(Tick::MAX); let param_state = Param::init_state(world, &mut meta); @@ -751,6 +752,7 @@ where { type System = FunctionSystem; fn into_system(func: Self) -> Self::System { + assert_valid_world_access_level::(); FunctionSystem { func, state: None, @@ -802,7 +804,7 @@ where #[inline] fn is_exclusive(&self) -> bool { - false + F::Param::world_access_level() == WorldAccessLevel::Exclusive } #[inline] diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index d5cbd5ffb1571..66d2099af788c 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -123,8 +123,6 @@ mod adapter_system; mod builder; mod combinator; mod commands; -mod exclusive_function_system; -mod exclusive_system_param; mod function_system; mod input; mod observer_system; @@ -141,8 +139,6 @@ pub use adapter_system::*; pub use builder::*; pub use combinator::*; pub use commands::*; -pub use exclusive_function_system::*; -pub use exclusive_system_param::*; pub use function_system::*; pub use input::*; pub use observer_system::*; diff --git a/crates/bevy_ecs/src/system/system_name.rs b/crates/bevy_ecs/src/system/system_name.rs index b28ddd89f6658..64a99877e33eb 100644 --- a/crates/bevy_ecs/src/system/system_name.rs +++ b/crates/bevy_ecs/src/system/system_name.rs @@ -1,13 +1,15 @@ use crate::{ component::Tick, prelude::World, - system::{ExclusiveSystemParam, ReadOnlySystemParam, SystemMeta, SystemParam}, + system::{ReadOnlySystemParam, SystemMeta, SystemParam}, world::unsafe_world_cell::UnsafeWorldCell, }; use alloc::borrow::Cow; use core::ops::Deref; use derive_more::derive::{AsRef, Display, Into}; +use super::WorldAccessLevel; + /// [`SystemParam`] that returns the name of the system which it is used in. /// /// This is not a reliable identifier, it is more so useful for debugging or logging. @@ -70,24 +72,15 @@ unsafe impl SystemParam for SystemName<'_> { ) -> Self::Item<'w, 's> { SystemName(name) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } } // SAFETY: Only reads internal system state unsafe impl<'s> ReadOnlySystemParam for SystemName<'s> {} -impl ExclusiveSystemParam for SystemName<'_> { - type State = Cow<'static, str>; - type Item<'s> = SystemName<'s>; - - fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - system_meta.name.clone() - } - - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - SystemName(state) - } -} - #[cfg(test)] mod tests { use crate::{ diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 5859b48346d89..231180b288edc 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -11,7 +11,7 @@ use crate::{ }, resource::Resource, storage::ResourceData, - system::{Query, Single, SystemMeta}, + system::{Query, Single, SystemMeta, SystemState}, world::{ unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, FromWorld, World, @@ -287,6 +287,10 @@ pub unsafe trait SystemParam: Sized { world: UnsafeWorldCell<'world>, change_tick: Tick, ) -> Self::Item<'world, 'state>; + + /// Returns the access level required by this [`SystemParam`] to access the + /// [`World`] via [`SystemParam::get_param`]. + fn world_access_level() -> WorldAccessLevel; } /// A [`SystemParam`] that only reads a given [`World`]. @@ -298,6 +302,31 @@ pub unsafe trait ReadOnlySystemParam: SystemParam {} /// Shorthand way of accessing the associated type [`SystemParam::Item`] for a given [`SystemParam`]. pub type SystemParamItem<'w, 's, P> =

::Item<'w, 's>; +/// The level of access a [`SystemParam`] needs from the [`World`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum WorldAccessLevel { + /// The [`SystemParam`] does not need access to the [`World`] beyond + /// [`SystemParam::init_state`]. + None, + /// The [`SystemParam`] needs access to the [`World`] for + /// [`SystemParam::get_param`] and registers its required access to the + /// [`SystemMeta`]. All other [`SystemParam`]s must be [`WorldAccessLevel::Shared`] + /// and register their access to the [`SystemMeta`] accordingly, or + /// [`WorldAccessLevel::None`]. + Shared, + /// The [`SystemParam`] needs exclusive access to the [`World`] for + /// [`SystemParam::get_param`] which requires all other [`SystemParam`]s to + /// be [`WorldAccessLevel::None`]. + Exclusive, +} + +/// Asserts that the given [`SystemParam`] has a valid [`WorldAccessLevel`]. +pub fn assert_valid_world_access_level() { + // This will panic on params/tuples whose fields/members have conflicting + // access levels. + let _ = P::world_access_level(); +} + // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam for Query<'w, 's, D, F> @@ -336,6 +365,10 @@ unsafe impl SystemParam for Qu // world data that the query needs. unsafe { Query::new(world, state, system_meta.last_run, change_tick) } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } pub(crate) fn init_query_param( @@ -436,6 +469,10 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo } is_valid } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If @@ -502,6 +539,10 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam } is_valid } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. @@ -563,6 +604,10 @@ unsafe impl SystemParam state.is_empty_unsafe_world_cell(world, system_meta.last_run, world.change_tick()) } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: QueryState is constrained to read-only fetches, so it only reads World. @@ -774,6 +819,27 @@ macro_rules! impl_param_set { change_tick, } } + + fn world_access_level() -> WorldAccessLevel { + let mut shared = false; + $( + match $param::world_access_level() { + WorldAccessLevel::Exclusive => { + return WorldAccessLevel::Exclusive; + } + WorldAccessLevel::Shared => { + shared = true; + } + WorldAccessLevel::None => {} + } + )* + + if shared { + WorldAccessLevel::Shared + } else { + WorldAccessLevel::None + } + } } impl<'w, 's, $($param: SystemParam,)*> ParamSet<'w, 's, ($($param,)*)> @@ -875,6 +941,10 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { changed_by: _caller.deref(), } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Only reads a single World resource @@ -910,6 +980,10 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { changed_by: _caller.deref(), }) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res @@ -988,6 +1062,10 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { changed_by: value.changed_by, } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access. @@ -1020,9 +1098,13 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { changed_by: value.changed_by, }) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } -/// SAFETY: only reads world +// SAFETY: only reads world unsafe impl<'w> ReadOnlySystemParam for &'w World {} // SAFETY: `read_all` access is set and conflicts result in a panic @@ -1042,7 +1124,6 @@ unsafe impl SystemParam for &'_ World { system_meta.archetype_component_access.extend(&access); let mut filtered_access = FilteredAccess::default(); - filtered_access.read_all(); if !system_meta .component_access_set @@ -1064,9 +1145,13 @@ unsafe impl SystemParam for &'_ World { // SAFETY: Read-only access to the entire world was registered in `init_state`. unsafe { world.world() } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } -/// SAFETY: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references. +// SAFETY: `DeferredWorld` registers exclusive access to the entire world. unsafe impl<'w> SystemParam for DeferredWorld<'w> { type State = (); type Item<'world, 'state> = DeferredWorld<'world>; @@ -1092,6 +1177,122 @@ unsafe impl<'w> SystemParam for DeferredWorld<'w> { ) -> Self::Item<'world, 'state> { world.into_deferred() } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Exclusive + } +} + +const MUT_WORLD_ERROR: &str = "&mut World requires exclusive access to the \ + entire world, but a previous system parameter already registered access to \ + a part of it. Allowing this would break Rust's mutability rules"; + +// SAFETY: `&mut World` registers exclusive access to the entire world. +unsafe impl SystemParam for &mut World { + type State = (); + type Item<'w, 's> = &'w mut World; + + fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + let mut access = Access::default(); + access.read_all(); + access.write_all(); + if !system_meta + .archetype_component_access + .is_compatible(&access) + { + panic!("{}", MUT_WORLD_ERROR); + } + system_meta.archetype_component_access.extend(&access); + + let mut filtered_access = FilteredAccess::default(); + filtered_access.read_all(); + filtered_access.write_all(); + if !system_meta + .component_access_set + .get_conflicts_single(&filtered_access) + .is_empty() + { + panic!("{}", MUT_WORLD_ERROR); + } + system_meta.component_access_set.add(filtered_access); + + system_meta.set_has_deferred(); + system_meta.set_non_send(); + } + + unsafe fn get_param<'world, 'state>( + _state: &'state mut Self::State, + _system_meta: &SystemMeta, + world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + // SAFETY: Exclusive access to the entire world was registered in `init_state`. + unsafe { world.world_mut() } + } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Exclusive + } +} + +// SAFETY: `&mut QueryState` only accesses internal state +unsafe impl SystemParam + for &mut QueryState +{ + type State = QueryState; + type Item<'w, 's> = &'s mut QueryState; + + fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + QueryState::new(world) + } + + unsafe fn new_archetype( + state: &mut Self::State, + archetype: &Archetype, + system_meta: &mut SystemMeta, + ) { + state.new_archetype(archetype, &mut system_meta.archetype_component_access); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + _system_meta: &SystemMeta, + _world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + state + } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } +} + +// SAFETY: `&mut SystemState` only accesses internal state +unsafe impl SystemParam for &mut SystemState

{ + type State = SystemState

; + type Item<'w, 's> = &'s mut SystemState

; + + fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + SystemState::new(world) + } + + fn apply(state: &mut Self::State, _system_meta: &SystemMeta, world: &mut World) { + state.apply(world); + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + _system_meta: &SystemMeta, + _world: UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + state + } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } } /// A system local [`SystemParam`]. @@ -1205,6 +1406,10 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { ) -> Self::Item<'w, 's> { Local(state.get()) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } } /// Types that can be used with [`Deferred`] in systems. @@ -1394,6 +1599,10 @@ unsafe impl SystemParam for Deferred<'_, T> { ) -> Self::Item<'w, 's> { Deferred(state.get()) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } } /// Shared borrow of a non-[`Send`] resource. @@ -1543,6 +1752,10 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { changed_by: _caller.deref(), } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Only reads a single World non-send resource @@ -1575,6 +1788,10 @@ unsafe impl SystemParam for Option> { changed_by: _caller.deref(), }) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this @@ -1651,6 +1868,10 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { changed_by: _caller.deref_mut(), } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access. @@ -1678,6 +1899,10 @@ unsafe impl<'a, T: 'static> SystemParam for Option> { changed_by: _caller.deref_mut(), }) } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Only reads World archetypes @@ -1699,6 +1924,10 @@ unsafe impl<'a> SystemParam for &'a Archetypes { ) -> Self::Item<'w, 's> { world.archetypes() } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Only reads World components @@ -1720,6 +1949,10 @@ unsafe impl<'a> SystemParam for &'a Components { ) -> Self::Item<'w, 's> { world.components() } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Only reads World entities @@ -1741,6 +1974,10 @@ unsafe impl<'a> SystemParam for &'a Entities { ) -> Self::Item<'w, 's> { world.entities() } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: Only reads World bundles @@ -1762,6 +1999,10 @@ unsafe impl<'a> SystemParam for &'a Bundles { ) -> Self::Item<'w, 's> { world.bundles() } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } /// A [`SystemParam`] that reads the previous and current change ticks of the system. @@ -1815,6 +2056,10 @@ unsafe impl SystemParam for SystemChangeTick { this_run: change_tick, } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } } // SAFETY: When initialized with `init_state`, `get_param` returns an empty `Vec` and does no access. @@ -1878,6 +2123,10 @@ unsafe impl SystemParam for Vec { T::queue(state, system_meta, world.reborrow()); } } + + fn world_access_level() -> WorldAccessLevel { + T::world_access_level() + } } // SAFETY: When initialized with `init_state`, `get_param` returns an empty `Vec` and does no access. @@ -1929,6 +2178,10 @@ unsafe impl SystemParam for ParamSet<'_, '_, Vec> { T::queue(state, system_meta, world.reborrow()); } } + + fn world_access_level() -> WorldAccessLevel { + T::world_access_level() + } } impl ParamSet<'_, '_, Vec> { @@ -2040,6 +2293,42 @@ macro_rules! impl_system_param_tuple { )] ($($param::get_param($param, system_meta, world, change_tick),)*) } + + fn world_access_level() -> WorldAccessLevel { + #[allow(unused_mut, reason = "tuple might be zero-length")] + let mut exclusive = false; + #[allow(unused_mut, reason = "tuple might be zero-length")] + let mut shared = false; + $( + match $param::world_access_level() { + WorldAccessLevel::Exclusive => { + if shared || exclusive { + let type_name = core::any::type_name::<$param>(); + panic!("error[B0002]: {type_name} conflicts with a previous system parameter. {type_name} is exclusive, and so cannot be combined with any system parameter that accesses the World."); + } else { + exclusive = true; + } + } + WorldAccessLevel::Shared => { + if exclusive { + let type_name = core::any::type_name::<$param>(); + panic!("error[B0002]: {type_name} conflicts with a previous system parameter which is exclusive. {type_name} is shared, and so cannot be combined with any system parameter that accesses the World exclusively."); + } else { + shared = true; + } + } + WorldAccessLevel::None => {} + } + )* + + if exclusive { + WorldAccessLevel::Exclusive + } else if shared { + WorldAccessLevel::Shared + } else { + WorldAccessLevel::None + } + } } }; } @@ -2204,6 +2493,10 @@ unsafe impl SystemParam for StaticSystemParam<'_, '_, // SAFETY: Defer to the safety of P::SystemParam StaticSystemParam(unsafe { P::get_param(state, system_meta, world, change_tick) }) } + + fn world_access_level() -> WorldAccessLevel { + P::world_access_level() + } } // SAFETY: No world access. @@ -2222,6 +2515,10 @@ unsafe impl SystemParam for PhantomData { ) -> Self::Item<'world, 'state> { PhantomData } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None + } } // SAFETY: No world access. @@ -2522,6 +2819,14 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> { fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) { state.0.queue(system_meta, world); } + + fn world_access_level() -> WorldAccessLevel { + // We don't allow users to create DynSystemParam's that hold exclusive + // params, so we can safely return Shared here. This is technically + // over-restrictive when storing `WorldAccessLevel::None` params, but we + // can't conditionally return based on the held state. + WorldAccessLevel::Shared + } } // SAFETY: When initialized with `init_state`, `get_param` returns a `FilteredResources` with no access. @@ -2546,6 +2851,10 @@ unsafe impl SystemParam for FilteredResources<'_, '_> { // and the builder registers `access` in `build`. unsafe { FilteredResources::new(world, state, system_meta.last_run, change_tick) } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } // SAFETY: FilteredResources only reads resources. @@ -2573,14 +2882,19 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> { // and the builder registers `access` in `build`. unsafe { FilteredResourcesMut::new(world, state, system_meta.last_run, change_tick) } } + + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::Shared + } } #[cfg(test)] mod tests { use super::*; use crate::{ - self as bevy_ecs, // Necessary for the `SystemParam` Derive when used inside `bevy_ecs`. - system::assert_is_system, + self as bevy_ecs, + prelude::Component, + system::{assert_is_system, IntoSystem, System}, }; use core::cell::RefCell; @@ -2811,4 +3125,156 @@ mod tests { let _query: Query<()> = p.downcast_mut_inner().unwrap(); let _query: Query<()> = p.downcast().unwrap(); } + + #[derive(Component, Resource)] + struct Foo; + + fn check_conflict(system: impl IntoSystem<(), (), M>) { + let mut world = World::new(); + let mut system = IntoSystem::into_system(system); + system.initialize(&mut world); + } + + #[test] + #[should_panic] + fn conflict_world_archetypes() { + fn system(_: &mut World, _: &Archetypes, _: &Components, _: &Entities, _: &Bundles) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_mut_world_multiple() { + fn system(_: &mut World, _: &mut World) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_mut_world_deferred_world() { + fn system(_: &mut World, _: DeferredWorld) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_mut_world_query() { + fn system(_: &mut World, _: Query<&Foo>) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_query_mut_world() { + fn system(_: Query<&Foo>, _: &mut World) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_mut_world_resource() { + fn system(_: &mut World, _: Res) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_resource_mut_world() { + fn system(_: Res, _: &mut World) {} + check_conflict(system); + } + + #[test] + fn no_conflict_mut_world_local() { + fn system(_: &mut World, _: Local) {} + check_conflict(system); + } + + #[test] + fn no_conflict_mut_world_query_state() { + fn system(_: &mut World, _: &mut QueryState<&Foo>) {} + check_conflict(system); + } + + #[test] + fn no_conflict_mut_world_system_state() { + fn system(_: &mut World, _: &mut SystemState>) {} + check_conflict(system); + } + + #[test] + fn no_conflict_mut_world_system_state_recursive() { + fn system(_: &mut World, _: &mut SystemState<&mut SystemState>>) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_deferred_world_multiple() { + fn system(_: DeferredWorld, _: DeferredWorld) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_deferred_world_query() { + fn system(_: DeferredWorld, _: Query<&Foo>) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_query_deferred_world() { + fn system(_: Query<&Foo>, _: DeferredWorld) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_deferred_world_resource() { + fn system(_: DeferredWorld, _: Res) {} + check_conflict(system); + } + + #[test] + #[should_panic] + fn conflict_resource_deferred_world() { + fn system(_: Res, _: DeferredWorld) {} + check_conflict(system); + } + + #[test] + fn no_conflict_deferred_world_local() { + fn system(_: DeferredWorld, _: Local) {} + check_conflict(system); + } + + #[test] + fn no_conflict_deferred_world_query_state() { + fn system(_: DeferredWorld, _: &mut QueryState<&Foo>) {} + check_conflict(system); + } + + #[test] + fn no_conflict_deferred_world_system_state() { + fn system(_: DeferredWorld, _: &mut SystemState>) {} + check_conflict(system); + } + + #[test] + fn param_set_no_conflicts() { + fn system(_: ParamSet<(&mut World, DeferredWorld, &World, Query<&Foo>)>) {} + check_conflict(system); + } + + #[test] + fn derived_deferred_world_system_state() { + #[derive(SystemParam)] + struct Test<'w, 's> { + _dw: DeferredWorld<'w>, + _ss: &'s mut SystemState>, + } + fn system(_: Test) {} + check_conflict(system); + } } diff --git a/crates/bevy_ecs/src/world/identifier.rs b/crates/bevy_ecs/src/world/identifier.rs index 221ddd8210919..dcd5308aa2880 100644 --- a/crates/bevy_ecs/src/world/identifier.rs +++ b/crates/bevy_ecs/src/world/identifier.rs @@ -1,7 +1,7 @@ use crate::{ component::Tick, storage::SparseSetIndex, - system::{ExclusiveSystemParam, ReadOnlySystemParam, SystemMeta, SystemParam}, + system::{ReadOnlySystemParam, SystemMeta, SystemParam, WorldAccessLevel}, world::{FromWorld, World}, }; use bevy_platform_support::sync::atomic::{AtomicUsize, Ordering}; @@ -64,18 +64,9 @@ unsafe impl SystemParam for WorldId { ) -> Self::Item<'world, 'state> { world.id() } -} - -impl ExclusiveSystemParam for WorldId { - type State = WorldId; - type Item<'s> = WorldId; - - fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { - world.id() - } - fn get_param<'s>(state: &'s mut Self::State, _system_meta: &SystemMeta) -> Self::Item<'s> { - *state + fn world_access_level() -> WorldAccessLevel { + WorldAccessLevel::None } } diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index b1fe363f510cb..c09b65012d9b3 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -11,7 +11,9 @@ use bevy_color::{Color, LinearRgba}; use bevy_ecs::{ component::Tick, resource::Resource, - system::{Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam}, + system::{ + Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam, WorldAccessLevel, + }, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3}; @@ -256,6 +258,10 @@ where config_ext, } } + + fn world_access_level() -> WorldAccessLevel { + GizmosState::::world_access_level() + } } #[expect( diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index 6ac7079bc5f49..d647fd6bf8030 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -2,7 +2,10 @@ use crate::MainWorld; use bevy_ecs::{ component::Tick, prelude::*, - system::{ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState}, + system::{ + ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState, + WorldAccessLevel, + }, world::unsafe_world_cell::UnsafeWorldCell, }; use core::ops::{Deref, DerefMut}; @@ -120,6 +123,10 @@ where let item = state.state.get(main_world.into_inner()); Extract { item } } + + fn world_access_level() -> WorldAccessLevel { + P::world_access_level() + } } impl<'w, 's, P> Deref for Extract<'w, 's, P>