This commit is contained in:
ickshonpe 2025-07-17 15:12:30 +02:00 committed by GitHub
commit 4f50daf682
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 48 deletions

View File

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

View File

@ -76,7 +76,7 @@ impl ComputedTextBlock {
/// Indicates if the text needs to be refreshed in [`TextLayoutInfo`]. /// Indicates if the text needs to be refreshed in [`TextLayoutInfo`].
/// ///
/// Updated automatically by [`detect_text_needs_rerender`] and cleared /// Updated automatically by [`detect_text_root_needs_rerender`] and [`detect_text_span_needs_rerender`], and cleared
/// by [`TextPipeline`](crate::TextPipeline) methods. /// by [`TextPipeline`](crate::TextPipeline) methods.
pub fn needs_rerender(&self) -> bool { pub fn needs_rerender(&self) -> bool {
self.needs_rerender self.needs_rerender
@ -472,11 +472,10 @@ pub enum FontSmoothing {
/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`. /// 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 /// Generic over the root text component.
/// 2d or `Text`/[`TextSpan`] for UI. pub fn detect_text_root_needs_rerender<Root: Component>(
pub fn detect_text_needs_rerender<Root: Component>( mut changed_roots: Query<
changed_roots: Query< &mut ComputedTextBlock,
Entity,
( (
Or<( Or<(
Changed<Root>, Changed<Root>,
@ -489,6 +488,30 @@ pub fn detect_text_needs_rerender<Root: Component>(
With<TextLayout>, 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< changed_spans: Query<
(Entity, Option<&ChildOf>, Has<TextLayout>), (Entity, Option<&ChildOf>, Has<TextLayout>),
( (
@ -509,20 +532,6 @@ pub fn detect_text_needs_rerender<Root: Component>(
Has<TextSpan>, 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 entity:
// - Span component changed. // - Span component changed.
// - Span TextFont changed. // - Span TextFont changed.
@ -530,16 +539,15 @@ pub fn detect_text_needs_rerender<Root: Component>(
for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() { for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() {
if has_text_block { if has_text_block {
once!(warn!("found entity {} with a TextSpan that has a TextLayout, which should only be on root \ 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", text entities; this warning only prints once",
entity, core::any::type_name::<Root>())); entity));
} }
let Some(span_child_of) = maybe_span_child_of else { let Some(span_child_of) = maybe_span_child_of else {
once!(warn!( once!(warn!(
"found entity {} with a TextSpan that has no parent; it should have an ancestor \ "found entity {} with a TextSpan that has no parent; it should have an ancestor \
with a root text component ({}); this warning only prints once", with a root text component; this warning only prints once",
entity, entity
core::any::type_name::<Root>()
)); ));
continue; continue;
}; };
@ -568,9 +576,8 @@ pub fn detect_text_needs_rerender<Root: Component>(
let Some(next_child_of) = maybe_child_of else { let Some(next_child_of) = maybe_child_of else {
once!(warn!( once!(warn!(
"found entity {} with a TextSpan that has no ancestor with the root text \ "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, entity,
core::any::type_name::<Root>()
)); ));
break; break;
}; };

View File

@ -389,7 +389,9 @@ mod tests {
use bevy_asset::{load_internal_binary_asset, Handle}; use bevy_asset::{load_internal_binary_asset, Handle};
use bevy_ecs::schedule::IntoScheduleConfigs; use bevy_ecs::schedule::IntoScheduleConfigs;
use crate::{detect_text_needs_rerender, TextIterScratch}; use crate::{
detect_text_root_needs_rerender, detect_text_span_needs_rerender, TextIterScratch,
};
use super::*; use super::*;
@ -409,7 +411,8 @@ mod tests {
.add_systems( .add_systems(
Update, Update,
( (
detect_text_needs_rerender::<Text2d>, detect_text_root_needs_rerender::<Text2d>,
detect_text_span_needs_rerender,
update_text2d_layout, update_text2d_layout,
calculate_bounds_text2d, calculate_bounds_text2d,
) )

View File

@ -34,6 +34,7 @@ mod layout;
mod stack; mod stack;
mod ui_node; mod ui_node;
use bevy_text::detect_text_span_needs_rerender;
pub use focus::*; pub use focus::*;
pub use geometry::*; pub use geometry::*;
pub use gradients::*; pub use gradients::*;
@ -201,7 +202,7 @@ impl Plugin for UiPlugin {
let ui_layout_system_config = ui_layout_system_config let ui_layout_system_config = ui_layout_system_config
// Text and Text2D operate on disjoint sets of entities // Text and Text2D operate on disjoint sets of entities
.ambiguous_with(bevy_text::update_text2d_layout) .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( app.add_systems(
PostUpdate, PostUpdate,
@ -252,26 +253,27 @@ fn build_text_interop(app: &mut App) {
PostUpdate, PostUpdate,
( (
( (
bevy_text::detect_text_needs_rerender::<Text>, bevy_text::detect_text_root_needs_rerender::<Text>,
widget::measure_text_system, widget::measure_text_system,
) )
.chain() .chain()
.in_set(UiSystems::Content) .in_set(UiSystems::Content)
// Text and Text2d are independent. // 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>` // Potential conflict: `Assets<Image>`
// Since both systems will only ever insert new [`Image`] assets, // Since both systems will only ever insert new [`Image`] assets,
// they will never observe each other's effects. // they will never observe each other's effects.
.ambiguous_with(bevy_text::update_text2d_layout) .ambiguous_with(bevy_text::update_text2d_layout)
// We assume Text is on disjoint UI entities to ImageNode and UiTextureAtlasImage // 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. // 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 widget::text_system
.in_set(UiSystems::PostLayout) .in_set(UiSystems::PostLayout)
.after(bevy_text::remove_dropped_font_atlas_sets) .after(bevy_text::remove_dropped_font_atlas_sets)
.before(bevy_asset::AssetEventSystems) .before(bevy_asset::AssetEventSystems)
// Text2d and bevy_ui text are entirely on separate entities // 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::update_text2d_layout)
.ambiguous_with(bevy_text::calculate_bounds_text2d), .ambiguous_with(bevy_text::calculate_bounds_text2d),
), ),

View File

@ -111,7 +111,7 @@ fn main() {
} }
if args.recompute_text { if args.recompute_text {
app.add_systems(Update, |mut text_query: Query<&mut Text>| { app.add_systems(Update, |mut text_query: Query<&mut TextSpan>| {
text_query text_query
.iter_mut() .iter_mut()
.for_each(|mut text| text.set_changed()); .for_each(|mut text| text.set_changed());