Skip to content
Closed
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
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ impl DerefMut for ResourceEntities {
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Debug))]
#[derive(Component, Debug)]
#[component(on_insert, on_discard, on_despawn)]
pub struct IsResource(ComponentId);
pub struct IsResource(pub ComponentId);

impl IsResource {
/// Creates a new instance with the given `component_id`
Expand Down
48 changes: 30 additions & 18 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ use crate::{
bundle::Bundles,
change_detection::{ComponentTicksMut, ComponentTicksRef, Tick},
component::{ComponentId, Components},
entity::{Entities, EntityAllocator},
entity::{Entities, Entity, EntityAllocator},
query::{
Access, FilteredAccess, FilteredAccessSet, IterQueryData, QueryData, QueryFilter,
QuerySingleError, QueryState, ReadOnlyQueryData,
},
resource::{Resource, IS_RESOURCE},
resource::{IsResource, Resource, IS_RESOURCE},
storage::NonSendData,
system::{Query, Single, SystemMeta},
world::{
Expand Down Expand Up @@ -738,15 +738,20 @@ unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {}
// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res
// conflicts with any prior access, a panic will occur.
unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
type State = ComponentId;
type State = (ComponentId, Entity);
type Item<'w, 's> = Res<'w, T>;

fn init_state(world: &mut World) -> Self::State {
world.components_registrator().register_component::<T>()
let component_id = world.components_registrator().register_component::<T>();
if let Some(entity) = world.resource_entities().get(component_id) {
return (component_id, *entity);
}
let entity = world.spawn(IsResource(component_id)).id();
(component_id, entity)
}

fn init_access(
&component_id: &Self::State,
&(component_id, _): &Self::State,
system_meta: &mut SystemMeta,
component_access_set: &mut FilteredAccessSet,
_world: &mut World,
Expand Down Expand Up @@ -777,13 +782,12 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {

#[inline]
unsafe fn validate_param(
&mut component_id: &mut Self::State,
&mut (component_id, entity): &mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
// SAFETY: Read-only access to the resource
if let Some(entity) = unsafe { world.resource_entities() }.get(component_id)
&& let Ok(entity_ref) = world.get_entity(*entity)
if let Ok(entity_ref) = world.get_entity(entity)
&& entity_ref.contains_id(component_id)
{
Ok(())
Expand All @@ -796,13 +800,15 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {

#[inline]
unsafe fn get_param<'w, 's>(
&mut component_id: &'s mut Self::State,
&mut (component_id, entity): &'s mut Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Self::Item<'w, 's> {
let (ptr, ticks) = world
.get_resource_with_ticks(component_id)
.get_entity(entity)
.ok()
.and_then(|entity| entity.get_with_ticks(component_id))
.unwrap_or_else(|| {
panic!(
"Resource requested by {} does not exist: {}",
Expand All @@ -826,15 +832,20 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res
// conflicts with any prior access, a panic will occur.
unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
type State = ComponentId;
type State = (ComponentId, Entity);
type Item<'w, 's> = ResMut<'w, T>;

fn init_state(world: &mut World) -> Self::State {
world.components_registrator().register_component::<T>()
let component_id = world.components_registrator().register_component::<T>();
if let Some(entity) = world.resource_entities().get(component_id) {
return (component_id, *entity);
}
let entity = world.spawn(IsResource(component_id)).id();
(component_id, entity)
}

fn init_access(
&component_id: &Self::State,
&(component_id, _): &Self::State,
system_meta: &mut SystemMeta,
component_access_set: &mut FilteredAccessSet,
_world: &mut World,
Expand Down Expand Up @@ -868,13 +879,12 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {

#[inline]
unsafe fn validate_param(
&mut component_id: &mut Self::State,
&mut (component_id, entity): &mut Self::State,
Copy link
Copy Markdown
Member

@cart cart Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redundant work in validate_param, which is done immediately before get_param never felt great to me. This isn't a problem exclusive to Res/ResMut, but it is certainly present.

I think doing validation as part of get_param is more defensible from a performance perspective.

I think it is worth doing some benchmarks where we skip validation (as-in, across all SystemParams), just to see what price we're paying here.

_system_meta: &SystemMeta,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
// SAFETY: Read-only access to the resource.
if let Some(entity) = unsafe { world.resource_entities() }.get(component_id)
&& let Ok(entity_ref) = world.get_entity(*entity)
if let Ok(entity_ref) = world.get_entity(entity)
&& entity_ref.contains_id(component_id)
{
Ok(())
Expand All @@ -887,13 +897,15 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {

#[inline]
unsafe fn get_param<'w, 's>(
&mut component_id: &'s mut Self::State,
&mut (component_id, entity): &'s mut Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Self::Item<'w, 's> {
let value = world
.get_resource_mut_by_id(component_id)
.get_entity(entity)
.ok()
.and_then(|entity| entity.get_mut_by_id(component_id).ok())
.unwrap_or_else(|| {
panic!(
"Resource requested by {} does not exist: {}",
Expand Down
33 changes: 33 additions & 0 deletions crates/bevy_ecs/src/world/unsafe_world_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,39 @@ impl<'w> UnsafeEntityCell<'w> {
}
}

/// Gets the component of the given [`ComponentId`] from the entity with the tick information.
///
/// **You should prefer to use the typed API where possible and only
/// use this in cases where the actual component types are not known at
/// compile time.**
///
/// Unlike [`UnsafeEntityCell::get`], this returns a raw pointer to the component,
/// which is only valid while the `'w` borrow of the lifetime is active.
///
/// # Safety
/// It is the caller's responsibility to ensure that
/// - the [`UnsafeEntityCell`] has permission to access the component
/// - no other mutable references to the component exist at the same time
#[inline]
pub unsafe fn get_with_ticks(
self,
component_id: ComponentId,
) -> Option<(Ptr<'w>, ComponentTickCells<'w>)> {
let info = self.world.components().get_info(component_id)?;
// SAFETY:
// - caller ensures there is no `&mut World`
// - caller ensures there are no mutable borrows of this resource
// - caller ensures that we have permission to access this resource
// - storage_type and location are valid
get_component_and_ticks(
self.world,
component_id,
info.storage_type(),
self.entity,
self.location,
)
}

/// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`].
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
///
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_render/src/extract_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ where
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
// SAFETY: Read-only access to world data registered in `init_state`.
let result = unsafe { world.get_resource_by_id(state.main_world_state) };
let result = unsafe { world.get_resource_by_id(state.main_world_state.0) };
let Some(main_world) = result else {
return Err(SystemParamValidationError::invalid::<Self>(
"`MainWorld` resource does not exist",
Expand Down