-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Queries as Entities #14668
base: main
Are you sure you want to change the base?
Queries as Entities #14668
Conversation
Can we not add a market component to query-entities and other internal stuff, and only nuke entities without that component? |
It's more complicated than that. Currently we clear all entities by just clearing the storage so to only clear a selection of entities would be a change in itself and has perf implications. The more complex aspect is currently entities in the render world have the same entity id as the main world, this breaks if, for example, a main world entity gets extracted and tries to take the same id as a query that's been retained. |
last_run: Tick, | ||
this_run: Tick, | ||
) -> Self { | ||
state.validate_world(world.id()); | ||
let query_state = world.world_mut().get::<QueryState<D, F>>(state).unwrap(); |
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 is unsound since there may be other system parameters that e.g. immutably borrow metadata from the world. This should use UnsafeWorldCell::get_entity
instead. Also, to make it completly safe, queries should register access to their state components.
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.
It feels like transforming this to use EntityRef
shouldn't be too difficult since the Query
should own the Entity
.
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.
EntityRef
requires read access to all other components of that Entity
as well, which will make it conflict with any other mutable query.
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 missed that this didn't namespace the entities it used at all...
I suspect this kind of work will require name-spacing of some kind to avoid the conflict.
After all if you have a Query that returns all Queries (assuming they are type erased) what sense does that make?
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.
It doesn't really make sense, but the way system params access currently works you can only request access for all components with a given id/type on entities that have or not some other components.
This will definitely need something like namespacing otherwise Query<EntityMut>
would not work.
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 am just saying that caution should be taken to avoid expanding existing "everything" definitions ala EntityRef
/EntityMut
to suddenly block infrastructure bits from working. Those things didn't overlap before they would only overlap in the entity backed world if we didn't keep them distinct.
Solutions could be a separate world for an extreme example or segregating things so that the system knows that a query entity doesn't have a component on it that a user would add.
The main reason I brought up EntityRef
is because it seems to more closely match what was written here. I guess it is technically a hypothetical combination of EntityRef
and a Query<&QueryState<D, F>>
(a bit recursive there) but I didn't think that type existed so referenced the other one.
Mostly it was "if Query
can share the entity how does that work" which I thought was what EntityRef
was about "shared ownership of an Entity
"
fn get_query_state(&self) -> &QueryState<D, F> { | ||
return unsafe { self.world.world_mut().get::<QueryState<D, F>>(self.state).unwrap() } | ||
} |
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.
Same here
fn get_query_state(&self) -> &QueryState<D, F> { | ||
return unsafe { self.world.world_mut().get::<QueryState<D, F>>(self.state).unwrap() } | ||
} |
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.
Also, couldn't this access be done only once in Query::new
(thus going back to Query
holding a &QueryState
, except now it's a &'w QueryState
)?
let new_state = self.state.as_readonly(); | ||
let mut query_state = self.get_query_state(); | ||
let new_state = query_state.as_readonly().clone(); | ||
let read_only_state = unsafe { self.world.world_mut() }.spawn(*new_state).id(); |
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 is very unsound not only due to the creation of the &mut World
, but also because spawn
actually needs exclusive access to the world to mutate its metadata.
IMO this is a big blocker because spawning entities is something that you just cannot do without exclusive access to the world, but obviously we don't want each query to require exclusive access to do so.
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.
We don't need another entity here. as_readonly
calls pointer::cast
and so doesn't need to be copied.
I am not sure the ideal solution but I think type erasing QueryState<D, F>
and "transmuting" that type erased form into a QueryState<D, F>
should work here as the underlying bytes are the same between the two forms.
However another reason to call clone
here is ownership, specifically how do we share ownership of the original QueryState<D, F>
since hypothetically both Query
should hold a immutable reference to the QueryState<D, F>
if it is shared like that. (Of course one would treat it as a QueryState<D::ReadOnly, F>
but that is a pointer cast thing)
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.
With how Query
is defined right now you need another entity, because the Query<D::ReadOnly, F>
will try to access a component of type QueryState<D::ReadOnly, F>
on that Entity
, but that may only have a component of type QueryState<D, F>
.
Query
would have to be changed to store a &'w QueryState<D, F>
to do what you're proposing.
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.
That is why I said it needs to be type erased, aka store the QueryState
in a way that isn't type specific.
Objective
Quoting the Relations RFC:
Solution
Continuing the quote:
Migration Guide
TBD