#ChangedTrackers -- Also get value?
94 messages · Page 1 of 1 (latest)
It appears correct, given that the trackers won't update between systems unless exclusive access is used or otherwise applied to the world, so be aware if things aren't being seen as changed until the next frame.
So if, say, my physics update changes a transform component, and then later in the same frame I go to see if it's been changed in order to do, say, networking updates, it won't appear in this query?
as I understand, someone will need to clarify, but I'm pretty sure that doesn't happen until postupdate or preupdate in the next frame unless manual intervention
exclusive access should work around that
How would I go about doing that?
My goal is to do this on a fixed update. Every fixed update I want to update physics, then collect anything for which its transform and velocity components changed, and serialize them -- but when serializing I'd like to know which of the two, or both, changed.
Possible Pitfalls
Beware of frame delay / 1-frame-lag. This can occur if Bevy runs the detecting system before the changing system. The detecting system will see the change the next time it runs, typically on the next frame update.
If you need to ensure that changes are handled immediately / during the same frame, you can use explicit system ordering.
However, when detecting component additions with Added<T> (which are typically done using Commands), this is not enough; you need stages.```
Okay, that suggests then that it would work all in the same frame?
Seems so
As long as this system runs after the physics system, that is.
ya
I'll have to add a bunch of change detection stuff to my bevy_stress app, curious how it does with lots of entities
gl, hope it works out for ya
Yeah I'm still unsure if the benefits are worth the cost but I'll certainly try it at least.
@honest solar you can call .is_changed and .is_added on the Mut wrapper
So just requesting the &mut here is almost certainly clearer IMO
Can you do it on the Ref<> wrapper too? The ones I want to check for changes are A and B, and write some resulting metadata to Foo.
No, we pass a pure &MyComponent, so there's no place to store that data / those methods
Ah, that's the issue I was running into then, okay.
Yeah. Also note that Changed is not unusually fast
It's just a filter on a bool
So you'll be faster by not double filtering
No, but you can just branch inside your system
Query<(&mut Foo, &mut A, &mut B)>
This is what I would use
It doesn't happen to matter in this case since my server is single-threaded, but wouldn't that be extra pessimistic for potential scheduling overlap?
then do
for (&mut foo, &mut a, &mut b) in query.iter_mut(){
if a.is_changed() | b.is_changed() {
foo.set()
}
}
Yes, this does upgrade those locks
So maybe the really galaxy brain way to do this is
Query<(&mut Foo, &A, &B, ChangeTrackers<A>, ChangeTrackers<B>)
Yeah, that's what I would probably do if I was interested in parallelism there.
But yeah, this avoids doubling up on the "is either changed" logic
Maybe something worthy of the todo list to have a ChangeTrackers query decorator that also gives you a Ref or something?
Yeah, I'd be down. Want to make an issue? That could honestly probably be derived from WorldQuery
Sure! I can draft one up in a little bit.
^this convo should be documented
do you have a massive amount of entities to iterate over here?
if the amount is really high, par_for_each might help unless order of iteration matters to some computation
are there even iteration order guarantees in a query? @cloud storm
This is for a single-threaded server (hosting on a cheap 1 vCPU VPS) so parallelism is something I'm trying to avoid paying the cost for currently.
Still useful for understanding the system, or if I need to bump up to more cores though.
right, that would only matter with like millions of records returned in a query
Yeah. Probably quite a few entities (3 digits worth) but nowhere near that much.
you'll be fine perf wise
🤞
wanna brief all the options attempted? I'll add some benches for them in my little stress test playground app that I plan on contributing back
give a real answer for how much it might hurt using x or y to do it
The two non-redundant ones I think would just be
Query<(&mut Foo, &A, &B, ChangeTrackers<A>, ChangeTrackers<B>)
and
Query<(&mut Foo, &mut A, &mut B)
That is explicitly behavior we don't guarantee
The former has more permissive reuse of A and B in other parallel systems, but is a little clunkier syntactically.
fair, I was going to consider some optimizations around it
but that idea is trashed I guess
Note that this may actually also be noticeably faster in loops, especially parallel or with small component size
Which, the ChangeTrackers one?
Yes. Since you're storing the u32 seperate
Not 100% convinced, but I'd want to do a quick benchmark
could you also give the entity itself(all components)
Ah interesting, I hadn't considered the AoS/SoA implications.
I'd like to use a real world case to base the test on
Follow-up, does this mean the effort to sort in extraction is moot if the order is non-deterministic on the other side?
Uh good question for #rendering-dev. The order is technically stable within each app but that's not a behavior we're making any promises about as it's likely to change for perf optimization
I do think that there should be a "ordered" storage type
// ~= 250usec
fn ct_0<const T: usize>(mut q: Query<
(&mut A<1, f32>, &mut A<2, f32>, &mut A<3, f32>),
Or<(Changed<A<2, f32>>, Changed<A<3, f32>>)>,
>) {
for (mut foo, a, b) in q.iter_mut() {
foo.0 += 1.0;
}
}
// ~= 180usec
fn ct_1<const T: usize>(mut q: Query<(&mut A<1, f32>, &mut A<2, f32>, &mut A<3, f32>)>) {
for (mut foo, mut a, mut b) in q.iter_mut() {
if a.is_changed() | b.is_changed() {
foo.0 += 1.0;
}
}
}
// ~= 250usec
fn ct_2<const T: usize>(mut q: Query<(
&mut A<1, f32>, &A<2, f32>, &A<3, f32>,
ChangeTrackers<A<2, f32>>, ChangeTrackers<A<3, f32>>)
>
) {
for (mut foo, a, b, t1, t2) in q.iter_mut() {
if t1.is_changed() | t2.is_changed() {
foo.0 += 1.0;
}
}
}
for 1million entities
what other components are on this entity?
just wanna make sure that was the intent to use | there Alice?
Exactly. Can you bench || too for my curiosity?
And I guess also bench this against the query in the OP?
ya
it probably optimizes the || in my build
ya, seems it is optimized away
same relative difference over both methods
with filter, k
let me eat first and stuff
❤️
iter 31000 iter/s 6670.23 total elapsed 4.65s last iter 0.109ms avg. 0.150ms iter 32000 iter/s 6707.86 total elapsed 4.77s last iter 0.110ms avg. 0.149ms ct_1 is clear winner
the avg is wonky, i need to fix that up, system ordering no doubt, but I didn't want to interfere with the scheduling too much
so 😒
iter 64000 iter/s 3997.82 total elapsed 16.01s last iter 0.109ms avg. 0.250ms
iter 65000 iter/s 3995.19 total elapsed 16.27s last iter 0.303ms avg. 0.250ms
iter 66000 iter/s 3997.61 total elapsed 16.51s last iter 0.109ms avg. 0.250ms
iter 67000 iter/s 3998.09 total elapsed 16.76s last iter 0.306ms avg. 0.250ms
iter 68000 iter/s 3997.79 total elapsed 17.01s last iter 0.289ms avg. 0.250ms
``` this happens with ct_0 keeps bouncing from the .110 upto .300
ct_2 is doing the same bouncing around as ct_0, not sure what to blame
the only change in the stress is single line of which of those funcs is picked
i'll profile maybe later
It is possible to wrap (&A, ChangeTrackers<A>) in a struct that derives WorldQuery. With a Deref impl it is pretty convenient to use
Wonder how that would play out with the potential perf advantages from separating them.
Oh interesting that the mut approach is a noticeable timing advantage.
Does lead me to believe that a non-mut equivalent would be advantageous. I got distracted but I can put up an issue for it tomorrow.
this is still a pretty contrived bench, best to intervene if it's a noticeable issue for you
Finally got around to creating an issue: https://github.com/bevyengine/bevy/issues/7066