Open
Description
What problem does this solve or what need does it fill?
Querying the world directly always the creation of QueryState
, rather than returning a Query
that can be worked with directly.
let query_state = world.query::<(&Foo, &mut Bar), With<A>>();
for (foo, bar) in query_state.iter(&world){
assert!(foo > bar);
}
This is directly relevant to:
- Exclusive systems. We could cache this state in a
Local
, but this is rarely done in practice. - Commands. Under the current commands model, there is nowhere to cache this state.
- Integration tests. There is effectively no point caching this state, both because the queries are often not repeated, and because we are not in a performance-constrained setting.
The existing approach is confusing to beginners, heavy on boilerplate and directly exposes end users to QueryState
, which should largely be engine-internal.
What solution would you like?
Create two core methods on World
, replacing the current World::query
and World::query_filtered
:
World::query
: returns a stateless Query directly from the world. This is used in commands and integration tests.World::query_state
: returns aQueryState
. This is used in engine internals, and exclusive systems.
In order to get this to work we need to:
- Allow
Query
to store a&QueryState
or a newInternalQueryState
value, rather than just aQueryState
. - Tweak the initialization methods.
- Warn when
Added
orChanged
are used with aInternalQueryState
.
The example above becomes the much more direct and familiar:
for (foo, bar) in world.query::<(&Foo, &mut Bar), With<A>>().iter(){
assert!(foo > bar);
}
What alternative(s) have you considered?
- Use and store a
QueryState
when writing integration tests and custom commands. Very boilerplate heavy and confusing to new users. - Write and use one-shot systems (One-shot systems via Commands for scripting-like logic #2192) for integration testing. Rather boilerplate-heavy, has some serious open questions and virtually all of the same issues around statelessness.
- Write helper methods (in the engine, or in end user test code) that wrap this boilerplate. This doesn't save much work, and seriously reduces clarity and directness, especially as the methods proliferate. This was the approach taken in Utility functions for integration testing #3839.
Additional context
Closely related to #3774.