-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Componentize system-internal QueryState. #19473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
74d9040
f25aa9c
08ac9d1
5f736e7
f5f22e0
7b530c9
9169590
2607987
5901c9e
f9f59b8
0f1c7c8
04cdaf7
3890ea9
c052054
e16b155
09a7cfc
533bcb1
09cea60
b7b4f22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ use crate::{ | |
component::{ComponentId, Tick}, | ||
entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, | ||
entity_disabling::DefaultQueryFilters, | ||
prelude::FromWorld, | ||
prelude::{Component, FromWorld}, | ||
query::{FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, | ||
storage::{SparseSetIndex, TableId}, | ||
system::Query, | ||
|
@@ -14,7 +14,8 @@ use crate::{ | |
use crate::entity::UniqueEntityEquivalentSlice; | ||
|
||
use alloc::vec::Vec; | ||
use core::{fmt, ptr}; | ||
use core::{fmt, ops::DerefMut, ptr}; | ||
use derive_more::derive::{Deref, DerefMut}; | ||
use fixedbitset::FixedBitSet; | ||
use log::warn; | ||
#[cfg(feature = "trace")] | ||
|
@@ -25,6 +26,12 @@ use super::{ | |
QueryManyUniqueIter, QuerySingleError, ROQueryItem, ReadOnlyQueryData, | ||
}; | ||
|
||
#[repr(C)] | ||
#[derive(Component, Deref, DerefMut)] | ||
#[component(storage = "SparseSet", immutable)] | ||
/// A Internal Wrapper of [`QueryState`] for safety reasons. | ||
pub(crate) struct InternalQueryState<D: QueryData, F: QueryFilter>(QueryState<D, F>); | ||
|
||
/// An ID for either a table or an archetype. Used for Query iteration. | ||
/// | ||
/// Query iteration is exclusively dense (over tables) or archetypal (over archetypes) based on whether | ||
|
@@ -1751,6 +1758,33 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> { | |
} | ||
} | ||
|
||
impl<D: QueryData + 'static, F: QueryFilter + 'static> QueryState<D, F> { | ||
/// cache a [`QueryState`] into world. | ||
pub(crate) fn cached(self, world: &mut World) -> (Entity, ComponentId) { | ||
let id = world.register_component::<InternalQueryState<D, F>>(); | ||
let e = world.spawn(InternalQueryState(self)).id(); | ||
(e, id) | ||
} | ||
|
||
/// fetch a cached [`QueryState`] from world | ||
re0312 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// Safety: | ||
/// - Must not mutably alias the returned [`QueryState`]. | ||
pub(crate) unsafe fn fetch_mut_from_cached<'w>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should have a safety contract. It is not sound to use this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure if I fully grasp your meaning, but each query system parameter maintains its own unique entity, so it shouldn't create mutable aliasing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Systems only need mutable access in this PR. Up coming PR will use observer to update cache, and observer will be the only place need it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe I've misunderstood, but how I see the issue: Does this make more sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even when using the same query type twice in a single system, the implementation creates two distinct querystate entities. As a result, get_param operations will never intersect between these instances. |
||
cached: (Entity, ComponentId), | ||
w: UnsafeWorldCell<'w>, | ||
) -> Option<&'w mut QueryState<D, F>> { | ||
w.storages() | ||
.sparse_sets | ||
.get(cached.1)? | ||
.get(cached.0) | ||
.map(|ptr| { | ||
ptr.assert_unique() | ||
.deref_mut::<InternalQueryState<D, F>>() | ||
.deref_mut() | ||
}) | ||
} | ||
} | ||
impl<D: QueryData, F: QueryFilter> From<QueryBuilder<'_, D, F>> for QueryState<D, F> { | ||
fn from(mut value: QueryBuilder<D, F>) -> Self { | ||
QueryState::from_builder(&mut value) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ use crate::{ | |
bundle::Bundles, | ||
change_detection::{MaybeLocation, Ticks, TicksMut}, | ||
component::{ComponentId, ComponentTicks, Components, Tick}, | ||
entity::Entities, | ||
entity::{Entities, Entity}, | ||
query::{ | ||
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, | ||
QueryState, ReadOnlyQueryData, | ||
|
@@ -65,7 +65,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated}; | |
/// # #[derive(SystemParam)] | ||
/// # struct ParamsExample<'w, 's> { | ||
/// # query: | ||
/// Query<'w, 's, Entity>, | ||
/// Query<'w, 'w, Entity>, | ||
/// # res: | ||
/// Res<'w, SomeResource>, | ||
/// # res_mut: | ||
|
@@ -171,7 +171,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated}; | |
/// #[derive(SystemParam)] | ||
/// #[system_param(builder)] | ||
/// pub struct CustomParam<'w, 's> { | ||
/// query: Query<'w, 's, ()>, | ||
/// query: Query<'w, 'w, ()>, | ||
/// local: Local<'s, usize>, | ||
/// } | ||
/// | ||
|
@@ -321,13 +321,13 @@ unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> Re | |
// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If | ||
// this Query conflicts with any prior access, a panic will occur. | ||
unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Query<'_, '_, D, F> { | ||
type State = QueryState<D, F>; | ||
type Item<'w, 's> = Query<'w, 's, D, F>; | ||
type State = (Entity, ComponentId); | ||
type Item<'w, 's> = Query<'w, 'w, D, F>; | ||
|
||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { | ||
let state = QueryState::new(world); | ||
let state: QueryState<D, F> = QueryState::new(world); | ||
init_query_param(world, system_meta, &state); | ||
state | ||
state.cached(world) | ||
} | ||
|
||
#[inline] | ||
|
@@ -336,11 +336,14 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Qu | |
system_meta: &SystemMeta, | ||
world: UnsafeWorldCell<'w>, | ||
change_tick: Tick, | ||
) -> Self::Item<'w, 's> { | ||
) -> Self::Item<'w, 'w> { | ||
let state = QueryState::fetch_mut_from_cached(*state, world) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be wrapped in an unsafe block and state how the safety contract is fulfilled. |
||
.expect("cached querystate entity not found"); | ||
// SAFETY: We have registered all of the query's world accesses, | ||
// so the caller ensures that `world` has permission to access any | ||
// world data that the query needs. | ||
// The caller ensures the world matches the one used in init_state. | ||
|
||
unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) } | ||
} | ||
} | ||
|
@@ -386,11 +389,11 @@ fn assert_component_access_compatibility( | |
// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If | ||
// this Query conflicts with any prior access, a panic will occur. | ||
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Single<'a, D, F> { | ||
type State = QueryState<D, F>; | ||
type State = (Entity, ComponentId); | ||
type Item<'w, 's> = Single<'w, D, F>; | ||
|
||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { | ||
Query::init_state(world, system_meta) | ||
Query::<D, F>::init_state(world, system_meta) | ||
} | ||
|
||
#[inline] | ||
|
@@ -400,10 +403,8 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo | |
world: UnsafeWorldCell<'w>, | ||
change_tick: Tick, | ||
) -> Self::Item<'w, 's> { | ||
// SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. | ||
// The caller ensures the world matches the one used in init_state. | ||
let query = | ||
unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) }; | ||
let query: Query<'_, '_, D, F> = Query::get_param(state, system_meta, world, change_tick); | ||
|
||
let single = query | ||
.single_inner() | ||
.expect("The query was expected to contain exactly one matching entity."); | ||
|
@@ -419,12 +420,9 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo | |
system_meta: &SystemMeta, | ||
world: UnsafeWorldCell, | ||
) -> Result<(), SystemParamValidationError> { | ||
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere | ||
// and the query is read only. | ||
// The caller ensures the world matches the one used in init_state. | ||
let query = unsafe { | ||
state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) | ||
}; | ||
let query: Query<'_, '_, D, F> = | ||
Query::get_param(state, system_meta, world, world.change_tick()); | ||
|
||
match query.single_inner() { | ||
Ok(_) => Ok(()), | ||
Err(QuerySingleError::NoEntities(_)) => Err( | ||
|
@@ -448,11 +446,11 @@ unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOn | |
unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam | ||
for Populated<'_, '_, D, F> | ||
{ | ||
type State = QueryState<D, F>; | ||
type Item<'w, 's> = Populated<'w, 's, D, F>; | ||
type State = (Entity, ComponentId); | ||
type Item<'w, 's> = Populated<'w, 'w, D, F>; | ||
|
||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { | ||
Query::init_state(world, system_meta) | ||
Query::<D, F>::init_state(world, system_meta) | ||
} | ||
|
||
#[inline] | ||
|
@@ -461,7 +459,7 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam | |
system_meta: &SystemMeta, | ||
world: UnsafeWorldCell<'w>, | ||
change_tick: Tick, | ||
) -> Self::Item<'w, 's> { | ||
) -> Self::Item<'w, 'w> { | ||
// SAFETY: Delegate to existing `SystemParam` implementations. | ||
let query = unsafe { Query::get_param(state, system_meta, world, change_tick) }; | ||
Populated(query) | ||
|
@@ -473,12 +471,9 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam | |
system_meta: &SystemMeta, | ||
world: UnsafeWorldCell, | ||
) -> Result<(), SystemParamValidationError> { | ||
// SAFETY: | ||
// - We have read-only access to the components accessed by query. | ||
// - The caller ensures the world matches the one used in init_state. | ||
let query = unsafe { | ||
state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) | ||
}; | ||
// SAFETY: Delegate to existing `SystemParam` implementations. | ||
let query: Query<'_, '_, D, F> = | ||
unsafe { Query::get_param(state, system_meta, world, world.change_tick()) }; | ||
if query.is_empty() { | ||
Err(SystemParamValidationError::skipped::<Self>( | ||
"No matching entities", | ||
|
@@ -2577,11 +2572,10 @@ mod tests { | |
#[derive(SystemParam)] | ||
pub struct SpecialQuery< | ||
'w, | ||
's, | ||
D: QueryData + Send + Sync + 'static, | ||
F: QueryFilter + Send + Sync + 'static = (), | ||
> { | ||
_query: Query<'w, 's, D, F>, | ||
_query: Query<'w, 'w, D, F>, | ||
} | ||
|
||
fn my_system(_: SpecialQuery<(), ()>) {} | ||
|
@@ -2710,11 +2704,11 @@ mod tests { | |
#[test] | ||
fn system_param_where_clause() { | ||
#[derive(SystemParam)] | ||
pub struct WhereParam<'w, 's, D> | ||
pub struct WhereParam<'w, D> | ||
where | ||
D: 'static + QueryData, | ||
{ | ||
_q: Query<'w, 's, D, ()>, | ||
_q: Query<'w, 'w, D, ()>, | ||
} | ||
|
||
fn my_system(_: WhereParam<()>) {} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like a better explanation for what those safety reasons are. From what I can remember it's mostly so that users don't query for QueryState and subsequently mess with it, since it's an internal engine thing. But there may be more reasons.