bevy_trait_query/one/impls/
one_changed.rs

1use bevy_ecs::ptr::UnsafeCellDeref;
2use std::marker::PhantomData;
3
4use bevy_ecs::{
5    archetype::Archetype,
6    component::{ComponentId, Components, Tick},
7    prelude::{Entity, World},
8    query::{FilteredAccess, QueryData, QueryFilter, ReadOnlyQueryData, WorldQuery},
9    storage::{Table, TableRow},
10    world::unsafe_world_cell::UnsafeWorldCell,
11};
12
13use crate::{debug_unreachable, TraitQuery, TraitQueryState};
14
15use crate::{ChangeDetectionFetch, ChangeDetectionStorage};
16
17/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component
18/// implementing a trait, which was added since the last time the system ran.
19pub struct OneChanged<Trait: ?Sized + TraitQuery> {
20    marker: PhantomData<&'static Trait>,
21}
22
23unsafe impl<Trait: ?Sized + TraitQuery> QueryData for OneChanged<Trait> {
24    type ReadOnly = Self;
25
26    /// SAFETY: read-only access
27    const IS_READ_ONLY: bool = true;
28
29    type Item<'w> = bool;
30
31    fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
32        item
33    }
34
35    #[inline(always)]
36    unsafe fn fetch<'w>(
37        fetch: &mut Self::Fetch<'w>,
38        entity: Entity,
39        table_row: TableRow,
40    ) -> Self::Item<'w> {
41        unsafe {
42            let ticks_ptr = match fetch.storage {
43                ChangeDetectionStorage::Uninit => {
44                    // set_archetype must have been called already
45                    debug_unreachable()
46                }
47                ChangeDetectionStorage::Table { ticks } => ticks.get(table_row.as_usize()),
48                ChangeDetectionStorage::SparseSet { components } => components
49                    .get_changed_tick(entity)
50                    .unwrap_or_else(|| debug_unreachable()),
51            };
52
53            (*ticks_ptr)
54                .deref()
55                .is_newer_than(fetch.last_run, fetch.this_run)
56        }
57    }
58}
59
60unsafe impl<Trait: ?Sized + TraitQuery> WorldQuery for OneChanged<Trait> {
61    type Fetch<'w> = ChangeDetectionFetch<'w>;
62    type State = TraitQueryState<Trait>;
63
64    unsafe fn init_fetch<'w>(
65        world: UnsafeWorldCell<'w>,
66        _state: &Self::State,
67        last_run: Tick,
68        this_run: Tick,
69    ) -> Self::Fetch<'w> {
70        unsafe {
71            Self::Fetch::<'w> {
72                storage: ChangeDetectionStorage::Uninit,
73                sparse_sets: &world.storages().sparse_sets,
74                last_run,
75                this_run,
76            }
77        }
78    }
79
80    // This will always be false for us, as we (so far) do not know at compile time whether the
81    // components our trait has been impl'd for are stored in table or in sparse set
82    const IS_DENSE: bool = false;
83
84    #[inline]
85    unsafe fn set_archetype<'w>(
86        fetch: &mut Self::Fetch<'w>,
87        state: &Self::State,
88        _archetype: &'w Archetype,
89        table: &'w Table,
90    ) {
91        unsafe {
92            // Search for a registered trait impl that is present in the archetype.
93            // We check the table components first since it is faster to retrieve data of this type.
94            for &component in &*state.components {
95                if let Some(changed) = table.get_changed_ticks_slice_for(component) {
96                    fetch.storage = ChangeDetectionStorage::Table {
97                        ticks: changed.into(),
98                    };
99                    return;
100                }
101            }
102            for &component in &*state.components {
103                if let Some(components) = fetch.sparse_sets.get(component) {
104                    fetch.storage = ChangeDetectionStorage::SparseSet { components };
105                    return;
106                }
107            }
108            // At least one of the components must be present in the table/sparse set.
109            debug_unreachable()
110        }
111    }
112
113    #[inline]
114    unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
115        unsafe {
116            // only gets called if IS_DENSE == true, which does not hold for us
117            debug_unreachable()
118        }
119    }
120
121    #[inline]
122    fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
123        let mut new_access = access.clone();
124        let mut not_first = false;
125        for &component in &*state.components {
126            assert!(
127                !access.access().has_component_write(component),
128                "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
129                std::any::type_name::<Trait>(),
130            );
131            if not_first {
132                let mut intermediate = access.clone();
133                intermediate.add_component_read(component);
134                new_access.append_or(&intermediate);
135                new_access.extend_access(&intermediate);
136            } else {
137                new_access.and_with(component);
138                new_access.access_mut().add_component_read(component);
139                not_first = true;
140            }
141        }
142        *access = new_access;
143    }
144
145    #[inline]
146    fn init_state(world: &mut World) -> Self::State {
147        TraitQueryState::init(world)
148    }
149
150    #[inline]
151    fn get_state(_: &Components) -> Option<Self::State> {
152        // TODO: fix this https://github.com/bevyengine/bevy/issues/13798
153        panic!("transmuting and any other operations concerning the state of a query are currently broken and shouldn't be used. See https://github.com/JoJoJet/bevy-trait-query/issues/59");
154    }
155
156    fn matches_component_set(
157        state: &Self::State,
158        set_contains_id: &impl Fn(ComponentId) -> bool,
159    ) -> bool {
160        state.matches_component_set_one(set_contains_id)
161    }
162
163    #[inline]
164    fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
165        fetch
166    }
167}
168
169unsafe impl<Trait: ?Sized + TraitQuery> ReadOnlyQueryData for OneChanged<Trait> {}
170unsafe impl<Trait: ?Sized + TraitQuery> QueryFilter for OneChanged<Trait> {
171    const IS_ARCHETYPAL: bool = false;
172    unsafe fn filter_fetch(
173        fetch: &mut Self::Fetch<'_>,
174        entity: Entity,
175        table_row: TableRow,
176    ) -> bool {
177        unsafe { <Self as QueryData>::fetch(fetch, entity, table_row) }
178    }
179}