
# Objective
Enable using exact World lifetimes during read-only access . This is motivated by the new renderer's need to allow read-only world-only queries to outlive the query itself (but still be constrained by the world lifetime).
For example:
115b170d1f/pipelined/bevy_pbr2/src/render/mod.rs (L774)
## Solution
Split out SystemParam state and world lifetimes and pipe those lifetimes up to read-only Query ops (and add into_inner for Res). According to every safety test I've run so far (except one), this is safe (see the temporary safety test commit). Note that changing the mutable variants to the new lifetimes would allow aliased mutable pointers (try doing that to see how it affects the temporary safety tests).
The new state lifetime on SystemParam does make `#[derive(SystemParam)]` more cumbersome (the current impl requires PhantomData if you don't use both lifetimes). We can make this better by detecting whether or not a lifetime is used in the derive and adjusting accordingly, but that should probably be done in its own pr.
## Why is this a draft?
The new lifetimes break QuerySet safety in one very specific case (see the query_set system in system_safety_test). We need to solve this before we can use the lifetimes given.
This is due to the fact that QuerySet is just a wrapper over Query, which now relies on world lifetimes instead of `&self` lifetimes to prevent aliasing (but in systems, each Query has its own implied lifetime, not a centralized world lifetime). I believe the fix is to rewrite QuerySet to have its own World lifetime (and own the internal reference). This will complicate the impl a bit, but I think it is doable. I'm curious if anyone else has better ideas.
Personally, I think these new lifetimes need to happen. We've gotta have a way to directly tie read-only World queries to the World lifetime. The new renderer is the first place this has come up, but I doubt it will be the last. Worst case scenario we can come up with a second `WorldLifetimeQuery<Q, F = ()>` parameter to enable these read-only scenarios, but I'd rather not add another type to the type zoo.
185 lines
6.3 KiB
Rust
185 lines
6.3 KiB
Rust
use crate::{CalculatedSize, Node, Style, Val};
|
|
use bevy_asset::Assets;
|
|
use bevy_ecs::{
|
|
entity::Entity,
|
|
query::{Changed, Or, QueryState, With, Without},
|
|
system::{Local, Query, QuerySet, Res, ResMut},
|
|
};
|
|
use bevy_math::Size;
|
|
use bevy_render::{
|
|
draw::{Draw, DrawContext, Drawable, OutsideFrustum},
|
|
mesh::Mesh,
|
|
prelude::{Msaa, Visible},
|
|
renderer::RenderResourceBindings,
|
|
texture::Texture,
|
|
};
|
|
use bevy_sprite::{TextureAtlas, QUAD_HANDLE};
|
|
use bevy_text::{DefaultTextPipeline, DrawableText, Font, FontAtlasSet, Text, TextError};
|
|
use bevy_transform::prelude::GlobalTransform;
|
|
use bevy_window::Windows;
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct QueuedText {
|
|
entities: Vec<Entity>,
|
|
}
|
|
|
|
fn scale_value(value: f32, factor: f64) -> f32 {
|
|
(value as f64 * factor) as f32
|
|
}
|
|
|
|
/// Defines how min_size, size, and max_size affects the bounds of a text
|
|
/// block.
|
|
pub fn text_constraint(min_size: Val, size: Val, max_size: Val, scale_factor: f64) -> f32 {
|
|
// Needs support for percentages
|
|
match (min_size, size, max_size) {
|
|
(_, _, Val::Px(max)) => scale_value(max, scale_factor),
|
|
(Val::Px(min), _, _) => scale_value(min, scale_factor),
|
|
(Val::Undefined, Val::Px(size), Val::Undefined) => scale_value(size, scale_factor),
|
|
(Val::Auto, Val::Px(size), Val::Auto) => scale_value(size, scale_factor),
|
|
_ => f32::MAX,
|
|
}
|
|
}
|
|
|
|
/// Computes the size of a text block and updates the TextGlyphs with the
|
|
/// new computed glyphs from the layout
|
|
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
|
pub fn text_system(
|
|
mut queued_text: Local<QueuedText>,
|
|
mut last_scale_factor: Local<f64>,
|
|
mut textures: ResMut<Assets<Texture>>,
|
|
fonts: Res<Assets<Font>>,
|
|
windows: Res<Windows>,
|
|
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
|
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
|
|
mut text_pipeline: ResMut<DefaultTextPipeline>,
|
|
mut text_queries: QuerySet<(
|
|
QueryState<Entity, Or<(Changed<Text>, Changed<Style>)>>,
|
|
QueryState<Entity, (With<Text>, With<Style>)>,
|
|
QueryState<(&Text, &Style, &mut CalculatedSize)>,
|
|
)>,
|
|
) {
|
|
let scale_factor = if let Some(window) = windows.get_primary() {
|
|
window.scale_factor()
|
|
} else {
|
|
1.
|
|
};
|
|
|
|
let inv_scale_factor = 1. / scale_factor;
|
|
|
|
#[allow(clippy::float_cmp)]
|
|
if *last_scale_factor == scale_factor {
|
|
// Adds all entities where the text or the style has changed to the local queue
|
|
for entity in text_queries.q0().iter() {
|
|
queued_text.entities.push(entity);
|
|
}
|
|
} else {
|
|
// If the scale factor has changed, queue all text
|
|
for entity in text_queries.q1().iter() {
|
|
queued_text.entities.push(entity);
|
|
}
|
|
*last_scale_factor = scale_factor;
|
|
}
|
|
|
|
if queued_text.entities.is_empty() {
|
|
return;
|
|
}
|
|
|
|
// Computes all text in the local queue
|
|
let mut new_queue = Vec::new();
|
|
let mut query = text_queries.q2();
|
|
for entity in queued_text.entities.drain(..) {
|
|
if let Ok((text, style, mut calculated_size)) = query.get_mut(entity) {
|
|
let node_size = Size::new(
|
|
text_constraint(
|
|
style.min_size.width,
|
|
style.size.width,
|
|
style.max_size.width,
|
|
scale_factor,
|
|
),
|
|
text_constraint(
|
|
style.min_size.height,
|
|
style.size.height,
|
|
style.max_size.height,
|
|
scale_factor,
|
|
),
|
|
);
|
|
|
|
match text_pipeline.queue_text(
|
|
entity,
|
|
&fonts,
|
|
&text.sections,
|
|
scale_factor,
|
|
text.alignment,
|
|
node_size,
|
|
&mut *font_atlas_set_storage,
|
|
&mut *texture_atlases,
|
|
&mut *textures,
|
|
) {
|
|
Err(TextError::NoSuchFont) => {
|
|
// There was an error processing the text layout, let's add this entity to the
|
|
// queue for further processing
|
|
new_queue.push(entity);
|
|
}
|
|
Err(e @ TextError::FailedToAddGlyph(_)) => {
|
|
panic!("Fatal error when processing text: {}.", e);
|
|
}
|
|
Ok(()) => {
|
|
let text_layout_info = text_pipeline.get_glyphs(&entity).expect(
|
|
"Failed to get glyphs from the pipeline that have just been computed",
|
|
);
|
|
calculated_size.size = Size {
|
|
width: scale_value(text_layout_info.size.width, inv_scale_factor),
|
|
height: scale_value(text_layout_info.size.height, inv_scale_factor),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
queued_text.entities = new_queue;
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
|
|
pub fn draw_text_system(
|
|
mut context: DrawContext,
|
|
msaa: Res<Msaa>,
|
|
windows: Res<Windows>,
|
|
meshes: Res<Assets<Mesh>>,
|
|
mut render_resource_bindings: ResMut<RenderResourceBindings>,
|
|
text_pipeline: Res<DefaultTextPipeline>,
|
|
mut query: Query<
|
|
(Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform),
|
|
Without<OutsideFrustum>,
|
|
>,
|
|
) {
|
|
let scale_factor = if let Some(window) = windows.get_primary() {
|
|
window.scale_factor()
|
|
} else {
|
|
1.
|
|
};
|
|
|
|
let font_quad = meshes.get(&QUAD_HANDLE).unwrap();
|
|
let vertex_buffer_layout = font_quad.get_vertex_buffer_layout();
|
|
|
|
for (entity, mut draw, visible, text, node, global_transform) in query.iter_mut() {
|
|
if !visible.is_visible {
|
|
continue;
|
|
}
|
|
|
|
if let Some(text_glyphs) = text_pipeline.get_glyphs(&entity) {
|
|
let mut drawable_text = DrawableText {
|
|
render_resource_bindings: &mut render_resource_bindings,
|
|
global_transform: *global_transform,
|
|
scale_factor: scale_factor as f32,
|
|
msaa: &msaa,
|
|
text_glyphs: &text_glyphs.glyphs,
|
|
font_quad_vertex_layout: &vertex_buffer_layout,
|
|
sections: &text.sections,
|
|
alignment_offset: (node.size / -2.0).extend(0.0),
|
|
};
|
|
|
|
drawable_text.draw(&mut draw, &mut context).unwrap();
|
|
}
|
|
}
|
|
}
|