diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 69b67368fe..f493ba7c97 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -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 QueryState { 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 { + 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 QueryState { 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 { + 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: ::State, + filter_state: ::State, + ) -> Self { let mut component_access = FilteredAccess::default(); D::update_component_access(&fetch_state, &mut component_access); @@ -205,7 +241,7 @@ impl QueryState { 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, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 63a3025eeb..6eb715af56 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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::(positions[0].0).unwrap(), positions[0].1); + /// assert_eq!(world.get::(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::(); + /// + /// let some_query = world.try_query::<&A>(); + /// assert!(some_query.is_some()); + /// ``` + #[inline] + pub fn try_query(&self) -> Option> { + self.try_query_filtered::() + } + + /// 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::>().unwrap(); + /// let matching_entities = query.iter(&world).collect::>(); + /// + /// 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(&self) -> Option> { + 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(&self) -> impl Iterator + '_ {