Skip to content

Commit 60ea43d

Browse files
Add system ticks to EntityRef/Mut WorldQuery (#19115)
# Objective - Fixes a subset of #13735 by making `EntityRef`, `EntityMut` + similar WorldQueries use the system's change ticks when being created from within a system. In particular, this means that `entity_ref.get_ref::<T>()` will use the correct change ticks (the ones from the system), which matches the behaviour of querying for `Ref<T>` directly in the system parameters. ## Solution - Implements the solution described by #13735 (comment) which is to add change ticks to the `UnsafeEntityCell` ## Testing - Added a unit test that is close to what users would encounter: before this PR the `Added`/`Changed` filters on `Ref`s created from `EntityRef` are incorrect.
1 parent 63e78fe commit 60ea43d

File tree

4 files changed

+196
-66
lines changed

4 files changed

+196
-66
lines changed

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 135 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -486,12 +486,22 @@ unsafe impl QueryData for EntityLocation {
486486
/// SAFETY: access is read only
487487
unsafe impl ReadOnlyQueryData for EntityLocation {}
488488

489+
/// The [`WorldQuery::Fetch`] type for WorldQueries that can fetch multiple components from an entity
490+
/// ([`EntityRef`], [`EntityMut`], etc.)
491+
#[derive(Copy, Clone)]
492+
#[doc(hidden)]
493+
pub struct EntityFetch<'w> {
494+
world: UnsafeWorldCell<'w>,
495+
last_run: Tick,
496+
this_run: Tick,
497+
}
498+
489499
/// SAFETY:
490500
/// `fetch` accesses all components in a readonly way.
491501
/// This is sound because `update_component_access` and `update_archetype_component_access` set read access for all components and panic when appropriate.
492502
/// Filters are unchanged.
493503
unsafe impl<'a> WorldQuery for EntityRef<'a> {
494-
type Fetch<'w> = UnsafeWorldCell<'w>;
504+
type Fetch<'w> = EntityFetch<'w>;
495505
type State = ();
496506

497507
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@@ -501,10 +511,14 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
501511
unsafe fn init_fetch<'w>(
502512
world: UnsafeWorldCell<'w>,
503513
_state: &Self::State,
504-
_last_run: Tick,
505-
_this_run: Tick,
514+
last_run: Tick,
515+
this_run: Tick,
506516
) -> Self::Fetch<'w> {
507-
world
517+
EntityFetch {
518+
world,
519+
last_run,
520+
this_run,
521+
}
508522
}
509523

510524
const IS_DENSE: bool = true;
@@ -556,12 +570,17 @@ unsafe impl<'a> QueryData for EntityRef<'a> {
556570

557571
#[inline(always)]
558572
unsafe fn fetch<'w>(
559-
world: &mut Self::Fetch<'w>,
573+
fetch: &mut Self::Fetch<'w>,
560574
entity: Entity,
561575
_table_row: TableRow,
562576
) -> Self::Item<'w> {
563577
// SAFETY: `fetch` must be called with an entity that exists in the world
564-
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
578+
let cell = unsafe {
579+
fetch
580+
.world
581+
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
582+
.debug_checked_unwrap()
583+
};
565584
// SAFETY: Read-only access to every component has been registered.
566585
unsafe { EntityRef::new(cell) }
567586
}
@@ -572,7 +591,7 @@ unsafe impl ReadOnlyQueryData for EntityRef<'_> {}
572591

573592
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
574593
unsafe impl<'a> WorldQuery for EntityMut<'a> {
575-
type Fetch<'w> = UnsafeWorldCell<'w>;
594+
type Fetch<'w> = EntityFetch<'w>;
576595
type State = ();
577596

578597
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@@ -582,10 +601,14 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> {
582601
unsafe fn init_fetch<'w>(
583602
world: UnsafeWorldCell<'w>,
584603
_state: &Self::State,
585-
_last_run: Tick,
586-
_this_run: Tick,
604+
last_run: Tick,
605+
this_run: Tick,
587606
) -> Self::Fetch<'w> {
588-
world
607+
EntityFetch {
608+
world,
609+
last_run,
610+
this_run,
611+
}
589612
}
590613

591614
const IS_DENSE: bool = true;
@@ -637,20 +660,25 @@ unsafe impl<'a> QueryData for EntityMut<'a> {
637660

638661
#[inline(always)]
639662
unsafe fn fetch<'w>(
640-
world: &mut Self::Fetch<'w>,
663+
fetch: &mut Self::Fetch<'w>,
641664
entity: Entity,
642665
_table_row: TableRow,
643666
) -> Self::Item<'w> {
644667
// SAFETY: `fetch` must be called with an entity that exists in the world
645-
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
668+
let cell = unsafe {
669+
fetch
670+
.world
671+
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
672+
.debug_checked_unwrap()
673+
};
646674
// SAFETY: mutable access to every component has been registered.
647675
unsafe { EntityMut::new(cell) }
648676
}
649677
}
650678

