-
-
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
Add set_if_neq
method to DetectChanges
trait
#5373
Conversation
@aevyrie IIRC you've wanted this functionality :) |
Maybe out of scope, but I think the real problem is that mutable dereferences of At minimum, I think you need to add a test here that verifies |
Agreed, working on a test here :) |
@maniwani, do you have any insight into how to manually update the change tick correctly? The test is currently failing because the resource is being set as
Obviously I can do something more realistic like use a schedule, but I'd like to understand what's going on. EDIT: |
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 looks really useful. Just have some non-blocking suggestions for the comments.
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'm sure I'd seen this in another PR, although can't locate it at the moment.
This seems fine. Possibly it should come as part of a wider conversation, but I don't have any suggestions for a real alternative.
Co-authored-by: Aevyrie <aevyrie@gmail.com>
Following #5007 we could consider doing fake-specialization with associated types, and then allow users to choose whether to use naive That's a much bigger and more controversial design (even if I agree with it); this is just intended to be a nice little helper for now. |
🚲 🏠 time. Would |
It's more precise, but also more jargon-y. Does get bonus points for brevity though. I'm not sure which one I prefer. I would have gone with |
Normally I'd agree, I always attempt to err on the side of explicitness. However, |
@alice-i-cecile remember to update the title and original post with the changed method name. :) |
set_if_differs
method to DetectChanges
traitset_if_neq
method to DetectChanges
trait
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.
Do we not use the encapsulated pattern anywhere in the engine?
Should this PR replace those instances if so - at the very least the choice not to do so needs to be mentioned somewhere.
Self: Deref<Target = T> + DerefMut<Target = T>, | ||
T: PartialEq, | ||
{ | ||
// This dereference is immutable, so does not trigger change detection |
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.
At first reading, I wasn't a huge fan of the term immutable
here. However, it seems to be the standard terminology, so it's fine.
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.
Changes looks OK, but it seems very hard to discover. Is there an example where it could be used?
Yeah, I should do a pass through and see both a) are there any internal usages and b) is there a good example to put this in? |
The change detection example might be a good place for this: https://github.com/bevyengine/bevy/blob/main/examples/ecs/component_change_detection.rs |
I found this pattern used in a handful of places internally, but could only find a couple of places where I could use this method directly. There are two other patterns that aren't covered:
|
5981564
to
6502c58
Compare
6502c58
to
3f64dd3
Compare
Can you give an example of this pattern?
We should be able to use |
bors r+ |
# Objective Change detection can be spuriously triggered by setting a field to the same value as before. As a result, a common pattern is to write: ```rust if *foo != value { *foo = value; } ``` This is confusing to read, and heavy on boilerplate. ## Solution 1. Add a method to the `DetectChanges` trait that implements this boilerplate when the appropriate trait bounds are met. 2. Document this minor footgun, and point users to it. ## Changelog - added the `set_if_neq` method to avoid triggering change detection when the new and previous values are equal. This will work on both components and resources. ## Migration Guide If you are manually checking if a component or resource's value is equal to its new value before setting it to avoid triggering change detection, migrate to the clearer and more convenient `set_if_neq` method. ## Context Related to #2363 as it avoids triggering change detection, but not a complete solution (as it still requires triggering it when real changes are made).
Build failed: |
This super isn't compiling ATM: I'm going to fix up the merge locally. |
if **self != value { | ||
**self = value; | ||
} |
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.
Perhaps explicit <Self as Deref<Target = T>>::deref(*self, ...)
and <Self as DerefMut<Target = T>>::deref_mut(*self, ...)
would be beneficial for clarity?
This pattern may be common, but it's rather opaque if you aren't aware that Rust will not cache one DerefMut implicitly to share across both references to **self
.
It's also a bit clearer than trying to explain that one is "immutable" as a prior commenter mentioned, as it becomes explicit; then you can just comment "triggers change tracking" or similar on the second one.
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.
Just to weigh in, I think it's fine as-is.
Closing in favor of #6853, which fixed the rebase for me <3 |
# Objective Change detection can be spuriously triggered by setting a field to the same value as before. As a result, a common pattern is to write: ```rust if *foo != value { *foo = value; } ``` This is confusing to read, and heavy on boilerplate. Adopted from #5373, but untangled and rebased to current `bevy/main`. ## Solution 1. Add a method to the `DetectChanges` trait that implements this boilerplate when the appropriate trait bounds are met. 2. Document this minor footgun, and point users to it. ## Changelog * added the `set_if_neq` method to avoid triggering change detection when the new and previous values are equal. This will work on both components and resources. ## Migration Guide If you are manually checking if a component or resource's value is equal to its new value before setting it to avoid triggering change detection, migrate to the clearer and more convenient `set_if_neq` method. ## Context Related to #2363 as it avoids triggering change detection, but not a complete solution (as it still requires triggering it when real changes are made). Co-authored-by: Zoey <Dessix@Dessix.net>
…e#6853) # Objective Change detection can be spuriously triggered by setting a field to the same value as before. As a result, a common pattern is to write: ```rust if *foo != value { *foo = value; } ``` This is confusing to read, and heavy on boilerplate. Adopted from bevyengine#5373, but untangled and rebased to current `bevy/main`. ## Solution 1. Add a method to the `DetectChanges` trait that implements this boilerplate when the appropriate trait bounds are met. 2. Document this minor footgun, and point users to it. ## Changelog * added the `set_if_neq` method to avoid triggering change detection when the new and previous values are equal. This will work on both components and resources. ## Migration Guide If you are manually checking if a component or resource's value is equal to its new value before setting it to avoid triggering change detection, migrate to the clearer and more convenient `set_if_neq` method. ## Context Related to bevyengine#2363 as it avoids triggering change detection, but not a complete solution (as it still requires triggering it when real changes are made). Co-authored-by: Zoey <Dessix@Dessix.net>
…e#6853) # Objective Change detection can be spuriously triggered by setting a field to the same value as before. As a result, a common pattern is to write: ```rust if *foo != value { *foo = value; } ``` This is confusing to read, and heavy on boilerplate. Adopted from bevyengine#5373, but untangled and rebased to current `bevy/main`. ## Solution 1. Add a method to the `DetectChanges` trait that implements this boilerplate when the appropriate trait bounds are met. 2. Document this minor footgun, and point users to it. ## Changelog * added the `set_if_neq` method to avoid triggering change detection when the new and previous values are equal. This will work on both components and resources. ## Migration Guide If you are manually checking if a component or resource's value is equal to its new value before setting it to avoid triggering change detection, migrate to the clearer and more convenient `set_if_neq` method. ## Context Related to bevyengine#2363 as it avoids triggering change detection, but not a complete solution (as it still requires triggering it when real changes are made). Co-authored-by: Zoey <Dessix@Dessix.net>
Objective
Change detection can be spuriously triggered by setting a field to the same value as before.
As a result, a common pattern is to write:
This is confusing to read, and heavy on boilerplate.
Solution
DetectChanges
trait that implements this boilerplate when the appropriate trait bounds are met.Changelog
set_if_neq
method to avoid triggering change detection when the new and previous values are equal. This will work on both components and resources.Migration Guide
If you are manually checking if a component or resource's value is equal to its new value before setting it to avoid triggering change detection, migrate to the clearer and more convenient
set_if_neq
method.Context
Related to #2363 as it avoids triggering change detection, but not a complete solution (as it still requires triggering it when real changes are made).