#ChangedTrackers -- Also get value?

94 messages · Page 1 of 1 (latest)

honest solar
#
Query<(&mut Foo, &A, &B, ChangeTrackers<A>, ChangeTrackers<B>), Or(Changed<A>, Changed<B>)>

Is this the correct query syntax if I want to grab Foo and A and B whenever A or B have changed, and also want to know which of them has changed? Is there a cleaner way to do this?

eternal gust
#

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.

honest solar
#

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?

eternal gust
#

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

honest solar
#

Oh.

#

That's... not great.

eternal gust
#

exclusive access should work around that

honest solar
#

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.

eternal gust
#

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.```
honest solar
#

Okay, that suggests then that it would work all in the same frame?

eternal gust
#

Seems so

honest solar
#

As long as this system runs after the physics system, that is.

eternal gust
#

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

honest solar
#

Yeah I'm still unsure if the benefits are worth the cost but I'll certainly try it at least.

cloud storm
#

@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

honest solar
#

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.

cloud storm
honest solar
#

Ah, that's the issue I was running into then, okay.

cloud storm
#

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

honest solar
#

What would be faster in this case?

#

Changed<Or<A, B>>?

#

Is that possible?

cloud storm
#

No, but you can just branch inside your system

#

Query<(&mut Foo, &mut A, &mut B)>

#

This is what I would use

honest solar
#

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?

cloud storm
#

then do

for (&mut foo, &mut a, &mut b) in query.iter_mut(){
  if a.is_changed() | b.is_changed() {
     foo.set()
  }
}
cloud storm
#

So maybe the really galaxy brain way to do this is

Query<(&mut Foo, &A, &B, ChangeTrackers<A>, ChangeTrackers<B>)
honest solar
#

Yeah, that's what I would probably do if I was interested in parallelism there.

cloud storm
honest solar
#

Maybe something worthy of the todo list to have a ChangeTrackers query decorator that also gives you a Ref or something?

cloud storm
honest solar
#

Sure! I can draft one up in a little bit.

eternal gust
#

^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

honest solar
#

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.

eternal gust
#

right, that would only matter with like millions of records returned in a query

honest solar
#

Yeah. Probably quite a few entities (3 digits worth) but nowhere near that much.

eternal gust
#

you'll be fine perf wise

honest solar
#

🤞

eternal gust
#

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

honest solar
#

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)
cloud storm
honest solar
#

The former has more permissive reuse of A and B in other parallel systems, but is a little clunkier syntactically.

eternal gust
#

fair, I was going to consider some optimizations around it

#

but that idea is trashed I guess

cloud storm
honest solar
#

Which, the ChangeTrackers one?

cloud storm
#

Yes. Since you're storing the u32 seperate

#

Not 100% convinced, but I'd want to do a quick benchmark

eternal gust
#

could you also give the entity itself(all components)

honest solar
#

Ah interesting, I hadn't considered the AoS/SoA implications.

eternal gust
#

I'd like to use a real world case to base the test on

eternal gust
cloud storm
#

I do think that there should be a "ordered" storage type

eternal gust
#
  // ~= 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?

cloud storm
#

And I guess also bench this against the query in the OP?

eternal gust
#

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

cloud storm
#

❤️

eternal gust
#

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

ivory patrol
honest solar
#

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.

eternal gust
#

this is still a pretty contrived bench, best to intervene if it's a noticeable issue for you

honest solar