Skip to content
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

Track source location in change detection #14034

Merged
merged 26 commits into from
Jul 30, 2024

Conversation

aevyrie
Copy link
Member

@aevyrie aevyrie commented Jun 26, 2024

Objective

  • Make it possible to know what changed your component or resource.
  • Common need when debugging, when you want to know the last code location that mutated a value in the ECS.
  • This feature would be very useful for the editor alongside system stepping.

Solution

  • Adds the caller location to column data.
  • Mutations now track_caller all the way up to the public API.
  • Commands that invoke these functions immediately call Location::caller, and pass this into the functions, instead of the functions themselves attempting to get the caller. This would not work for commands which are deferred, as the commands are executed by the scheduler, not the user's code.

Testing

  • The component_change_detection example now shows where the component was mutated:
2024-07-28T06:57:48.946022Z  INFO component_change_detection: Entity { index: 1, generation: 1 }: New value: MyComponent(0.0)
2024-07-28T06:57:49.004371Z  INFO component_change_detection: Entity { index: 1, generation: 1 }: New value: MyComponent(1.0)
2024-07-28T06:57:49.012738Z  WARN component_change_detection: Change detected!
        -> value: Ref(MyComponent(1.0))
        -> added: false
        -> changed: true
        -> changed by: examples/ecs/component_change_detection.rs:36:23
  • It's also possible to inspect change location from a debugger:
image

Changelog

  • Added source locations to ECS change detection behind the track_change_detection flag.

Migration Guide

  • Added changed_by field to many internal ECS functions used with change detection when the track_change_detection feature flag is enabled. Use Location::caller() to provide the source of the function call.

@aevyrie aevyrie added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events labels Jun 26, 2024
@aevyrie aevyrie force-pushed the changed-by branch 4 times, most recently from d3242ff to 9ac216d Compare June 26, 2024 08:12
@cart
Copy link
Member

cart commented Jun 26, 2024

Mut is one of the hottest paths in Bevy. I'm concerned about the performance implications of introducing anything (even the cheapest ops) to it. I'd like to either see compelling evidence that this won't meaningfully affect performance, especially given that this is a "debugging tool". I'd feel much more comfortable if this was behind a feature flag.

@alice-i-cecile alice-i-cecile added the X-Contentious There are nontrivial implications that should be thought through label Jun 26, 2024
@alice-i-cecile
Copy link
Member

I really like this feature and think it'll be very useful, but I'll second the request for putting it behind an off-by-default feature flag.

@alice-i-cecile alice-i-cecile added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Jun 26, 2024
@aevyrie
Copy link
Member Author

aevyrie commented Jun 26, 2024

Yup, I assumed as much. I was hoping to get a read on whether this is even an acceptable approach to take for the feature. Before going down the path of adding feature flags.

@Brezak
Copy link
Contributor

Brezak commented Jun 26, 2024

Is there a reason you're de referencing the static reference you get from Location::caller? Couldn't you just pass the reference around?

@BD103
Copy link
Member

BD103 commented Jun 27, 2024

Is there a reason you're de referencing the static reference you get from Location::caller? Couldn't you just pass the reference around?

Location is two u32s and one &str, making it 16 bytes on 64-bit platforms. Storing a reference instead would bring it down to 8 bytes, which might have some mild performance gains because it would fit in a register.

Furthermore, why do you use core::panic::Location as compared with the std variant?

@aevyrie
Copy link
Member Author

aevyrie commented Jun 28, 2024

  • static reference: I started with this, but was having trouble with the deref making it hard to update the field, so I switched to an owned value to get it working. I agree it should be fixed if this is going to be merged.
  • using core: because it can't hurt? I wasn't sure if bevy_ecs was ever intended to be used in a no_std context, and figured it would be safer to use core for a type so ubiquitous.

The answer to most questions is probably going to be "because this is a proof of concept". 😄
I wasn't sure if this was going to be possible or if it would return any useful information, so I'm mostly just happy to see that it's doable. The useful findings from this are:

  1. It's possible to get line and column level tracing for change detection, which is super useful
  2. Changes from commands completely break this approach because commands are deferred, and the call stack can't be traced to the calling location by the time the command is actually executed.

For 2, it seems like it should be possible to get information from tracing to figure out what system the commands are being executed for. Not quite sure how to do that though, might need to replace Location with a new struct that also contains the Location as well as the current system span.

@BD103
Copy link
Member

BD103 commented Jun 28, 2024

2. Changes from commands completely break this approach because commands are deferred, and the call stack can't be traced to the calling location by the time the command is actually executed.

