Skip to content

Commit

Permalink
Added Ref to allow immutable access with change detection (#7097)
Browse files Browse the repository at this point in the history
# Objective

- Fixes #7066 

## Solution

- Split the ChangeDetection trait into ChangeDetection and ChangeDetectionMut
- Added Ref as equivalent to &T with change detection

---

## Changelog

- Support for Ref which allow inspecting change detection flags in an immutable way

## Migration Guide

- While bevy prelude includes both ChangeDetection and ChangeDetectionMut any code explicitly referencing ChangeDetection might need to be updated to ChangeDetectionMut or both. Specifically any reading logic requires ChangeDetection while writes requires ChangeDetectionMut.

use bevy_ecs::change_detection::DetectChanges -> use bevy_ecs::change_detection::{DetectChanges, DetectChangesMut}

- Previously Res had methods to access change detection `is_changed` and `is_added` those methods have been moved to the `DetectChanges` trait. If you are including bevy prelude you will have access to these types otherwise you will need to `use bevy_ecs::change_detection::DetectChanges` to continue using them.
  • Loading branch information
Guvante committed Jan 10, 2023
1 parent a207178 commit 6cded5c
Show file tree
Hide file tree
Showing 14 changed files with 454 additions and 213 deletions.
298 changes: 230 additions & 68 deletions crates/bevy_ecs/src/change_detection.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
bundle::Bundle,
change_detection::DetectChanges,
change_detection::{DetectChanges, DetectChangesMut},
component::Component,
entity::Entity,
event::{EventReader, EventWriter, Events},
Expand Down
173 changes: 169 additions & 4 deletions crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{
archetype::{Archetype, ArchetypeComponentId},
change_detection::Ticks,
change_detection::{Ticks, TicksMut},
component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick},
entity::Entity,
query::{Access, DebugCheckedUnwrap, FilteredAccess},
storage::{ComponentSparseSet, Table, TableRow},
world::{Mut, World},
world::{Mut, Ref, World},
};
use bevy_ecs_macros::all_tuples;
pub use bevy_ecs_macros::WorldQuery;
Expand Down Expand Up @@ -653,6 +653,167 @@ unsafe impl<T: Component> WorldQuery for &T {
/// SAFETY: access is read only
unsafe impl<T: Component> ReadOnlyWorldQuery for &T {}

#[doc(hidden)]
pub struct RefFetch<'w, T> {
// T::Storage = TableStorage
table_data: Option<(
ThinSlicePtr<'w, UnsafeCell<T>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
ThinSlicePtr<'w, UnsafeCell<Tick>>,
)>,
// T::Storage = SparseStorage
sparse_set: Option<&'w ComponentSparseSet>,

last_change_tick: u32,
change_tick: u32,
}

/// SAFETY: `Self` is the same as `Self::ReadOnly`
unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
type Fetch<'w> = RefFetch<'w, T>;
type Item<'w> = Ref<'w, T>;
type ReadOnly = Self;
type State = ComponentId;

fn shrink<'wlong: 'wshort, 'wshort>(item: Ref<'wlong, T>) -> Ref<'wshort, T> {
item
}

const IS_DENSE: bool = {
match T::Storage::STORAGE_TYPE {
StorageType::Table => true,
StorageType::SparseSet => false,
}
};

const IS_ARCHETYPAL: bool = true;

unsafe fn init_fetch<'w>(
world: &'w World,
&component_id: &ComponentId,
last_change_tick: u32,
change_tick: u32,
) -> RefFetch<'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()
}),
last_change_tick,
change_tick,
}
}

unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
RefFetch {
table_data: fetch.table_data,
sparse_set: fetch.sparse_set,
last_change_tick: fetch.last_change_tick,
change_tick: fetch.change_tick,
}
}

#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut RefFetch<'w, T>,
component_id: &ComponentId,
_archetype: &'w Archetype,
table: &'w Table,
) {
if Self::IS_DENSE {
Self::set_table(fetch, component_id, table);
}
}

#[inline]
unsafe fn set_table<'w>(
fetch: &mut RefFetch<'w, T>,
&component_id: &ComponentId,
table: &'w Table,
) {
let column = table.get_column(component_id).debug_checked_unwrap();
fetch.table_data = Some((
column.get_data_slice().into(),
column.get_added_ticks_slice().into(),
column.get_changed_ticks_slice().into(),
));
}

#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Ref {
value: table_components.get(table_row.index()).deref(),
ticks: Ticks {
added: added_ticks.get(table_row.index()).deref(),
changed: changed_ticks.get(table_row.index()).deref(),
change_tick: fetch.change_tick,
last_change_tick: fetch.last_change_tick,
},
}
}
StorageType::SparseSet => {
let (component, ticks) = fetch
.sparse_set
.debug_checked_unwrap()
.get_with_ticks(entity)
.debug_checked_unwrap();
Ref {
value: component.deref(),
ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick),
}
}
}
}

fn update_component_access(
&component_id: &ComponentId,
access: &mut FilteredAccess<ComponentId>,
) {
assert!(
!access.access().has_write(component_id),
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
std::any::type_name::<T>(),
);
access.add_read(component_id);
}

fn update_archetype_component_access(
&component_id: &ComponentId,
archetype: &Archetype,
access: &mut Access<ArchetypeComponentId>,
) {
if let Some(archetype_component_id) = archetype.get_archetype_component_id(component_id) {
access.add_read(archetype_component_id);
}
}

fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}

fn matches_component_set(
&state: &ComponentId,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
set_contains_id(state)
}
}

/// SAFETY: access is read only
unsafe impl<'__w, T: Component> ReadOnlyWorldQuery for Ref<'__w, T> {}

#[doc(hidden)]
pub struct WriteFetch<'w, T> {
// T::Storage = TableStorage
Expand Down Expand Up @@ -755,7 +916,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
fetch.table_data.debug_checked_unwrap();
Mut {
value: table_components.get(table_row.index()).deref_mut(),
ticks: Ticks {
ticks: TicksMut {
added: added_ticks.get(table_row.index()).deref_mut(),
changed: changed_ticks.get(table_row.index()).deref_mut(),
change_tick: fetch.change_tick,
Expand All @@ -771,7 +932,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
.debug_checked_unwrap();
Mut {
value: component.assert_unique().deref_mut(),
ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick),
ticks: TicksMut::from_tick_cells(
ticks,
fetch.last_change_tick,
fetch.change_tick,
),
}
}
}
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,14 @@ pub fn assert_is_system<In, Out, Params, S: IntoSystem<In, Out, Params>>(sys: S)
mod tests {
use std::any::TypeId;

use crate::prelude::StageLabel;

use crate::{
self as bevy_ecs,
archetype::{ArchetypeComponentId, Archetypes},
bundle::Bundles,
change_detection::DetectChanges,
component::{Component, Components},
entity::{Entities, Entity},
prelude::AnyOf,
prelude::{AnyOf, StageLabel},
query::{Added, Changed, Or, With, Without},
schedule::{Schedule, Stage, SystemStage},
system::{
Expand Down
Loading

0 comments on commit 6cded5c

Please sign in to comment.