Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions _release-content/migration-guides/entity_deduplication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "`UnsafeEntityCell` functions now have an `AsAccess` parameter"
pull_requests: [22538]
---

The following functions now return a `Result` with a proper error, instead of an
`Option`. Handle accordingly.

- `FilteredEntityRef::get_by_id`
- `FilteredEntityMut::get_by_id`
- `FilteredEntityMut::get_mut_by_id`
- `FilteredEntityMut::get_mut_by_id_unchecked`
- `EntityRefExcept::get_by_id`
- `EntityMutExcept::get_by_id`
- `EntityMutExcept::get_mut_by_id`

The following functions now take an `AsAccess` as an additional argument.
You should pass an access type that most closely matches your access patterns,
and ensure it abides by Rust aliasing rules.

- `UnsafeEntityCell::get`
- `UnsafeEntityCell::get_ref`
- `UnsafeEntityCell::get_change_ticks`
- `UnsafeEntityCell::get_change_ticks_by_id`
- `UnsafeEntityCell::get_mut`
- `UnsafeEntityCell::get_mut_assume_mutable`
- `UnsafeEntityCell::get_by_id`
- `UnsafeEntityCell::get_mut_by_id`
- `UnsafeEntityCell::get_mut_assume_mutable_by_id`

For example, if your cell can access all components without violating aliasing
rules, use `All`. If your cell can only access a specific set of
components without violating aliasing rules, consider using `Filtered` or `Except`.
If you are able to validate externally that you won't violate aliasing
rules by accessing a particular component, you may use `All`.
3 changes: 2 additions & 1 deletion crates/bevy_asset/src/asset_changed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use crate::{AsAssetId, Asset, AssetId};
use bevy_ecs::component::Components;
use bevy_ecs::world::All;
use bevy_ecs::{
archetype::Archetype,
change_detection::Tick,
Expand Down Expand Up @@ -174,7 +175,7 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
// read that resource here (see trait-level safety comments on `WorldQuery`)
let Some(changes) = (unsafe {
world
.get_resource_by_id(state.resource_id)
.get_resource_by_id(All, state.resource_id)
.map(|ptr| ptr.deref::<AssetChanges<A::Asset>>())
}) else {
error!(
Expand Down
9 changes: 7 additions & 2 deletions crates/bevy_asset/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use thiserror::Error;
use tracing::warn;
use uuid::Uuid;

use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, All, World};
use bevy_reflect::{
serde::{ReflectDeserializerProcessor, ReflectSerializerProcessor},
FromReflect, FromType, PartialReflect, Reflect, TypeRegistry,
Expand Down Expand Up @@ -170,7 +170,12 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
// SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
// and must ensure to only have at most one reference to it live at all times.
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
let assets = unsafe {
world
.get_resource_mut::<Assets<A>>(All)
.unwrap()
.into_inner()
};
let asset = assets.get_mut(asset_id.typed_debug_checked());
asset.map(|asset| asset.into_inner() as &mut dyn Reflect)
},
Expand Down
11 changes: 7 additions & 4 deletions crates/bevy_ecs/src/entity/clone_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
entity::{hash_map::EntityHashMap, Entity, EntityAllocator, EntityMapper},
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
world::World,
world::{All, World},
};
use alloc::{boxed::Box, collections::VecDeque, vec::Vec};
use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
Expand Down Expand Up @@ -590,7 +590,7 @@ impl EntityCloner {
// the registry, which prevents future conflicts.
let app_registry = unsafe {
world
.get_resource::<crate::reflect::AppTypeRegistry>()
.get_resource::<crate::reflect::AppTypeRegistry>(All)
.cloned()
};
#[cfg(not(feature = "bevy_reflect"))]
Expand Down Expand Up @@ -633,8 +633,11 @@ impl EntityCloner {
// SAFETY:
// - There are no other mutable references to source entity.
// - `component` is from `source_entity`'s archetype
let source_component_ptr =
unsafe { source_entity.get_by_id(component).debug_checked_unwrap() };
let source_component_ptr = unsafe {
source_entity
.get_by_id(All, component)
.debug_checked_unwrap()
};

let source_component = SourceComponent {
info,
Expand Down
10 changes: 7 additions & 3 deletions crates/bevy_ecs/src/observer/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
prelude::*,
query::DebugCheckedUnwrap,
system::{ObserverSystem, RunSystemError},
world::DeferredWorld,
world::{All, DeferredWorld},
};
use bevy_ptr::PtrMut;

Expand Down Expand Up @@ -44,7 +44,11 @@ pub(super) unsafe fn observer_system_runner<E: Event, B: Bundle, S: ObserverSyst
// SAFETY: Observer was triggered so must still exist in world
let observer_cell = unsafe { world.get_entity(observer).debug_checked_unwrap() };
// SAFETY: Observer was triggered so must have an `Observer`
let mut state = unsafe { observer_cell.get_mut::<Observer>().debug_checked_unwrap() };
let mut state = unsafe {
observer_cell
.get_mut::<Observer>(All)
.debug_checked_unwrap()
};

// TODO: Move this check into the observer cache to avoid dynamic dispatch
let last_trigger = world.last_trigger_id();
Expand Down Expand Up @@ -101,7 +105,7 @@ pub(super) unsafe fn observer_system_runner<E: Event, B: Bundle, S: ObserverSyst
unsafe {
#[cfg(feature = "hotpatching")]
if world
.get_resource_ref::<crate::HotPatchChanges>()
.get_resource_ref::<crate::HotPatchChanges>(All)
.is_none_or(|r| r.is_changed_after((*system).get_last_run()))
{
(*system).refresh_hotpatch();
Expand Down
31 changes: 17 additions & 14 deletions crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::{
storage::{ComponentSparseSet, Table, TableRow},
system::Query,
world::{
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
unsafe_world_cell::UnsafeWorldCell, All, EntityMut, EntityMutExcept, EntityRef,
EntityRefExcept, Except, Filtered, FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
},
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
Expand Down Expand Up @@ -479,10 +479,11 @@ pub type ROQueryItem<'w, 's, D> = QueryItem<'w, 's, <D as QueryData>::ReadOnly>;

/// A [`QueryData`] that does not borrow from its [`QueryState`].
///
/// This is implemented by most `QueryData` types.
/// The main exceptions are [`FilteredEntityRef`], [`FilteredEntityMut`], [`EntityRefExcept`], and [`EntityMutExcept`],
/// which borrow an access list from their query state.
/// Consider using a full [`EntityRef`] or [`EntityMut`] if you would need those.
/// This is implemented by most `QueryData` types. The main exceptions are
/// [`FilteredEntityRef`], [`FilteredEntityMut`], [`EntityRefExcept`], and
/// [`EntityMutExcept`], which borrow an access list from their query state.
/// Consider using a full [`EntityRef<All>`] or [`EntityMut<All>`] if you would
/// need those.
pub trait ReleaseStateQueryData: QueryData {
/// Releases the borrow from the query state by converting an item to have a `'static` state lifetime.
fn release_state<'w>(item: Self::Item<'w, '_>) -> Self::Item<'w, 'static>;
Expand Down Expand Up @@ -1006,7 +1007,7 @@ unsafe impl<'a> QueryData for EntityRef<'a> {
.debug_checked_unwrap()
};
// SAFETY: Read-only access to every component has been registered.
Some(unsafe { EntityRef::new(cell) })
Some(unsafe { EntityRef::new(cell, All) })
}

fn iter_access(_state: &Self::State) -> impl Iterator<Item = EcsAccessType<'_>> {
Expand Down Expand Up @@ -1122,7 +1123,7 @@ unsafe impl<'a> QueryData for EntityMut<'a> {
.debug_checked_unwrap()
};
// SAFETY: mutable access to every component has been registered.
Some(unsafe { EntityMut::new(cell) })
Some(unsafe { EntityMut::new(cell, All) })
}

fn iter_access(_state: &Self::State) -> impl Iterator<Item = EcsAccessType<'_>> {
Expand Down Expand Up @@ -1255,8 +1256,8 @@ unsafe impl<'a, 'b> QueryData for FilteredEntityRef<'a, 'b> {
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.debug_checked_unwrap()
};
// SAFETY: mutable access to every component has been registered.
Some(unsafe { FilteredEntityRef::new(cell, access) })
// SAFETY: read-only access to the components in `access` has been registered.
Some(unsafe { EntityRef::new(cell, Filtered(access)) })
}

fn iter_access(state: &Self::State) -> impl Iterator<Item = EcsAccessType<'_>> {
Expand Down Expand Up @@ -1384,8 +1385,8 @@ unsafe impl<'a, 'b> QueryData for FilteredEntityMut<'a, 'b> {
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.debug_checked_unwrap()
};
// SAFETY: mutable access to every component has been registered.
Some(unsafe { FilteredEntityMut::new(cell, access) })
// SAFETY: mutable access to the components in `access` has been registered.
Some(unsafe { EntityMut::new(cell, Filtered(access)) })
}

fn iter_access(state: &Self::State) -> impl Iterator<Item = EcsAccessType<'_>> {
Expand Down Expand Up @@ -1505,7 +1506,8 @@ where
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.unwrap();
Some(EntityRefExcept::new(cell, access))
// SAFETY: `access` was constructed based on the components in `B`, and read-only access registered.
Some(unsafe { EntityRef::new(cell, Except::new(access)) })
}

fn iter_access(state: &Self::State) -> impl Iterator<Item = EcsAccessType<'_>> {
Expand Down Expand Up @@ -1629,7 +1631,8 @@ where
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.unwrap();
Some(EntityMutExcept::new(cell, access))
// SAFETY: `access` was constructed based on the components in `B`, and mutable access registered.
Some(unsafe { EntityMut::new(cell, Except::new(access)) })
}

fn iter_access(state: &Self::State) -> impl Iterator<Item = EcsAccessType<'_>> {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/reflect/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use crate::{
prelude::Component,
relationship::RelationshipHookMode,
world::{
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
unsafe_world_cell::UnsafeEntityCell, All, EntityMut, EntityWorldMut, FilteredEntityMut,
FilteredEntityRef, World,
},
};
Expand Down Expand Up @@ -396,7 +396,7 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
// SAFETY: reflect_unchecked_mut is an unsafe function pointer used by
// `reflect_unchecked_mut` which must be called with an UnsafeEntityCell with access to the component `C` on the `entity`
// guard ensures `C` is a mutable component
let c = unsafe { entity.get_mut_assume_mutable::<C>() };
let c = unsafe { entity.get_mut_assume_mutable::<C>(All) };
c.map(|c| c.map_unchanged(|value| value as &mut dyn Reflect))
},
register_component: |world: &mut World| -> ComponentId {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/schedule/executor/multi_threaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ impl ExecutorState {
context
.environment
.world_cell
.get_resource_ref::<HotPatchChanges>()
.get_resource_ref::<HotPatchChanges>(crate::world::All)
}
.map(|r| r.last_changed())
.unwrap_or_default();
Expand Down
20 changes: 12 additions & 8 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ use crate::{
resource::{Resource, IS_RESOURCE},
system::{Query, Single, SystemMeta},
world::{
unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut,
FromWorld, World,
unsafe_world_cell::UnsafeWorldCell, All, DeferredWorld, FilteredResources,
FilteredResourcesMut, FromWorld, World,
},
};
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
Expand Down Expand Up @@ -707,9 +707,11 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
let (ptr, ticks) = world.get_resource_with_ticks(component_id).ok_or_else(|| {
SystemParamValidationError::invalid::<Self>("Resource does not exist")
})?;
// SAFETY: Access to the resource was registered in `init_access`.
let (ptr, ticks) =
unsafe { world.get_resource_with_ticks(All, component_id) }.ok_or_else(|| {
SystemParamValidationError::invalid::<Self>("Resource does not exist")
})?;
Ok(Res {
value: ptr.deref(),
ticks: ComponentTicksRef {
Expand Down Expand Up @@ -764,9 +766,11 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
let value = world.get_resource_mut_by_id(component_id).ok_or_else(|| {
SystemParamValidationError::invalid::<Self>("Resource does not exist")
})?;
// SAFETY: Access to the resource was registered in `init_access`.
let value =
unsafe { world.get_resource_mut_by_id(All, component_id) }.ok_or_else(|| {
SystemParamValidationError::invalid::<Self>("Resource does not exist")
})?;
Ok(ResMut {
value: value.value.deref_mut::<T>(),
ticks: ComponentTicksMut {
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/world/deferred_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
relationship::RelationshipHookMode,
resource::Resource,
system::{Commands, Query},
world::{error::EntityMutableFetchError, EntityFetcher, WorldEntityFetch},
world::{error::EntityMutableFetchError, All, EntityFetcher, WorldEntityFetch},
};

use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World};
Expand Down Expand Up @@ -489,7 +489,7 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub fn get_resource_mut<R: Resource>(&mut self) -> Option<Mut<'_, R>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_resource_mut() }
unsafe { self.world.get_resource_mut(All) }
}

/// Gets a mutable reference to a non-send resource of the given type, if it exists.
Expand Down Expand Up @@ -581,7 +581,7 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the resource
unsafe { self.world.get_resource_mut_by_id(component_id) }
unsafe { self.world.get_resource_mut_by_id(All, component_id) }
}

/// Gets mutable access to `!Send` data with the id [`ComponentId`] if it exists.
Expand Down
Loading