Is it possible to find the Location in Commands::insert, then pass that forward to the actual command?

@alice-i-cecile
Copy link
Member

using core: because it can't hurt? I wasn't sure if bevy_ecs was ever intended to be used in a no_std context, and figured it would be safer to use core for a type so ubiquitous.

I would like this, one day :) Probably core + alloc, but still! Not a priority, but might as well do it when there's no cost to it.

@SpecificProtagonist
Copy link
Contributor

SpecificProtagonist commented Jul 8, 2024

Is it possible to find the Location in Commands::insert, then pass that forward to the actual command?

Yep, that would look like this (you'd also need to do the same for init_resource, insert_resource, spawn_batch etc).

@aevyrie
Copy link
Member Author

aevyrie commented Jul 8, 2024

Good call! That looks pretty straightforward.

slyedoc added a commit to slyedoc/bevy that referenced this pull request Jul 26, 2024
@slyedoc
Copy link
Contributor

slyedoc commented Jul 26, 2024

Spent far to long trying to figure out why a camera transform from glb was getting reset, was correct on spawn, next frame its Identity, this found the issue in seconds. Was calling remove_parent_in_place before TransformPropagate had ran. Huge help.

@BD103 BD103 mentioned this pull request Jul 27, 2024
3 tasks
Copy link
Contributor

@NthTensor NthTensor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this and I want it. Should Commands::add and Command::push be track_caller so that custom user commands can provide call tracing? Or would that be something user should do on the methods of their extension trait.

It looks like this fully covers commands as well as direct world access. Except for the special paths that let you avoid change detection, is there anything that this can't track?

@BD103 BD103 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 30, 2024
@BD103
Copy link
Member

BD103 commented Jul 30, 2024

I like this and I want it. Should Commands::add and Command::push be track_caller so that custom user commands can provide call tracing? Or would that be something user should do on the methods of their extension trait.

If we do want to support custom commands like this, it would need more documentation. I feel like this is probably follow-up PR territory.

@NthTensor
Copy link
Contributor

follow-up PR territory.

Fair enough, agreed.

@workingjubilee
Copy link

workingjubilee commented Jul 30, 2024

Is there a reason you're de referencing the static reference you get from Location::caller? Couldn't you just pass the reference around?

Location is two u32s and one &str, making it 16 bytes on 64-bit platforms. Storing a reference instead would bring it down to 8 bytes, which might have some mild performance gains because it would fit in a register.

at the risk of sounding incredibly pedantic: 24 bytes. &str is the pointer and the length, so it is already 16 bytes.

remember that this is a pretty magical struct that gets willed into existence by the compiler during codegen. I kinda expect it to have somewhat nonintuitive codegen properties because inspecting Location::caller() kinda... creates the thing as much as it returns a reference.

@SpecificProtagonist
Copy link
Contributor

SpecificProtagonist commented Jul 30, 2024

Should Commands::add and Command::push be track_caller so that custom user commands can provide call tracing?

At best that would mean storing the caller location and changing Commands::apply to take a &Location, but custom commands can already just store the location themselves in their constructor.

In either case the problem for custom commands is that insert_with_caller & friends aren't pub. So the actual question is: Do we want to make them pub?

pub unsafe fn insert_resource_by_id(
&mut self,
component_id: ComponentId,
value: OwningPtr<'_>,
#[cfg(feature = "track_change_detection")] caller: &'static Location,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and insert_non_send_by_id are the only pub functions that take Locations (and also have a unused #[track_caller]); they should be spit into a pub version with #[track_caller] and a pub(crate) one that takes the caller.

@aevyrie
Copy link
Member Author

aevyrie commented Jul 30, 2024

insert_with_caller & friends aren't pub. So the actual question is: Do we want to make them pub?

I think so. This will be needed for custom impls, as you said. We can document the context around this on the with_caller methods. I wish there was a way to make this pattern nicer, but alas.

Comment on lines +354 to +357
#[cfg(feature = "track_change_detection")]
{
*self.changed_by = Location::caller();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.set_changed already updates the location

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jul 30, 2024
Merged via the queue into bevyengine:main with commit 9575b20 Jul 30, 2024
30 checks passed
@BD103
Copy link
Member

BD103 commented Jul 30, 2024

at the risk of sounding incredibly pedantic: 24 bytes. &str is the pointer and the length, so it is already 16 bytes.

Hah, I forgot about wide pointers. Thanks!

@alice-i-cecile
Copy link
Member

Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1666 if you'd like to help out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants