diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 418c5741e75e2..23414662c3942 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -220,7 +220,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { } unsafe fn init_fetch<'__w>( - _world: &'__w #path::world::World, + _world: #path::world::unsafe_world_cell::UnsafeWorldCell<'__w>, state: &Self::State, _last_change_tick: u32, _change_tick: u32 diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 7196e8c9d255c..e46547dd269c0 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -294,7 +294,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { ParamSet { @@ -498,7 +498,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { unsafe fn get_param<'w2, 's2>( state: &'s2 mut Self::State, system_meta: &#path::system::SystemMeta, - world: &'w2 #path::world::World, + world: #path::world::unsafe_world_cell::UnsafeWorldCell<'w2>, change_tick: u32, ) -> Self::Item<'w2, 's2> { let (#(#tuple_patterns,)*) = < diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 5733ffd262829..0c345efa9f33c 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -5,7 +5,7 @@ use crate::{ entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess}, storage::{ComponentSparseSet, Table, TableRow}, - world::{Mut, Ref, World}, + world::{unsafe_world_cell::UnsafeWorldCell, Mut, Ref, World}, }; use bevy_ecs_macros::all_tuples; pub use bevy_ecs_macros::WorldQuery; @@ -328,10 +328,11 @@ pub unsafe trait WorldQuery { /// /// # Safety /// - /// `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed + /// - `state` must have been initialized (via [`WorldQuery::init_state`]) using the same `world` passed /// in to this function. + /// - `world` must be an `UnsafeWorldCell` with access to everything listed in `update_archetype_component_access` unsafe fn init_fetch<'w>( - world: &'w World, + world: UnsafeWorldCell<'w>, state: &Self::State, last_change_tick: u32, change_tick: u32, @@ -462,7 +463,7 @@ unsafe impl WorldQuery for Entity { const IS_ARCHETYPAL: bool = true; unsafe fn init_fetch<'w>( - _world: &'w World, + _world: UnsafeWorldCell<'w>, _state: &Self::State, _last_change_tick: u32, _change_tick: u32, @@ -544,7 +545,7 @@ unsafe impl WorldQuery for &T { const IS_ARCHETYPAL: bool = true; unsafe fn init_fetch<'w>( - world: &'w World, + world: UnsafeWorldCell<'w>, &component_id: &ComponentId, _last_change_tick: u32, _change_tick: u32, @@ -552,11 +553,15 @@ unsafe impl WorldQuery for &T { ReadFetch { table_components: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(component_id) - .debug_checked_unwrap() + // SAFETY: TODO + unsafe { + world + .world() + .storages() + .sparse_sets + .get(component_id) + .debug_checked_unwrap() + } }), } } @@ -689,7 +694,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { const IS_ARCHETYPAL: bool = true; unsafe fn init_fetch<'w>( - world: &'w World, + world: UnsafeWorldCell<'w>, &component_id: &ComponentId, last_change_tick: u32, change_tick: u32, @@ -697,11 +702,15 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { RefFetch { table_data: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(component_id) - .debug_checked_unwrap() + // SAFETY: world has access to + unsafe { + world + .world() + .storages() + .sparse_sets + .get(component_id) + .debug_checked_unwrap() + } }), last_change_tick, change_tick, @@ -850,7 +859,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { const IS_ARCHETYPAL: bool = true; unsafe fn init_fetch<'w>( - world: &'w World, + world: UnsafeWorldCell<'w>, &component_id: &ComponentId, last_change_tick: u32, change_tick: u32, @@ -859,6 +868,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { table_data: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world + .world() .storages() .sparse_sets .get(component_id) @@ -998,7 +1008,7 @@ unsafe impl WorldQuery for Option { const IS_ARCHETYPAL: bool = T::IS_ARCHETYPAL; unsafe fn init_fetch<'w>( - world: &'w World, + world: UnsafeWorldCell<'w>, state: &T::State, last_change_tick: u32, change_tick: u32, @@ -1192,7 +1202,7 @@ unsafe impl WorldQuery for ChangeTrackers { const IS_ARCHETYPAL: bool = true; unsafe fn init_fetch<'w>( - world: &'w World, + world: UnsafeWorldCell<'w>, &component_id: &ComponentId, last_change_tick: u32, change_tick: u32, @@ -1339,7 +1349,7 @@ macro_rules! impl_tuple_fetch { } #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { let ($($name,)*) = state; ($($name::init_fetch(_world, $name, _last_change_tick, _change_tick),)*) } @@ -1448,7 +1458,7 @@ macro_rules! impl_anytuple_fetch { } #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(_world: UnsafeWorldCell<'w>, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> { let ($($name,)*) = state; ($(($name::init_fetch(_world, $name, _last_change_tick, _change_tick), false),)*) } @@ -1587,7 +1597,7 @@ unsafe impl WorldQuery for NopWorldQuery { #[inline(always)] unsafe fn init_fetch( - _world: &World, + _world: UnsafeWorldCell<'_>, _state: &Q::State, _last_change_tick: u32, _change_tick: u32, diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index a067acbd89932..1423643e8a756 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -4,7 +4,7 @@ use crate::{ entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{Column, ComponentSparseSet, Table, TableRow}, - world::World, + world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_ecs_macros::all_tuples; use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; @@ -51,7 +51,7 @@ unsafe impl WorldQuery for With { fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {} unsafe fn init_fetch( - _world: &World, + _world: UnsafeWorldCell<'_>, _state: &ComponentId, _last_change_tick: u32, _change_tick: u32, @@ -153,7 +153,7 @@ unsafe impl WorldQuery for Without { fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {} unsafe fn init_fetch( - _world: &World, + _world: UnsafeWorldCell<'_>, _state: &ComponentId, _last_change_tick: u32, _change_tick: u32, @@ -277,7 +277,7 @@ macro_rules! impl_query_filter_tuple { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; - unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, state: &Self::State, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> { let ($($filter,)*) = state; ($(OrFetch { fetch: $filter::init_fetch(world, $filter, last_change_tick, change_tick), @@ -432,7 +432,7 @@ macro_rules! impl_tick_filter { item } - unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> { + unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, &id: &ComponentId, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> { Self::Fetch::<'w> { table_ticks: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 138235b836e19..b962559bc22fe 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,9 +1,9 @@ use crate::{ archetype::{ArchetypeEntity, ArchetypeId, Archetypes}, entity::{Entities, Entity}, - prelude::World, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery}, storage::{TableId, TableRow, Tables}, + world::unsafe_world_cell::UnsafeWorldCell, }; use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit}; @@ -27,7 +27,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { /// This does not validate that `world.id()` matches `query_state.world_id`. Calling this on a `world` /// with a mismatched [`WorldId`](crate::world::WorldId) is unsound. pub(crate) unsafe fn new( - world: &'w World, + world: UnsafeWorldCell<'w>, query_state: &'s QueryState, last_change_tick: u32, change_tick: u32, @@ -35,7 +35,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { QueryIter { query_state, tables: &world.storages().tables, - archetypes: &world.archetypes, + archetypes: world.archetypes(), cursor: QueryIterationCursor::init(world, query_state, last_change_tick, change_tick), } } @@ -95,7 +95,7 @@ where /// This does not validate that `world.id()` matches `query_state.world_id`. Calling this on a `world` /// with a mismatched [`WorldId`](crate::world::WorldId) is unsound. pub(crate) unsafe fn new>( - world: &'w World, + world: UnsafeWorldCell<'w>, query_state: &'s QueryState, entity_list: EntityList, last_change_tick: u32, @@ -115,9 +115,9 @@ where ); QueryManyIter { query_state, - entities: &world.entities, - archetypes: &world.archetypes, - tables: &world.storages.tables, + entities: world.entities(), + archetypes: world.archetypes(), + tables: &world.storages().tables, fetch, filter, entity_iter: entity_list.into_iter(), @@ -296,7 +296,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize> /// This does not validate that `world.id()` matches `query_state.world_id`. Calling this on a /// `world` with a mismatched [`WorldId`](crate::world::WorldId) is unsound. pub(crate) unsafe fn new( - world: &'w World, + world: UnsafeWorldCell<'w>, query_state: &'s QueryState, last_change_tick: u32, change_tick: u32, @@ -328,7 +328,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize> QueryCombinationIter { query_state, tables: &world.storages().tables, - archetypes: &world.archetypes, + archetypes: world.archetypes(), cursors: array.assume_init(), } } @@ -494,7 +494,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, const IS_DENSE: bool = Q::IS_DENSE && F::IS_DENSE; unsafe fn init_empty( - world: &'w World, + world: UnsafeWorldCell<'w>, query_state: &'s QueryState, last_change_tick: u32, change_tick: u32, @@ -507,7 +507,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, } unsafe fn init( - world: &'w World, + world: UnsafeWorldCell<'w>, query_state: &'s QueryState, last_change_tick: u32, change_tick: u32, diff --git a/crates/bevy_ecs/src/query/par_iter.rs b/crates/bevy_ecs/src/query/par_iter.rs index fd53fb71a2b9d..8322e82692d80 100644 --- a/crates/bevy_ecs/src/query/par_iter.rs +++ b/crates/bevy_ecs/src/query/par_iter.rs @@ -1,4 +1,4 @@ -use crate::world::World; +use crate::world::unsafe_world_cell::UnsafeWorldCell; use bevy_tasks::ComputeTaskPool; use std::ops::Range; @@ -79,7 +79,7 @@ impl BatchingStrategy { /// This struct is created by the [`Query::par_iter`](crate::system::Query::iter) and /// [`Query::par_iter_mut`](crate::system::Query::iter_mut) methods. pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> { - pub(crate) world: &'w World, + pub(crate) world: UnsafeWorldCell<'w>, pub(crate) state: &'s QueryState, pub(crate) batching_strategy: BatchingStrategy, } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index b7b163a2f5340..d9e1805c3e651 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -8,7 +8,7 @@ use crate::{ QueryIter, QueryParIter, WorldQuery, }, storage::{TableId, TableRow}, - world::{World, WorldId}, + world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; use bevy_tasks::ComputeTaskPool; #[cfg(feature = "trace")] @@ -134,7 +134,11 @@ impl QueryState { // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access unsafe { self.as_nop() - .iter_unchecked_manual(world, last_change_tick, change_tick) + .iter_unchecked_manual( + world.as_unsafe_world_cell_readonly(), + last_change_tick, + change_tick, + ) .next() .is_none() } @@ -158,8 +162,18 @@ impl QueryState { } } + fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell<'_>) { + // SAFETY: update_archetypes does not access any resources or components + let world = unsafe { world.world() }; + self.update_archetypes(world); + } + #[inline] pub fn validate_world(&self, world: &World) { + self.validate_world_unsafe_world_cell(world.as_unsafe_world_cell_readonly()) + } + #[inline] + pub fn validate_world_unsafe_world_cell(&self, world: UnsafeWorldCell<'_>) { assert!( world.id() == self.world_id, "Attempted to use {} with a mismatched World. QueryStates can only be used with the World they were created from.", @@ -211,7 +225,7 @@ impl QueryState { // SAFETY: query is read only unsafe { self.as_readonly().get_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), entity, world.last_change_tick(), world.read_change_tick(), @@ -279,8 +293,16 @@ impl QueryState { ) -> Result, QueryEntityError> { self.update_archetypes(world); let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: query has unique world access - unsafe { self.get_unchecked_manual(world, entity, world.last_change_tick(), change_tick) } + unsafe { + self.get_unchecked_manual( + world.as_unsafe_world_cell(), + entity, + last_change_tick, + change_tick, + ) + } } /// Returns the query results for the given array of [`Entity`]. @@ -330,10 +352,16 @@ impl QueryState { self.update_archetypes(world); let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: method requires exclusive world access // and world has been validated via update_archetypes unsafe { - self.get_many_unchecked_manual(world, entities, world.last_change_tick(), change_tick) + self.get_many_unchecked_manual( + world.as_unsafe_world_cell(), + entities, + last_change_tick, + change_tick, + ) } } @@ -347,7 +375,7 @@ impl QueryState { // SAFETY: query is read only and world is validated unsafe { self.as_readonly().get_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), entity, world.last_change_tick(), world.read_change_tick(), @@ -369,7 +397,7 @@ impl QueryState { ) -> Result, QueryEntityError> { self.update_archetypes(world); self.get_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), entity, world.last_change_tick(), world.read_change_tick(), @@ -388,13 +416,13 @@ impl QueryState { /// use `QueryState::validate_world` to verify this. pub(crate) unsafe fn get_unchecked_manual<'w>( &self, - world: &'w World, + world: UnsafeWorldCell<'w>, entity: Entity, last_change_tick: u32, change_tick: u32, ) -> Result, QueryEntityError> { let location = world - .entities + .entities() .get(entity) .ok_or(QueryEntityError::NoSuchEntity(entity))?; if !self @@ -404,7 +432,7 @@ impl QueryState { return Err(QueryEntityError::QueryDoesNotMatch(entity)); } let archetype = world - .archetypes + .archetypes() .get(location.archetype_id) .debug_checked_unwrap(); let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick); @@ -445,7 +473,7 @@ impl QueryState { // SAFETY: fetch is read-only // and world must be validated let item = self.as_readonly().get_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), entity, last_change_tick, change_tick, @@ -469,7 +497,7 @@ impl QueryState { /// use `QueryState::validate_world` to verify this. pub(crate) unsafe fn get_many_unchecked_manual<'w, const N: usize>( &self, - world: &'w World, + world: UnsafeWorldCell<'w>, entities: [Entity; N], last_change_tick: u32, change_tick: u32, @@ -506,7 +534,7 @@ impl QueryState { unsafe { self.update_archetypes(world); self.as_readonly().iter_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), world.last_change_tick(), world.read_change_tick(), ) @@ -517,10 +545,11 @@ impl QueryState { #[inline] pub fn iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryIter<'w, 's, Q, F> { let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); - self.iter_unchecked_manual(world, world.last_change_tick(), change_tick) + self.iter_unchecked_manual(world.as_unsafe_world_cell(), last_change_tick, change_tick) } } @@ -537,7 +566,7 @@ impl QueryState { // SAFETY: query is read only and world is validated unsafe { self.as_readonly().iter_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), world.last_change_tick(), world.read_change_tick(), ) @@ -574,7 +603,7 @@ impl QueryState { unsafe { self.update_archetypes(world); self.as_readonly().iter_combinations_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), world.last_change_tick(), world.read_change_tick(), ) @@ -604,10 +633,15 @@ impl QueryState { world: &'w mut World, ) -> QueryCombinationIter<'w, 's, Q, F, K> { let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); - self.iter_combinations_unchecked_manual(world, world.last_change_tick(), change_tick) + self.iter_combinations_unchecked_manual( + world.as_unsafe_world_cell(), + last_change_tick, + change_tick, + ) } } @@ -633,7 +667,7 @@ impl QueryState { unsafe { self.as_readonly().iter_many_unchecked_manual( entities, - world, + world.as_unsafe_world_cell_readonly(), world.last_change_tick(), world.read_change_tick(), ) @@ -655,9 +689,15 @@ impl QueryState { { self.update_archetypes(world); let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: Query has unique world access. unsafe { - self.iter_many_unchecked_manual(entities, world, world.last_change_tick(), change_tick) + self.iter_many_unchecked_manual( + entities, + world.as_unsafe_world_cell(), + last_change_tick, + change_tick, + ) } } @@ -670,9 +710,9 @@ impl QueryState { #[inline] pub unsafe fn iter_unchecked<'w, 's>( &'s mut self, - world: &'w World, + world: UnsafeWorldCell<'w>, ) -> QueryIter<'w, 's, Q, F> { - self.update_archetypes(world); + self.update_archetypes_unsafe_world_cell(world); self.iter_unchecked_manual(world, world.last_change_tick(), world.read_change_tick()) } @@ -687,9 +727,9 @@ impl QueryState { #[inline] pub unsafe fn iter_combinations_unchecked<'w, 's, const K: usize>( &'s mut self, - world: &'w World, + world: UnsafeWorldCell<'w>, ) -> QueryCombinationIter<'w, 's, Q, F, K> { - self.update_archetypes(world); + self.update_archetypes_unsafe_world_cell(world); self.iter_combinations_unchecked_manual( world, world.last_change_tick(), @@ -709,7 +749,7 @@ impl QueryState { #[inline] pub(crate) unsafe fn iter_unchecked_manual<'w, 's>( &'s self, - world: &'w World, + world: UnsafeWorldCell<'w>, last_change_tick: u32, change_tick: u32, ) -> QueryIter<'w, 's, Q, F> { @@ -730,7 +770,7 @@ impl QueryState { pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList: IntoIterator>( &'s self, entities: EntityList, - world: &'w World, + world: UnsafeWorldCell<'w>, last_change_tick: u32, change_tick: u32, ) -> QueryManyIter<'w, 's, Q, F, EntityList::IntoIter> @@ -753,7 +793,7 @@ impl QueryState { #[inline] pub(crate) unsafe fn iter_combinations_unchecked_manual<'w, 's, const K: usize>( &'s self, - world: &'w World, + world: UnsafeWorldCell<'w>, last_change_tick: u32, change_tick: u32, ) -> QueryCombinationIter<'w, 's, Q, F, K> { @@ -770,7 +810,7 @@ impl QueryState { unsafe { self.update_archetypes(world); self.as_readonly().for_each_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), func, world.last_change_tick(), world.read_change_tick(), @@ -783,10 +823,16 @@ impl QueryState { #[inline] pub fn for_each_mut<'w, FN: FnMut(Q::Item<'w>)>(&mut self, world: &'w mut World, func: FN) { let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: query has unique world access unsafe { self.update_archetypes(world); - self.for_each_unchecked_manual(world, func, world.last_change_tick(), change_tick); + self.for_each_unchecked_manual( + world.as_unsafe_world_cell(), + func, + last_change_tick, + change_tick, + ); } } @@ -802,10 +848,10 @@ impl QueryState { #[inline] pub unsafe fn for_each_unchecked<'w, FN: FnMut(Q::Item<'w>)>( &mut self, - world: &'w World, + world: UnsafeWorldCell<'w>, func: FN, ) { - self.update_archetypes(world); + self.update_archetypes_unsafe_world_cell(world); self.for_each_unchecked_manual( world, func, @@ -823,7 +869,7 @@ impl QueryState { pub fn par_iter<'w, 's>(&'s mut self, world: &'w World) -> QueryParIter<'w, 's, Q, F> { self.update_archetypes(world); QueryParIter { - world, + world: world.as_unsafe_world_cell_readonly(), state: self, batching_strategy: BatchingStrategy::new(), } @@ -838,7 +884,7 @@ impl QueryState { pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, Q, F> { self.update_archetypes(world); QueryParIter { - world, + world: world.as_unsafe_world_cell(), state: self, batching_strategy: BatchingStrategy::new(), } @@ -856,7 +902,7 @@ impl QueryState { /// with a mismatched [`WorldId`] is unsound. pub(crate) unsafe fn for_each_unchecked_manual<'w, FN: FnMut(Q::Item<'w>)>( &self, - world: &'w World, + world: UnsafeWorldCell<'w>, mut func: FN, last_change_tick: u32, change_tick: u32, @@ -884,7 +930,7 @@ impl QueryState { } } } else { - let archetypes = &world.archetypes; + let archetypes = world.archetypes(); for archetype_id in &self.matched_archetype_ids { let archetype = archetypes.get(*archetype_id).debug_checked_unwrap(); let table = tables.get(archetype.table_id()).debug_checked_unwrap(); @@ -930,7 +976,7 @@ impl QueryState { FN: Fn(Q::Item<'w>) + Send + Sync + Clone, >( &self, - world: &'w World, + world: UnsafeWorldCell<'w>, batch_size: usize, func: FN, last_change_tick: u32, @@ -992,7 +1038,7 @@ impl QueryState { } } } else { - let archetypes = &world.archetypes; + let archetypes = world.archetypes(); for archetype_id in &self.matched_archetype_ids { let mut offset = 0; let archetype = &archetypes[*archetype_id]; @@ -1018,7 +1064,7 @@ impl QueryState { ); let tables = &world.storages().tables; let archetype = - world.archetypes.get(*archetype_id).debug_checked_unwrap(); + world.archetypes().get(*archetype_id).debug_checked_unwrap(); let table = tables.get(archetype.table_id()).debug_checked_unwrap(); Q::set_archetype(&mut fetch, &self.fetch_state, archetype, table); F::set_archetype(&mut filter, &self.filter_state, archetype, table); @@ -1093,7 +1139,7 @@ impl QueryState { // SAFETY: query is read only unsafe { self.as_readonly().get_single_unchecked_manual( - world, + world.as_unsafe_world_cell_readonly(), world.last_change_tick(), world.read_change_tick(), ) @@ -1127,8 +1173,15 @@ impl QueryState { self.update_archetypes(world); let change_tick = world.change_tick(); + let last_change_tick = world.last_change_tick(); // SAFETY: query has unique world access - unsafe { self.get_single_unchecked_manual(world, world.last_change_tick(), change_tick) } + unsafe { + self.get_single_unchecked_manual( + world.as_unsafe_world_cell(), + last_change_tick, + change_tick, + ) + } } /// Returns a query result when there is exactly one entity matching the query. @@ -1143,9 +1196,9 @@ impl QueryState { #[inline] pub unsafe fn get_single_unchecked<'w>( &mut self, - world: &'w World, + world: UnsafeWorldCell<'w>, ) -> Result, QuerySingleError> { - self.update_archetypes(world); + self.update_archetypes_unsafe_world_cell(world); self.get_single_unchecked_manual(world, world.last_change_tick(), world.read_change_tick()) } @@ -1162,7 +1215,7 @@ impl QueryState { #[inline] pub unsafe fn get_single_unchecked_manual<'w>( &self, - world: &'w World, + world: UnsafeWorldCell<'w>, last_change_tick: u32, change_tick: u32, ) -> Result, QuerySingleError> { @@ -1229,7 +1282,7 @@ mod tests { assert!(unsafe { query_state .get_many_unchecked_manual::<10>( - &world, + world.as_unsafe_world_cell(), entities.clone().try_into().unwrap(), last_change_tick, change_tick, @@ -1242,7 +1295,7 @@ mod tests { unsafe { query_state .get_many_unchecked_manual( - &world, + world.as_unsafe_world_cell(), [entities[0], entities[0]], last_change_tick, change_tick, @@ -1257,7 +1310,7 @@ mod tests { unsafe { query_state .get_many_unchecked_manual( - &world, + world.as_unsafe_world_cell(), [entities[0], entities[1], entities[0]], last_change_tick, change_tick, @@ -1272,7 +1325,7 @@ mod tests { unsafe { query_state .get_many_unchecked_manual( - &world, + world.as_unsafe_world_cell(), [entities[9], entities[9]], last_change_tick, change_tick, diff --git a/crates/bevy_ecs/src/removal_detection.rs b/crates/bevy_ecs/src/removal_detection.rs index f9953ff77cf9e..c5e0ee8006108 100644 --- a/crates/bevy_ecs/src/removal_detection.rs +++ b/crates/bevy_ecs/src/removal_detection.rs @@ -8,7 +8,7 @@ use crate::{ prelude::Local, storage::SparseSet, system::{ReadOnlySystemParam, SystemMeta, SystemParam}, - world::World, + world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use std::{ @@ -178,9 +178,10 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, _system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { - world.removed_components() + // SAFETY: only metadata is accessed + unsafe { world.world_metadata() }.removed_components() } } diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 16ab5f789675d..7a936c96743a7 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -18,7 +18,7 @@ use crate::{ is_apply_system_buffers, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule, }, system::BoxedSystem, - world::World, + world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use crate as bevy_ecs; @@ -281,6 +281,7 @@ impl MultiThreadedExecutor { // SAFETY: No exclusive system is running. // Therefore, there is no existing mutable reference to the world. let world = unsafe { &*cell.get() }; + if !self.can_run(system_index, system, conditions, world) { // NOTE: exclusive systems with ambiguities are susceptible to // being significantly displaced here (compared to single-threaded order) @@ -309,6 +310,9 @@ impl MultiThreadedExecutor { break; } + // TODO: do this earlier? + let world = world.as_unsafe_world_cell_migration_internal(); + // SAFETY: No other reference to this system exists. unsafe { self.spawn_system_task(scope, system_index, systems, world); @@ -425,7 +429,7 @@ impl MultiThreadedExecutor { scope: &Scope<'_, 'scope, ()>, system_index: usize, systems: &'scope [SyncUnsafeCell], - world: &'scope World, + world: UnsafeWorldCell<'scope>, ) { // SAFETY: this system is not running, no other reference exists let system = unsafe { &mut *systems[system_index].get() }; @@ -604,6 +608,7 @@ fn apply_system_buffers( } } +// TODO: this is unsafe fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World) -> bool { // not short-circuiting is intentional #[allow(clippy::unnecessary_fold)] @@ -613,7 +618,7 @@ fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &World #[cfg(feature = "trace")] let _condition_span = info_span!("condition", name = &*condition.name()).entered(); // SAFETY: caller ensures system access is compatible - unsafe { condition.run_unsafe((), world) } + unsafe { condition.run_unsafe((), world.as_unsafe_world_cell_readonly()) } }) .fold(true, |acc, res| acc && res) } diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 7853586bdfee6..d9c6393077fb4 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -7,7 +7,7 @@ use crate::{ check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, InputMarker, IntoSystem, System, SystemMeta, }, - world::{World, WorldId}, + world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; use bevy_ecs_macros::all_tuples; use std::{any::TypeId, borrow::Cow, marker::PhantomData}; @@ -95,7 +95,7 @@ where } #[inline] - unsafe fn run_unsafe(&mut self, _input: Self::In, _world: &World) -> Self::Out { + unsafe fn run_unsafe(&mut self, _input: Self::In, _world: UnsafeWorldCell<'_>) -> Self::Out { panic!("Cannot run exclusive systems with a shared World reference"); } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index a95a108bdc066..c9376d9260165 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -5,7 +5,7 @@ use crate::{ prelude::FromWorld, query::{Access, FilteredAccessSet}, system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem}, - world::{World, WorldId}, + world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId}, }; use bevy_ecs_macros::all_tuples; use std::{any::TypeId, borrow::Cow, marker::PhantomData}; @@ -174,7 +174,7 @@ impl SystemState { self.validate_world(world); self.update_archetypes(world); // SAFETY: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with. - unsafe { self.get_unchecked_manual(world) } + unsafe { self.get_unchecked_manual(world.as_unsafe_world_cell_readonly()) } } /// Retrieve the mutable [`SystemParam`] values. @@ -183,7 +183,7 @@ impl SystemState { self.validate_world(world); self.update_archetypes(world); // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with. - unsafe { self.get_unchecked_manual(world) } + unsafe { self.get_unchecked_manual(world.as_unsafe_world_cell()) } } /// Applies all state queued up for [`SystemParam`] values. For example, this will apply commands queued up @@ -243,7 +243,7 @@ impl SystemState { self.validate_world(world); let change_tick = world.read_change_tick(); // SAFETY: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with. - unsafe { self.fetch(world, change_tick) } + unsafe { self.fetch(world.as_unsafe_world_cell_readonly(), change_tick) } } /// Retrieve the mutable [`SystemParam`] values. This will not update the state's view of the world's archetypes @@ -261,7 +261,7 @@ impl SystemState { self.validate_world(world); let change_tick = world.change_tick(); // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with. - unsafe { self.fetch(world, change_tick) } + unsafe { self.fetch(world.as_unsafe_world_cell(), change_tick) } } /// Retrieve the [`SystemParam`] values. This will not update archetypes automatically. @@ -273,7 +273,7 @@ impl SystemState { #[inline] pub unsafe fn get_unchecked_manual<'w, 's>( &'s mut self, - world: &'w World, + world: UnsafeWorldCell<'w>, ) -> SystemParamItem<'w, 's, Param> { let change_tick = world.increment_change_tick(); self.fetch(world, change_tick) @@ -286,7 +286,7 @@ impl SystemState { #[inline] unsafe fn fetch<'w, 's>( &'s mut self, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> SystemParamItem<'w, 's, Param> { let param = Param::get_param(&mut self.param_state, &self.meta, world, change_tick); @@ -459,7 +459,7 @@ where } #[inline] - unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { + unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell<'_>) -> Self::Out { let change_tick = world.increment_change_tick(); // Safety: diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index dab5dd654d825..10510a319d8d8 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1225,7 +1225,8 @@ mod tests { let world2 = World::new(); let qstate = world1.query::<()>(); // SAFETY: doesnt access anything - let query = unsafe { Query::new(&world2, &qstate, 0, 0, false) }; + let query = + unsafe { Query::new(world2.as_unsafe_world_cell_readonly(), &qstate, 0, 0, false) }; query.iter(); } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 4d3a12ed49bc7..8e4c22000a043 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -5,7 +5,7 @@ use crate::{ BatchingStrategy, QueryCombinationIter, QueryEntityError, QueryIter, QueryManyIter, QueryParIter, QuerySingleError, QueryState, ROQueryItem, ReadOnlyWorldQuery, WorldQuery, }, - world::{Mut, World}, + world::{unsafe_world_cell::UnsafeWorldCell, Mut}, }; use std::{any::TypeId, borrow::Borrow, fmt::Debug}; @@ -274,7 +274,7 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug}; /// [`With`]: crate::query::With /// [`Without`]: crate::query::Without pub struct Query<'world, 'state, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> { - world: &'world World, + world: UnsafeWorldCell<'world>, state: &'state QueryState, last_change_tick: u32, change_tick: u32, @@ -303,15 +303,16 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// /// This will create a query that could violate memory safety rules. Make sure that this is only /// called in ways that ensure the queries have unique mutable access. + /// Specifically, the `state` must only access data allowed by the UnsafeWorldCell. #[inline] pub(crate) unsafe fn new( - world: &'w World, + world: UnsafeWorldCell<'w>, state: &'s QueryState, last_change_tick: u32, change_tick: u32, force_read_only_component_access: bool, ) -> Self { - state.validate_world(world); + state.validate_world_unsafe_world_cell(world); Self { force_read_only_component_access, @@ -824,7 +825,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`. unsafe { self.state.get_many_read_only_manual( - self.world, + // SAFETY: by construction this Query contains a QueryState that accesses data allowed by the UnsafeWorldCell + self.world.world(), entities, self.last_change_tick, self.change_tick, @@ -1066,9 +1068,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { .archetype_component_access .has_read(archetype_component) { - entity_ref - .get::() - .ok_or(QueryComponentError::MissingComponent) + unsafe { + entity_ref + .get::() + .ok_or(QueryComponentError::MissingComponent) + } } else { Err(QueryComponentError::MissingReadAccess) } @@ -1132,12 +1136,12 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { if self.force_read_only_component_access { return Err(QueryComponentError::MissingWriteAccess); } - let world = self.world; - let entity_ref = world - .as_unsafe_world_cell_migration_internal() + let entity_ref = self + .world .get_entity(entity) .ok_or(QueryComponentError::NoSuchEntity)?; - let component_id = world + let component_id = self + .world .components() .get_id(TypeId::of::()) .ok_or(QueryComponentError::MissingComponent)?; @@ -1150,9 +1154,12 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { .archetype_component_access .has_write(archetype_component) { - entity_ref - .get_mut_using_ticks::(self.last_change_tick, self.change_tick) - .ok_or(QueryComponentError::MissingComponent) + // SAFETY: we just checked that we have write access to `T`, so `self.world` must be allowed to mutably access it by construction + unsafe { + entity_ref + .get_mut_using_ticks::(self.last_change_tick, self.change_tick) + .ok_or(QueryComponentError::MissingComponent) + } } else { Err(QueryComponentError::MissingWriteAccess) } @@ -1326,8 +1333,10 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// ``` #[inline] pub fn is_empty(&self) -> bool { + // SAFETY: QueryState::is_empty does not access any resources or components + let world = unsafe { self.world.world() }; self.state - .is_empty(self.world, self.last_change_tick, self.change_tick) + .is_empty(world, self.last_change_tick, self.change_tick) } /// Returns `true` if the given [`Entity`] matches the query. diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 78980e9063b81..2e098c17fa023 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -3,7 +3,7 @@ use core::fmt::Debug; use crate::{ archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId, - query::Access, world::World, + query::Access, world::unsafe_world_cell::UnsafeWorldCell, world::World, }; use std::any::TypeId; @@ -46,17 +46,14 @@ pub trait System: Send + Sync + 'static { /// /// # Safety /// - /// This might access world and resources in an unsafe manner. This should only be called in one - /// of the following contexts: - /// 1. This system is the only system running on the given world across all threads. - /// 2. This system only runs in parallel with other systems that do not conflict with the - /// [`System::archetype_component_access()`]. - unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out; + /// This function may only be called with a [`UnsafeWorldCell`] that can be used for + /// everything listed in [`System::archetype_component_access`]. + unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell<'_>) -> Self::Out; /// Runs the system with the given input in the world. fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { self.update_archetype_component_access(world); // SAFETY: world and resources are exclusively borrowed - unsafe { self.run_unsafe(input, world) } + unsafe { self.run_unsafe(input, world.as_unsafe_world_cell()) } } fn apply_buffers(&mut self, world: &mut World); /// Initialize the system. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index df524f742c5f1..2ca9298ce0cca 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -9,7 +9,7 @@ use crate::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, }, system::{Query, SystemMeta}, - world::{FromWorld, World}, + world::{unsafe_world_cell::UnsafeWorldCell, FromWorld, World}, }; pub use bevy_ecs_macros::Resource; pub use bevy_ecs_macros::SystemParam; @@ -168,7 +168,7 @@ pub unsafe trait SystemParam: Sized { unsafe fn get_param<'world, 'state>( state: &'state mut Self::State, system_meta: &SystemMeta, - world: &'world World, + world: UnsafeWorldCell<'world>, change_tick: u32, ) -> Self::Item<'world, 'state>; } @@ -226,9 +226,12 @@ unsafe impl SystemPara unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { + // SAFETY: QueryState::new populates `component_access` which is used in `impl SystemParamState for QueryState`, + // so the scheduler only calls this in systems with access to every component used in the query, + // so using `world` for `state` is always allowed. Query::new( world, state, @@ -369,7 +372,7 @@ fn assert_component_access_compatibility( /// ``` pub struct ParamSet<'w, 's, T: SystemParam> { param_states: &'s mut T::State, - world: &'w World, + world: UnsafeWorldCell<'w>, system_meta: SystemMeta, change_tick: u32, } @@ -444,11 +447,10 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world - .as_unsafe_world_cell_migration_internal() .get_resource_with_ticks(component_id) .unwrap_or_else(|| { panic!( @@ -485,11 +487,10 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { world - .as_unsafe_world_cell_migration_internal() .get_resource_with_ticks(component_id) .map(|(ptr, ticks)| Res { value: ptr.deref(), @@ -539,11 +540,10 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { let value = world - .as_unsafe_world_cell_migration_internal() .get_resource_mut_by_id(component_id) .unwrap_or_else(|| { panic!( @@ -577,11 +577,10 @@ unsafe impl<'a, T: Resource> SystemParam for Option> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { world - .as_unsafe_world_cell_migration_internal() .get_resource_mut_by_id(component_id) .map(|value| ResMut { value: value.value.deref_mut::(), @@ -630,10 +629,11 @@ unsafe impl SystemParam for &'_ World { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, _system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { - world + // SAFTY: `WorldState` has `read_all` access + unsafe { world.world() } } } @@ -751,7 +751,7 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { unsafe fn get_param<'w, 's>( state: &'s mut Self::State, _system_meta: &SystemMeta, - _world: &'w World, + _world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { Local(state.get()) @@ -926,7 +926,7 @@ unsafe impl SystemParam for Deferred<'_, T> { unsafe fn get_param<'w, 's>( state: &'s mut Self::State, _system_meta: &SystemMeta, - _world: &'w World, + _world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { Deferred(state.get()) @@ -1033,11 +1033,10 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world - .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) .unwrap_or_else(|| { panic!( @@ -1072,11 +1071,10 @@ unsafe impl SystemParam for Option> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { world - .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) .map(|(ptr, ticks)| NonSend { value: ptr.deref(), @@ -1125,11 +1123,10 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { let (ptr, ticks) = world - .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) .unwrap_or_else(|| { panic!( @@ -1158,11 +1155,10 @@ unsafe impl<'a, T: 'static> SystemParam for Option> { unsafe fn get_param<'w, 's>( &mut component_id: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { world - .as_unsafe_world_cell_migration_internal() .get_non_send_with_ticks(component_id) .map(|(ptr, ticks)| NonSendMut { value: ptr.assert_unique().deref_mut(), @@ -1185,7 +1181,7 @@ unsafe impl<'a> SystemParam for &'a Archetypes { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, _system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { world.archetypes() @@ -1206,7 +1202,7 @@ unsafe impl<'a> SystemParam for &'a Components { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, _system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { world.components() @@ -1227,7 +1223,7 @@ unsafe impl<'a> SystemParam for &'a Entities { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, _system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { world.entities() @@ -1248,7 +1244,7 @@ unsafe impl<'a> SystemParam for &'a Bundles { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, _system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { world.bundles() @@ -1297,7 +1293,7 @@ unsafe impl SystemParam for SystemChangeTick { unsafe fn get_param<'w, 's>( _state: &'s mut Self::State, system_meta: &SystemMeta, - _world: &'w World, + _world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { SystemChangeTick { @@ -1367,7 +1363,7 @@ unsafe impl SystemParam for SystemName<'_> { unsafe fn get_param<'w, 's>( name: &'s mut Self::State, _system_meta: &SystemMeta, - _world: &'w World, + _world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { SystemName { name } @@ -1409,7 +1405,7 @@ macro_rules! impl_system_param_tuple { unsafe fn get_param<'w, 's>( state: &'s mut Self::State, _system_meta: &SystemMeta, - _world: &'w World, + _world: UnsafeWorldCell<'w>, _change_tick: u32, ) -> Self::Item<'w, 's> { @@ -1533,7 +1529,7 @@ unsafe impl SystemParam for StaticSystemParam<'_, '_, unsafe fn get_param<'world, 'state>( state: &'state mut Self::State, system_meta: &SystemMeta, - world: &'world World, + world: UnsafeWorldCell<'world>, change_tick: u32, ) -> Self::Item<'world, 'state> { // SAFETY: Defer to the safety of P::SystemParam diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index 4787fccd469d0..20d2caa7dc239 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -3,7 +3,7 @@ use crate::{ component::ComponentId, query::Access, system::{IntoSystem, System}, - world::World, + world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use std::{any::TypeId, borrow::Cow}; @@ -99,7 +99,7 @@ impl> System for PipeSystem< self.system_a.is_exclusive() || self.system_b.is_exclusive() } - unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { + unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell<'_>) -> Self::Out { let out = self.system_a.run_unsafe(input, world); self.system_b.run_unsafe(out, world) } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index face0b33ef0b4..12cd419cbf422 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1753,6 +1753,9 @@ impl World { impl fmt::Debug for World { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // NOTE: we can not access any resources or components here + // as the debug impl is reused by UnsafeWorldCell + f.debug_struct("World") .field("id", &self.id) .field("entity_count", &self.entities.len()) diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index ef36097646c75..510c46deb7f59 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -1,6 +1,6 @@ #![warn(unsafe_op_in_unsafe_fn)] -use super::{Mut, World}; +use super::{Mut, World, WorldId}; use crate::{ archetype::{Archetype, ArchetypeComponentId, Archetypes}, bundle::Bundles, @@ -10,7 +10,7 @@ use crate::{ }, entity::{Entities, Entity, EntityLocation}, prelude::Component, - storage::{Column, ComponentSparseSet, TableRow}, + storage::{Column, ComponentSparseSet, Storages, TableRow}, system::Resource, }; use bevy_ptr::Ptr; @@ -79,6 +79,14 @@ unsafe impl Send for UnsafeWorldCell<'_> {} // SAFETY: `&World` and `&mut World` are both `Sync` unsafe impl Sync for UnsafeWorldCell<'_> {} +impl std::fmt::Debug for UnsafeWorldCell<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // SAFETY: `World` debug impl does not access any resources or components + let world = unsafe { self.world() }; + world.fmt(f) + } +} + impl<'w> UnsafeWorldCell<'w> { /// Creates a [`UnsafeWorldCell`] that can be used to access everything immutably pub(crate) fn new_readonly(world: &'w World) -> Self { @@ -151,6 +159,13 @@ impl<'w> UnsafeWorldCell<'w> { unsafe { &*self.0 } } + /// Retrieves this [`World`]'s unique ID + #[inline] + pub fn id(&self) -> WorldId { + // SAFETY: only metadata is accessed + unsafe { self.world_metadata() }.id() + } + /// Retrieves this world's [Entities] collection #[inline] pub fn entities(self) -> &'w Entities { @@ -183,6 +198,12 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.bundles } + // TODO: figure out safety + #[inline] + pub fn storages(self) -> &'w Storages { + &unsafe { self.unsafe_world() }.storages + } + /// Reads the current change tick of this world. #[inline] pub fn read_change_tick(self) -> u32 { diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index 92290407f5b25..43ad9f9444714 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -2,6 +2,7 @@ use crate::MainWorld; use bevy_ecs::{ prelude::*, system::{ReadOnlySystemParam, SystemMeta, SystemParam, SystemParamItem, SystemState}, + world::unsafe_world_cell::UnsafeWorldCell, }; use std::ops::{Deref, DerefMut}; @@ -75,7 +76,7 @@ where unsafe fn get_param<'w, 's>( state: &'s mut Self::State, system_meta: &SystemMeta, - world: &'w World, + world: UnsafeWorldCell<'w>, change_tick: u32, ) -> Self::Item<'w, 's> { // SAFETY: