Skip to content

trigger Text & Text2d re-render when Children is removed #19666

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Austreelis
Copy link
Contributor

Objective

As of at least bevy 0.16.0, Removing Children from a Text Node would not be detected by bevy_text::text::detect_text_needs_rerender, leading to rendering glitches (#19657). After pinning down the bug, the same happens with Text2d, assuming its docs are correct about its use of TextSpan (I didn't actually went down the rabbit hole a second time to make sure).

Fixes #19657

Solution

  • Observe On<Remove, Children>, and query then set ComputedTextBlock.needs_rerendering if present.

Testing

@Austreelis Austreelis force-pushed the fix/19657-children-text-span-cleanup branch from 53732a5 to 2c91c80 Compare June 15, 2025 23:33
@hukasu hukasu added A-Rendering Drawing game state to the screen A-UI Graphical user interfaces, styles, layouts, and widgets S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 16, 2025
@hukasu hukasu added C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples labels Jun 16, 2025
@alice-i-cecile alice-i-cecile requested a review from ickshonpe June 16, 2025 02:29
@rendaoer
Copy link
Contributor

Haha, I still want to come back to repair him today.

Copy link
Contributor

@ickshonpe ickshonpe left a comment

Choose a reason for hiding this comment

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

As well the problems with using an observer, this won't work because the bug is recursive and it affects the Children of TextSpan entities too.

Maybe we should just give up on fine grained change detection for now, walk every TextSpan entity building an ordered list of spans for each ComputedTextBlock entity, and relayout if the list of spans or a TextSpan's contents changes. I'm not sure.

/// Generic over the root text component and text span component. For example,
/// [`Text2d`](crate::Text2d)/[`TextSpan`] for 2d or `Text`/[`TextSpan`] for UI.
pub fn detect_text_needs_rerender_on_children_removed<Root: Component>(
trigger: On<Remove, Children>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't work I think, it's going to trigger and do a ComputedTextBlock look up everytime any Children component is despawned, not just those belonging to text entities.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we explicitely observe entities that have Text/Text2d ? I don't know if that dupplicates observer entities for every observed text root, I'm not sure it would be an issue either.

/// [`Text2d`](crate::Text2d)/[`TextSpan`] for 2d or `Text`/[`TextSpan`] for UI.
pub fn detect_text_needs_rerender_on_children_removed<Root: Component>(
trigger: On<Remove, Children>,
mut computed: Query<&mut ComputedTextBlock, With<Root>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

The same observer could update both root types. The discriminator also means that the observer is going to run twice every time any Children component is removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll at least dedupplicate that first

@Austreelis
Copy link
Contributor Author

Maybe we should just give up on fine grained change detection for now, walk every TextSpan entity building an ordered list of spans for each ComputedTextBlock entity

I originally wished I could use a custom relation (e.g. ChildTextOf) that would do exactly what ChildOf is, with the added hooks needed to take care of text-specific stuff. But I didn't want to overthrow ChildOf, or effectively dupplicate it. Though I didn't consider the recursive TextSpan children.

@Austreelis Austreelis marked this pull request as draft June 17, 2025 07:49
@Austreelis
Copy link
Contributor Author

I've come back to this, to try and handle any of Text, Text2d and TextSpan: we now observe when any of those component is added, then add an observer to the target - that will track when its children are removed - to traverse up the hierarchy, find the root computed text block and set needs_rerender. This avoids the bug, including with recursive TextSpans, and does not trigger observers needlessly. It does create an observer per text node (+ 1 global) however :/

@Austreelis Austreelis force-pushed the fix/19657-children-text-span-cleanup branch from 2c91c80 to 86c0e30 Compare June 17, 2025 13:32
@Austreelis Austreelis force-pushed the fix/19657-children-text-span-cleanup branch from 86c0e30 to 4ee77c9 Compare June 17, 2025 14:10
@Austreelis Austreelis requested a review from ickshonpe June 17, 2025 14:21
@Austreelis Austreelis marked this pull request as ready for review June 17, 2025 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen A-UI Graphical user interfaces, styles, layouts, and widgets C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

In TextLayout, when deleting all TextSpans, TextLayout still retains the last text
4 participants