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

Prevent ImageBundles from causing constant layout recalculations #1299

Merged
merged 2 commits into from
Jan 25, 2021

Conversation

Davier
Copy link
Contributor

@Davier Davier commented Jan 24, 2021

The complete UI layout is recalculated when a CalculatedSize is updated, and image_node_system updates them every frame for images. The fix checks if the size has actually changed.

Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

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

Seems sensible to me. I wonder if we could add something similar as a method to Mut, i.e.

impl<'a, T: PartialEq<T>> Mut<'a, T> {
    fn set_if_new(&mut self, value: T) -> Option<T>{
        if &*self == &value {
            *self = value;
            None
        } else {
            Some(value)
        }
    }
}

In theory, we could make a version which just returns T and returns the old value in the first branch, but I suspect that the return value of this function used often anyway.

Obviously to use it here we'd need to derive PartialEq for CalculatedSize, but that isn't the end of the world. I'm also not sure if just plain Eq would be more correct in this case

@alice-i-cecile
Copy link
Member

Seems sensible to me. I wonder if we could add something similar as a method to Mut, i.e.

You definitely could, but I don't think it's worth expanding the API to save some pretty simple code?

I'm also not sure if just plain Eq would be more correct in this case

Eq is more correct here IMO: trying to figure out what the correct behavior is for when self == value but value != self or when we can't compare their equality seems unintuitive.

@rparrett
Copy link
Contributor

rparrett commented Jan 24, 2021

Does this work? (Sorry, bit rude of me not to check myself.) Seems like you'd still be mut dereferencing CalculatedSize and triggering a Changed<CalculatedSize>.

This seems related to something that goes on with text, where we re-layout text on any Changed<Text> and thus modify CalculatedSize. So the act of mut dereferencing the text causes a re-layout even if the value ends up "the same."

At one point I wrote some code like this which seems to help but it's not exactly ergonomic.

fn test(mut queries: QuerySet<(Query<&Text>, Query<&mut Text>)>, state: Res<State>) {
    if let Some(text) = queries.q0().iter().next() {
        let score_text = format!("Score: {}", state.score);

        if (text.value != score_text) {
            if let Some(mut text) = queries.q1_mut().iter_mut().next() {
                text.value = score_text;
            }   
        }   
    }   
}

Something like that could perhaps go on in TextSystem, but there's already a QuerySet going on in there and it's a bit complicated. There's also the same concern around Changed<Style> I think.

@DJMcNab
Copy link
Member

DJMcNab commented Jan 24, 2021

I can say with confidence that if the diagnosis of the problem is correct, this solution works.

The line calculated.size is only a Deref, not a DerefMut, which I'm confident of because the PartialEq impl which is being called takes Size by reference. The Deref impl of Mut<'a, T> does not change the mutated flag.

@rparrett
Copy link
Contributor

Cool. I'm very happy that it's just my understanding of DerefMut that's flawed.

width: texture.size.width as f32,
height: texture.size.height as f32,
};
if size != calculated_size.size {
Copy link
Member

Choose a reason for hiding this comment

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

Its probably a good idea to leave a comment here about why we're doing this check. People with less context might look at this and say "this check isn't needed".

@cart
Copy link
Member

cart commented Jan 25, 2021

Nice catch!

set_if_new

I think the one benefit of adding a new method to Mut for this is that it self-documents intent. Otherwise i think we need comments everywhere to ensure people don't try to "remove an unnecessary branch"

@Davier
Copy link
Contributor Author

Davier commented Jan 25, 2021

Should I add set_if_new to this PR then? Or should that be a new PR?

@Davier
Copy link
Contributor Author

Davier commented Jan 25, 2021

Seems sensible to me. I wonder if we could add something similar as a method to Mut, i.e.

You definitely could, but I don't think it's worth expanding the API to save some pretty simple code?

As @rparrett pointed out, this issue is present everywhere. It's most notable in any UI component like Text, Style and Image since the layout calculations take so long in any non trivial app. I expect Mut::set_if_new to be useful in other parts of bevy too, since mutation detection is on by default (see #63).

Does this work?

Yes, I tested it.

@cart
Copy link
Member

cart commented Jan 25, 2021

Hmm yeah lets do a separate pr to make it clearer / easier to point to in the changelog. We should also discuss the name as this will be used everywhere.

For now lets just add a comment above the if statement.

@cart cart merged commit 5edf2d2 into bevyengine:master Jan 25, 2021
@Davier Davier deleted the fix_imagebundle branch January 25, 2021 05:24
rparrett pushed a commit to rparrett/bevy that referenced this pull request Jan 27, 2021
…yengine#1299)

Prevent ImageBundles from causing constant layout recalculations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants