Getting QueryState from immutable World reference (#16434)

# Objective

There is currently no way of getting `QueryState` from `&World`, so it
is hard to, for example, iterate over all entities with a component,
only having `&World`.

## Solution

Add `try_new` function to `QueryState` that internally uses
`WorldQuery`'s `get_state`.

## Testing

No testing
This commit is contained in:
vil'mo 2024-12-02 03:09:29 +07:00 committed by GitHub
parent 6fe4b1440c
commit 2a1064ec5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 1 deletions

View file

@ -6,6 +6,7 @@ use crate::{
prelude::FromWorld,
query::{
Access, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter,
WorldQuery,
},
storage::{SparseSetIndex, TableId},
world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId},
@ -160,6 +161,16 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
state
}
/// Creates a new [`QueryState`] from an immutable [`World`] reference and inherits the result of `world.id()`.
///
/// This function may fail if, for example,
/// the components that make up this query have not been registered into the world.
pub fn try_new(world: &World) -> Option<Self> {
let mut state = Self::try_new_uninitialized(world)?;
state.update_archetypes(world);
Some(state)
}
/// Identical to `new`, but it populates the provided `access` with the matched results.
pub(crate) fn new_with_access(
world: &mut World,
@ -186,7 +197,32 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
fn new_uninitialized(world: &mut World) -> Self {
let fetch_state = D::init_state(world);
let filter_state = F::init_state(world);
Self::from_states_uninitialized(world.id(), fetch_state, filter_state)
}
/// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet
///
/// `new_archetype` and its variants must be called on all of the World's archetypes before the
/// state can return valid query results.
fn try_new_uninitialized(world: &World) -> Option<Self> {
let fetch_state = D::get_state(world.components())?;
let filter_state = F::get_state(world.components())?;
Some(Self::from_states_uninitialized(
world.id(),
fetch_state,
filter_state,
))
}
/// Creates a new [`QueryState`] but does not populate it with the matched results from the World yet
///
/// `new_archetype` and its variants must be called on all of the World's archetypes before the
/// state can return valid query results.
fn from_states_uninitialized(
world_id: WorldId,
fetch_state: <D as WorldQuery>::State,
filter_state: <F as WorldQuery>::State,
) -> Self {
let mut component_access = FilteredAccess::default();
D::update_component_access(&fetch_state, &mut component_access);
@ -205,7 +241,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
let is_dense = D::IS_DENSE && F::IS_DENSE;
Self {
world_id: world.id(),
world_id,
archetype_generation: ArchetypeGeneration::initial(),
matched_storage_ids: Vec::new(),
is_dense,

View file

@ -1670,6 +1670,84 @@ impl World {
QueryState::new(self)
}
/// Returns [`QueryState`] for the given [`QueryData`], which is used to efficiently
/// run queries on the [`World`] by storing and reusing the [`QueryState`].
/// ```
/// use bevy_ecs::{component::Component, entity::Entity, world::World};
///
/// #[derive(Component, Debug, PartialEq)]
/// struct Position {
/// x: f32,
/// y: f32,
/// }
///
/// let mut world = World::new();
/// world.spawn_batch(vec![
/// Position { x: 0.0, y: 0.0 },
/// Position { x: 1.0, y: 1.0 },
/// ]);
///
/// fn get_positions(world: &World) -> Vec<(Entity, &Position)> {
/// let mut query = world.try_query::<(Entity, &Position)>().unwrap();
/// query.iter(world).collect()
/// }
///
/// let positions = get_positions(&world);
///
/// assert_eq!(world.get::<Position>(positions[0].0).unwrap(), positions[0].1);
/// assert_eq!(world.get::<Position>(positions[1].0).unwrap(), positions[1].1);
/// ```
///
/// Requires only an immutable world reference, but may fail if, for example,
/// the components that make up this query have not been registered into the world.
/// ```
/// use bevy_ecs::{component::Component, entity::Entity, world::World};
///
/// #[derive(Component)]
/// struct A;
///
/// let mut world = World::new();
///
/// let none_query = world.try_query::<&A>();
/// assert!(none_query.is_none());
///
/// world.register_component::<A>();
///
/// let some_query = world.try_query::<&A>();
/// assert!(some_query.is_some());
/// ```
#[inline]
pub fn try_query<D: QueryData>(&self) -> Option<QueryState<D, ()>> {
self.try_query_filtered::<D, ()>()
}
/// Returns [`QueryState`] for the given filtered [`QueryData`], which is used to efficiently
/// run queries on the [`World`] by storing and reusing the [`QueryState`].
/// ```
/// use bevy_ecs::{component::Component, entity::Entity, world::World, query::With};
///
/// #[derive(Component)]
/// struct A;
/// #[derive(Component)]
/// struct B;
///
/// let mut world = World::new();
/// let e1 = world.spawn(A).id();
/// let e2 = world.spawn((A, B)).id();
///
/// let mut query = world.try_query_filtered::<Entity, With<B>>().unwrap();
/// let matching_entities = query.iter(&world).collect::<Vec<Entity>>();
///
/// assert_eq!(matching_entities, vec![e2]);
/// ```
///
/// Requires only an immutable world reference, but may fail if, for example,
/// the components that make up this query have not been registered into the world.
#[inline]
pub fn try_query_filtered<D: QueryData, F: QueryFilter>(&self) -> Option<QueryState<D, F>> {
QueryState::try_new(self)
}
/// Returns an iterator of entities that had components of type `T` removed
/// since the last call to [`World::clear_trackers`].
pub fn removed<T: Component>(&self) -> impl Iterator<Item = Entity> + '_ {