mirror of
https://github.com/bevyengine/bevy
synced 2024-12-26 13:03:06 +00:00
9575b20d31
# 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: <img width="608" alt="image" src="https://github.com/user-attachments/assets/c90ecc7a-0462-457a-80ae-42e7f5d346b4"> --- ## 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. --------- Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
106 lines
4.1 KiB
Rust
106 lines
4.1 KiB
Rust
//! This example illustrates how to react to component and resource changes.
|
|
|
|
use bevy::prelude::*;
|
|
use rand::Rng;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, setup)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
change_component,
|
|
change_component_2,
|
|
change_resource,
|
|
change_detection,
|
|
),
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[derive(Component, PartialEq, Debug)]
|
|
struct MyComponent(f32);
|
|
|
|
#[derive(Resource, PartialEq, Debug)]
|
|
struct MyResource(f32);
|
|
|
|
fn setup(mut commands: Commands) {
|
|
// Note the first change detection log correctly points to this line because the component is
|
|
// added. Although commands are deferred, they are able to track the original calling location.
|
|
commands.spawn(MyComponent(0.0));
|
|
commands.insert_resource(MyResource(0.0));
|
|
}
|
|
|
|
fn change_component(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
|
|
for (entity, mut component) in &mut query {
|
|
if rand::thread_rng().gen_bool(0.1) {
|
|
let new_component = MyComponent(time.elapsed_seconds().round());
|
|
info!("New value: {new_component:?} {entity:?}");
|
|
// Change detection occurs on mutable dereference, and does not consider whether or not
|
|
// a value is actually equal. To avoid triggering change detection when nothing has
|
|
// actually changed, you can use the `set_if_neq` method on any component or resource
|
|
// that implements PartialEq.
|
|
component.set_if_neq(new_component);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This is a duplicate of the `change_component` system, added to show that change tracking can
|
|
/// help you find *where* your component is being changed, when there are multiple possible
|
|
/// locations.
|
|
fn change_component_2(time: Res<Time>, mut query: Query<(Entity, &mut MyComponent)>) {
|
|
for (entity, mut component) in &mut query {
|
|
if rand::thread_rng().gen_bool(0.1) {
|
|
let new_component = MyComponent(time.elapsed_seconds().round());
|
|
info!("New value: {new_component:?} {entity:?}");
|
|
component.set_if_neq(new_component);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Change detection concepts for components apply similarly to resources.
|
|
fn change_resource(time: Res<Time>, mut my_resource: ResMut<MyResource>) {
|
|
if rand::thread_rng().gen_bool(0.1) {
|
|
let new_resource = MyResource(time.elapsed_seconds().round());
|
|
info!("New value: {new_resource:?}");
|
|
my_resource.set_if_neq(new_resource);
|
|
}
|
|
}
|
|
|
|
/// Query filters like [`Changed<T>`] and [`Added<T>`] ensure only entities matching these filters
|
|
/// will be returned by the query.
|
|
///
|
|
/// Using the [`Ref<T>`] system param allows you to access change detection information, but does
|
|
/// not filter the query.
|
|
fn change_detection(
|
|
changed_components: Query<Ref<MyComponent>, Changed<MyComponent>>,
|
|
my_resource: Res<MyResource>,
|
|
) {
|
|
for component in &changed_components {
|
|
// By default, you can only tell that a component was changed.
|
|
//
|
|
// This is useful, but what if you have multiple systems modifying the same component, how
|
|
// will you know which system is causing the component to change?
|
|
warn!(
|
|
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
|
|
component,
|
|
component.is_added(),
|
|
component.is_changed(),
|
|
// If you enable the `track_change_detection` feature, you can unlock the `changed_by()`
|
|
// method. It returns the file and line number that the component or resource was
|
|
// changed in. It's not recommended for released games, but great for debugging!
|
|
component.changed_by()
|
|
);
|
|
}
|
|
|
|
if my_resource.is_changed() {
|
|
warn!(
|
|
"Change detected!\n\t-> value: {:?}\n\t-> added: {}\n\t-> changed: {}\n\t-> changed by: {}",
|
|
my_resource,
|
|
my_resource.is_added(),
|
|
my_resource.is_changed(),
|
|
my_resource.changed_by() // Like components, requires `track_change_detection` feature.
|
|
);
|
|
}
|
|
}
|