651679
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
652680
unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
653-
type Fetch<'w> = (UnsafeWorldCell<'w>, Access<ComponentId>);
681+
type Fetch<'w> = (EntityFetch<'w>, Access<ComponentId>);
654682
type State = Access<ComponentId>;
655683

656684
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@@ -662,12 +690,19 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
662690
unsafe fn init_fetch<'w>(
663691
world: UnsafeWorldCell<'w>,
664692
_state: &Self::State,
665-
_last_run: Tick,
666-
_this_run: Tick,
693+
last_run: Tick,
694+
this_run: Tick,
667695
) -> Self::Fetch<'w> {
668696
let mut access = Access::default();
669697
access.read_all_components();
670-
(world, access)
698+
(
699+
EntityFetch {
700+
world,
701+
last_run,
702+
this_run,
703+
},
704+
access,
705+
)
671706
}
672707

673708
#[inline]
@@ -743,12 +778,17 @@ unsafe impl<'a> QueryData for FilteredEntityRef<'a> {
743778

744779
#[inline(always)]
745780
unsafe fn fetch<'w>(
746-
(world, access): &mut Self::Fetch<'w>,
781+
(fetch, access): &mut Self::Fetch<'w>,
747782
entity: Entity,
748783
_table_row: TableRow,
749784
) -> Self::Item<'w> {
750785
// SAFETY: `fetch` must be called with an entity that exists in the world
751-
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
786+
let cell = unsafe {
787+
fetch
788+
.world
789+
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
790+
.debug_checked_unwrap()
791+
};
752792
// SAFETY: mutable access to every component has been registered.
753793
unsafe { FilteredEntityRef::new(cell, access.clone()) }
754794
}
@@ -759,7 +799,7 @@ unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {}
759799

760800
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
761801
unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
762-
type Fetch<'w> = (UnsafeWorldCell<'w>, Access<ComponentId>);
802+
type Fetch<'w> = (EntityFetch<'w>, Access<ComponentId>);
763803
type State = Access<ComponentId>;
764804

765805
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@@ -771,12 +811,19 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
771811
unsafe fn init_fetch<'w>(
772812
world: UnsafeWorldCell<'w>,
773813
_state: &Self::State,
774-
_last_run: Tick,
775-
_this_run: Tick,
814+
last_run: Tick,
815+
this_run: Tick,
776816
) -> Self::Fetch<'w> {
777817
let mut access = Access::default();
778818
access.write_all_components();
779-
(world, access)
819+
(
820+
EntityFetch {
821+
world,
822+
last_run,
823+
this_run,
824+
},
825+
access,
826+
)
780827
}
781828

782829
#[inline]
@@ -850,12 +897,17 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> {
850897

851898
#[inline(always)]
852899
unsafe fn fetch<'w>(
853-
(world, access): &mut Self::Fetch<'w>,
900+
(fetch, access): &mut Self::Fetch<'w>,
854901
entity: Entity,
855902
_table_row: TableRow,
856903
) -> Self::Item<'w> {
857904
// SAFETY: `fetch` must be called with an entity that exists in the world
858-
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
905+
let cell = unsafe {
906+
fetch
907+
.world
908+
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
909+
.debug_checked_unwrap()
910+
};
859911
// SAFETY: mutable access to every component has been registered.
860912
unsafe { FilteredEntityMut::new(cell, access.clone()) }
861913
}
@@ -868,7 +920,7 @@ unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B>
868920
where
869921
B: Bundle,
870922
{
871-
type Fetch<'w> = UnsafeWorldCell<'w>;
923+
type Fetch<'w> = EntityFetch<'w>;
872924
type State = SmallVec<[ComponentId; 4]>;
873925

874926
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@@ -878,10 +930,14 @@ where
878930
unsafe fn init_fetch<'w>(
879931
world: UnsafeWorldCell<'w>,
880932
_: &Self::State,
881-
_: Tick,
882-
_: Tick,
933+
last_run: Tick,
934+
this_run: Tick,
883935
) -> Self::Fetch<'w> {
884-
world
936+
EntityFetch {
937+
world,
938+
last_run,
939+
this_run,
940+
}
885941
}
886942

887943
const IS_DENSE: bool = true;
@@ -948,11 +1004,14 @@ where
9481004
}
9491005

9501006
unsafe fn fetch<'w>(
951-
world: &mut Self::Fetch<'w>,
1007+
fetch: &mut Self::Fetch<'w>,
9521008
entity: Entity,
9531009
_: TableRow,
9541010
) -> Self::Item<'w> {
955-
let cell = world.get_entity(entity).unwrap();
1011+
let cell = fetch
1012+
.world
1013+
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
1014+
.unwrap();
9561015
EntityRefExcept::new(cell)
9571016
}
9581017
}
@@ -968,7 +1027,7 @@ unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B>
9681027
where
9691028
B: Bundle,
9701029
{
971-
type Fetch<'w> = UnsafeWorldCell<'w>;
1030+
type Fetch<'w> = EntityFetch<'w>;
9721031
type State = SmallVec<[ComponentId; 4]>;
9731032

9741033
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@@ -978,10 +1037,14 @@ where
9781037
unsafe fn init_fetch<'w>(
9791038
world: UnsafeWorldCell<'w>,
9801039
_: &Self::State,
981-
_: Tick,
982-
_: Tick,
1040+
last_run: Tick,
1041+
this_run: Tick,
9831042
) -> Self::Fetch<'w> {
984-
world
1043+
EntityFetch {
1044+
world,
1045+
last_run,
1046+
this_run,
1047+
}
9851048
}
9861049

9871050
const IS_DENSE: bool = true;
@@ -1049,11 +1112,14 @@ where
10491112
}
10501113

10511114
unsafe fn fetch<'w>(
1052-
world: &mut Self::Fetch<'w>,
1115+
fetch: &mut Self::Fetch<'w>,
10531116
entity: Entity,
10541117
_: TableRow,
10551118
) -> Self::Item<'w> {
1056-
let cell = world.get_entity(entity).unwrap();
1119+
let cell = fetch
1120+
.world
1121+
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
1122+
.unwrap();
10571123
EntityMutExcept::new(cell)
10581124
}
10591125
}
@@ -2544,10 +2610,11 @@ impl<C: Component, T: Copy, S: Copy> Copy for StorageSwitch<C, T, S> {}
25442610

25452611
#[cfg(test)]
25462612
mod tests {
2547-
use bevy_ecs_macros::QueryData;
2548-
25492613
use super::*;
2614+
use crate::change_detection::DetectChanges;
25502615
use crate::system::{assert_is_system, Query};
2616+
use bevy_ecs::prelude::Schedule;
2617+
use bevy_ecs_macros::QueryData;
25512618

25522619
#[derive(Component)]
25532620
pub struct A;
@@ -2641,4 +2708,34 @@ mod tests {
26412708

26422709
assert_is_system(client_system);
26432710
}
2711+
2712+
// Test that EntityRef::get_ref::<T>() returns a Ref<T> value with the correct
2713+
// ticks when the EntityRef was retrieved from a Query.
2714+
// See: https://github.com/bevyengine/bevy/issues/13735
2715+
#[test]
2716+
fn test_entity_ref_query_with_ticks() {
2717+
#[derive(Component)]
2718+
pub struct C;
2719+
2720+
fn system(query: Query<EntityRef>) {
2721+
for entity_ref in &query {
2722+
if let Some(c) = entity_ref.get_ref::<C>() {
2723+
if !c.is_added() {
2724+
panic!("Expected C to be added");
2725+
}
2726+
}
2727+
}
2728+
}
2729+
2730+
let mut world = World::new();
2731+
let mut schedule = Schedule::default();
2732+
schedule.add_systems(system);
2733+
world.spawn(C);
2734+
2735+
// reset the change ticks
2736+
world.clear_trackers();
2737+
2738+
// we want EntityRef to use the change ticks of the system
2739+
schedule.run(&mut world);
2740+
}
26442741
}

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,26 +1113,38 @@ impl<'w> EntityWorldMut<'w> {
11131113

11141114
fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> {
11151115
self.assert_not_despawned();
1116+
let last_change_tick = self.world.last_change_tick;
1117+
let change_tick = self.world.read_change_tick();
11161118
UnsafeEntityCell::new(
11171119
self.world.as_unsafe_world_cell_readonly(),
11181120
self.entity,
11191121
self.location,
1122+
last_change_tick,
1123+
change_tick,
11201124
)
11211125
}
11221126
fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> {
11231127
self.assert_not_despawned();
1128+
let last_change_tick = self.world.last_change_tick;
1129+
let change_tick = self.world.change_tick();
11241130
UnsafeEntityCell::new(
11251131
self.world.as_unsafe_world_cell(),
11261132
self.entity,
11271133
self.location,
1134+
last_change_tick,
1135+
change_tick,
11281136
)
11291137
}
11301138
fn into_unsafe_entity_cell(self) -> UnsafeEntityCell<'w> {
11311139
self.assert_not_despawned();
1140+
let last_change_tick = self.world.last_change_tick;
1141+
let change_tick = self.world.change_tick();
11321142
UnsafeEntityCell::new(
11331143
self.world.as_unsafe_world_cell(),
11341144
self.entity,
11351145
self.location,
1146+
last_change_tick,
1147+
change_tick,
11361148
)
11371149
}
11381150

0 commit comments

Comments
 (0)