mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Added Has<T> WorldQuery type (#8844)
# Objective - Fixes #7811 ## Solution - I added `Has<T>` (and `HasFetch<T>` ) and implemented `WorldQuery`, `ReadonlyWorldQuery`, and `ArchetypeFilter` it - I also added documentation with an example and a unit test I believe I've done everything right but this is my first contribution and I'm not an ECS expert so someone who is should probably check my implementation. I based it on what `Or<With<T>,>`, would do. The only difference is that `Has` does not update component access - adding `Has` to a query should never affect whether or not it is disjoint with another query *I think*. --- ## Changelog ## Added - Added `Has<T>` WorldQuery to find out whether or not an entity has a particular component. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com>
This commit is contained in:
parent
6ce4bf5181
commit
6529d2e7f0
2 changed files with 159 additions and 1 deletions
|
@ -1110,6 +1110,146 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
|||
/// SAFETY: [`OptionFetch`] is read only because `T` is read only
|
||||
unsafe impl<T: ReadOnlyWorldQuery> ReadOnlyWorldQuery for Option<T> {}
|
||||
|
||||
/// Returns a bool that describes if an entity has the component `T`.
|
||||
///
|
||||
/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities
|
||||
/// have the component `T` but don't actually care about the component's value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::query::Has;
|
||||
/// # use bevy_ecs::system::IntoSystem;
|
||||
/// # use bevy_ecs::system::Query;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct IsHungry;
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Name { name: &'static str };
|
||||
/// #
|
||||
/// fn food_entity_system(query: Query<(&Name, Has<IsHungry>) >) {
|
||||
/// for (name, is_hungry) in &query {
|
||||
/// if is_hungry{
|
||||
/// println!("{} would like some food.", name.name);
|
||||
/// } else {
|
||||
/// println!("{} has had sufficient.", name.name);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(food_entity_system);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::query::Has;
|
||||
/// # use bevy_ecs::system::IntoSystem;
|
||||
/// # use bevy_ecs::system::Query;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Alpha{has_beta: bool};
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Beta { has_alpha: bool };
|
||||
/// #
|
||||
/// // Unlike `Option<&T>`, `Has<T>` is compatible with `&mut T`
|
||||
/// // as it does not actually access any data.
|
||||
/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has<Beta>)>, mut betas: Query<(&mut Beta, Has<Alpha>)>) {
|
||||
/// for (mut alpha, has_beta) in alphas.iter_mut() {
|
||||
/// alpha.has_beta = has_beta;
|
||||
/// }
|
||||
/// for (mut beta, has_alpha) in betas.iter_mut() {
|
||||
/// beta.has_alpha = has_alpha;
|
||||
/// }
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(alphabet_entity_system);
|
||||
/// ```
|
||||
pub struct Has<T>(PhantomData<T>);
|
||||
|
||||
// SAFETY: `Self::ReadOnly` is the same as `Self`
|
||||
unsafe impl<T: Component> WorldQuery for Has<T> {
|
||||
type Fetch<'w> = bool;
|
||||
type Item<'w> = bool;
|
||||
type ReadOnly = Self;
|
||||
type State = ComponentId;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
|
||||
item
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = {
|
||||
match T::Storage::STORAGE_TYPE {
|
||||
StorageType::Table => true,
|
||||
StorageType::SparseSet => false,
|
||||
}
|
||||
};
|
||||
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
#[inline]
|
||||
unsafe fn init_fetch<'w>(
|
||||
_world: UnsafeWorldCell<'w>,
|
||||
_state: &Self::State,
|
||||
_last_run: Tick,
|
||||
_this_run: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
false
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
||||
*fetch
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
archetype: &'w Archetype,
|
||||
_table: &Table,
|
||||
) {
|
||||
*fetch = archetype.contains(*state);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
*fetch = table.has_column(*state);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn fetch<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
_entity: Entity,
|
||||
_table_row: TableRow,
|
||||
) -> Self::Item<'w> {
|
||||
*fetch
|
||||
}
|
||||
|
||||
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {
|
||||
// Do nothing as presence of `Has<T>` never affects whether two queries are disjoint
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
_state: &Self::State,
|
||||
_archetype: &Archetype,
|
||||
_access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
world.init_component::<T>()
|
||||
}
|
||||
|
||||
fn matches_component_set(
|
||||
_state: &Self::State,
|
||||
_set_contains_id: &impl Fn(ComponentId) -> bool,
|
||||
) -> bool {
|
||||
// `Has<T>` always matches
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: [`Has`] is read only
|
||||
unsafe impl<T: Component> ReadOnlyWorldQuery for Has<T> {}
|
||||
|
||||
macro_rules! impl_tuple_fetch {
|
||||
($(($name: ident, $state: ident)),*) => {
|
||||
#[allow(non_snake_case)]
|
||||
|
|
|
@ -64,7 +64,7 @@ impl<T> DebugCheckedUnwrap for Option<T> {
|
|||
mod tests {
|
||||
use super::{ReadOnlyWorldQuery, WorldQuery};
|
||||
use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without};
|
||||
use crate::query::{ArchetypeFilter, QueryCombinationIter};
|
||||
use crate::query::{ArchetypeFilter, Has, QueryCombinationIter};
|
||||
use crate::schedule::{IntoSystemConfigs, Schedule};
|
||||
use crate::system::{IntoSystem, Query, System, SystemState};
|
||||
use crate::{self as bevy_ecs, component::Component, world::World};
|
||||
|
@ -476,6 +476,24 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_query() {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn((A(1), B(1)));
|
||||
world.spawn(A(2));
|
||||
world.spawn((A(3), B(1)));
|
||||
world.spawn(A(4));
|
||||
|
||||
let values: Vec<(&A, bool)> = world.query::<(&A, Has<B>)>().iter(&world).collect();
|
||||
|
||||
// The query seems to put the components with B first
|
||||
assert_eq!(
|
||||
values,
|
||||
vec![(&A(1), true), (&A(3), true), (&A(2), false), (&A(4), false),]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."]
|
||||
fn self_conflicting_worldquery() {
|
||||
|
|
Loading…
Reference in a new issue