bevy_trait_query/one/impls/
one_added.rs

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