-
-
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?
Conversation
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.
Looks good, just small question
} | ||
|
||
/// fetch a cached [`QueryState`] from world | ||
pub(crate) unsafe fn fetch_mut_from_cached<'w>( |
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.
This function should have a safety contract.
It is not sound to use this in SystemParam::get_param
!
That impl passes on a QueryState
reference to the resulting Query
, meaning any other Query
SystemParam
with the same QueryState
would now create a mutable reference while an aliasing one is still live.
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'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 comment
The 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 comment
The 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.
Hmm, maybe I've misunderstood, but how I see the issue:
Per my understanding, it is legal to have the exact same Query
type twice in the same system, given that access doesn't conflict (when read-only, f.e.). If the unique entities are differentiated by Query
type, then that means one Entity
will then serve multiple system parameters.
However, if you say that this is not the case, then my question goes somewhere else.
If get_param
accesses the ECS, then per the safety contract of get_param
, this access must be registered in init_state
.
If the access for a Query
type is registered in a type-based way, which is what I believe the current API does, then how do we prevent conflicts when multiple instances exist?
It did not seem to me that the current access infrastructure is fine-grained enough for entity-disjoint mutable access.
Does this make more sense?
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.
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.
I think the issue I pointed out here is core to why the previous attempts failed, and it seems pretty difficult to solve. |
Could you please provide a more detailed example? IMO, this pull request appears unrelated to the issue addressed in #18162. |
In some ways, the current |
emm, Could you clarify how exactly? I don’t see how your suggestion relates to this PR. For queries as entities, the goal is to host the system’s built-in querystate into ecs observer. This change has no impact on regular (non-cached) querystate. |
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.
This pr is going to "leak" memory when a system is dropped or when as the query state entities are spawned, but never removed.World::query
is called
Edit: was the pr for World::query to return a Query
never merged?
@@ -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 comment
The 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.
Co-authored-by: Mike <mike.hsu@gmail.com>
yeah, the current implementation of one-shot systems and observer system may lead to "memory leak" when users unregister them, as proper cleanup mechanisms are not yet implemented. |
Unfortunately, it seems that there's no clean solution to this problem at the moment unless |
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.
One minor documentation improvement.
And concerning what @hymm said about leaking memory. I do think this should be addressed. There are only a couple places where we currently drop systems:
- In 'unregister_system' (https://docs.rs/bevy_ecs/0.16.1/src/bevy_ecs/system/system_registry.rs.html#187)
- In 'run_system_once_with' (https://docs.rs/bevy_ecs/0.16.1/src/bevy_ecs/system/system.rs.html#359)
- and observers
It's currently not possible to remove systems from schedules, so we don't have to worry about that.
I believe that we should manually deal with it in these places before we have systems as entities (maybe we can also implement a custom Dropped
, I haven't messed with that before). I don't think I can approve merging this before it's resolved.
#[repr(C)] | ||
#[derive(Component, Deref, DerefMut)] | ||
#[component(storage = "SparseSet", immutable)] | ||
/// A Internal Wrapper of [`QueryState`] for safety reasons. |
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.
The situation may be more complex than this ,While our existing system-build API allows users to construct world-unrelated systems, we cannot support |
Objective
Solution
QueryState
by wrapping it into anInternalQueryStatem
compoent.Performance
ecs system

ecs iteration

This PR should not significantly impact existing benchmarks. Any observed discrepancies are likely due to noise.
However the potential archetype fragmentation - which isn't covered by current benchmark - may have more substantial effects in real scenario, for example, in
many_cubes
, the archetype counts increase from 23 to 262.but the type-erased
QueryState
implementation will ultimately resolve these fragmentation concerns