Split detect_text_needs_rerender into two systems detect_text_root_needs_rerender and detect_text_spawn_needs_rerender.

This avoids checking `TextSpan` nodes for changes twice.
This commit is contained in:
ickshonpe 2025-05-30 15:01:42 +01:00
parent 0ee8bf978c
commit d354bfeebc
3 changed files with 59 additions and 44 deletions

View File

@ -117,19 +117,25 @@ impl Plugin for TextPlugin {
.add_systems(
PostUpdate,
(
remove_dropped_font_atlas_sets.before(AssetEventSystems),
detect_text_needs_rerender::<Text2d>,
update_text2d_layout
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text2d_layout`
// will never modify a pre-existing `Image` asset.
.ambiguous_with(CameraUpdateSystems),
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
)
.chain()
.in_set(Text2dUpdateSystems)
.after(AnimationSystems),
(
remove_dropped_font_atlas_sets.before(AssetEventSystems),
detect_text_root_needs_rerender::<Text2d>,
update_text2d_layout
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
// will only ever observe its own render target, and `update_text2d_layout`
// will never modify a pre-existing `Image` asset.
.ambiguous_with(CameraUpdateSystems),
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
)
.chain()
.in_set(Text2dUpdateSystems)
.after(AnimationSystems),
detect_text_span_needs_rerender
.before(Text2dUpdateSystems)
.ambiguous_with(CameraUpdateSystems)
.after(AnimationSystems),
),
)
.add_systems(Last, trim_cosmic_cache);

View File

@ -477,11 +477,10 @@ pub enum FontSmoothing {
/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`.
///
/// 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<Root: Component>(
changed_roots: Query<
Entity,
/// Generic over the root text component.
pub fn detect_text_root_needs_rerender<Root: Component>(
mut changed_roots: Query<
&mut ComputedTextBlock,
(
Or<(
Changed<Root>,
@ -494,6 +493,30 @@ pub fn detect_text_needs_rerender<Root: Component>(
With<TextLayout>,
),
>,
roots_without_computed_block: Query<Entity, (With<Root>, Without<ComputedTextBlock>)>,
) {
// Root entity:
// - Root component changed.
// - TextFont on root changed.
// - TextLayout changed.
// - Root children changed (can include additions and removals).
for mut computed in changed_roots.iter_mut() {
computed.needs_rerender = true;
}
for entity in roots_without_computed_block.iter() {
// If the root entity does not have a ComputedTextBlock, then it needs one.
// This can happen if the root was spawned without a ComputedTextBlock, or if it was removed.
once!(warn!(
"found entity {} with a root text component ({}) that has no ComputedTextBlock; this warning only prints once",
entity,
core::any::type_name::<Root>()
));
}
}
// System that detects changes to text spans and sets `ComputedTextBlock::should_rerender`.
pub fn detect_text_span_needs_rerender(
changed_spans: Query<
(Entity, Option<&ChildOf>, Has<TextLayout>),
(
@ -514,20 +537,6 @@ pub fn detect_text_needs_rerender<Root: Component>(
Has<TextSpan>,
)>,
) {
// Root entity:
// - Root component changed.
// - TextFont on root changed.
// - TextLayout changed.
// - Root children changed (can include additions and removals).
for root in changed_roots.iter() {
let Ok((_, Some(mut computed), _)) = computed.get_mut(root) else {
once!(warn!("found entity {} with a root text component ({}) but no ComputedTextBlock; this warning only \
prints once", root, core::any::type_name::<Root>()));
continue;
};
computed.needs_rerender = true;
}
// Span entity:
// - Span component changed.
// - Span TextFont changed.
@ -535,16 +544,15 @@ pub fn detect_text_needs_rerender<Root: Component>(
for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() {
if has_text_block {
once!(warn!("found entity {} with a TextSpan that has a TextLayout, which should only be on root \
text entities (that have {}); this warning only prints once",
entity, core::any::type_name::<Root>()));
text entities; this warning only prints once",
entity));
}
let Some(span_child_of) = maybe_span_child_of else {
once!(warn!(
"found entity {} with a TextSpan that has no parent; it should have an ancestor \
with a root text component ({}); this warning only prints once",
entity,
core::any::type_name::<Root>()
with a root text component; this warning only prints once",
entity
));
continue;
};
@ -573,9 +581,8 @@ pub fn detect_text_needs_rerender<Root: Component>(
let Some(next_child_of) = maybe_child_of else {
once!(warn!(
"found entity {} with a TextSpan that has no ancestor with the root text \
component ({}); this warning only prints once",
component; this warning only prints once",
entity,
core::any::type_name::<Root>()
));
break;
};

View File

@ -34,6 +34,7 @@ mod render;
mod stack;
mod ui_node;
use bevy_text::detect_text_span_needs_rerender;
pub use focus::*;
pub use geometry::*;
pub use gradients::*;
@ -218,7 +219,7 @@ impl Plugin for UiPlugin {
let ui_layout_system_config = ui_layout_system_config
// Text and Text2D operate on disjoint sets of entities
.ambiguous_with(bevy_text::update_text2d_layout)
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>);
.ambiguous_with(bevy_text::detect_text_root_needs_rerender::<bevy_text::Text2d>);
app.add_systems(
PostUpdate,
@ -289,26 +290,27 @@ fn build_text_interop(app: &mut App) {
PostUpdate,
(
(
bevy_text::detect_text_needs_rerender::<Text>,
bevy_text::detect_text_root_needs_rerender::<Text>,
widget::measure_text_system,
)
.chain()
.in_set(UiSystems::Content)
// Text and Text2d are independent.
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
.ambiguous_with(bevy_text::detect_text_root_needs_rerender::<bevy_text::Text2d>)
// Potential conflict: `Assets<Image>`
// Since both systems will only ever insert new [`Image`] assets,
// they will never observe each other's effects.
.ambiguous_with(bevy_text::update_text2d_layout)
// We assume Text is on disjoint UI entities to ImageNode and UiTextureAtlasImage
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
.ambiguous_with(widget::update_image_content_size_system),
.ambiguous_with(widget::update_image_content_size_system)
.after(detect_text_span_needs_rerender),
widget::text_system
.in_set(UiSystems::PostLayout)
.after(bevy_text::remove_dropped_font_atlas_sets)
.before(bevy_asset::AssetEventSystems)
// Text2d and bevy_ui text are entirely on separate entities
.ambiguous_with(bevy_text::detect_text_needs_rerender::<bevy_text::Text2d>)
.ambiguous_with(bevy_text::detect_text_root_needs_rerender::<bevy_text::Text2d>)
.ambiguous_with(bevy_text::update_text2d_layout)
.ambiguous_with(bevy_text::calculate_bounds_text2d),
),