Reduce steady-state allocations in ui_stack_system (#12413)
# Objective - Reduce allocations on the UI hot path. ## Solution - Cache buffers used by the `ui_stack_system`. ## Follow-Up - `sort_by_key` is potentially-allocating. It might be worthwhile to include the child index as part of the sort-key and use unstable sort.
This commit is contained in:
parent
4c47e31be6
commit
535250c088
@ -15,22 +15,42 @@ pub struct UiStack {
|
||||
pub uinodes: Vec<Entity>,
|
||||
}
|
||||
|
||||
/// Caches stacking context buffers for use in [`ui_stack_system`].
|
||||
#[derive(Default)]
|
||||
pub(crate) struct StackingContextCache {
|
||||
inner: Vec<StackingContext>,
|
||||
}
|
||||
|
||||
impl StackingContextCache {
|
||||
fn pop(&mut self) -> StackingContext {
|
||||
self.inner.pop().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn push(&mut self, mut context: StackingContext) {
|
||||
for entry in context.entries.drain(..) {
|
||||
self.push(entry.stack);
|
||||
}
|
||||
self.inner.push(context);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StackingContext {
|
||||
pub entries: Vec<StackingContextEntry>,
|
||||
entries: Vec<StackingContextEntry>,
|
||||
}
|
||||
|
||||
struct StackingContextEntry {
|
||||
pub z_index: i32,
|
||||
pub entity: Entity,
|
||||
pub stack: StackingContext,
|
||||
z_index: i32,
|
||||
entity: Entity,
|
||||
stack: StackingContext,
|
||||
}
|
||||
|
||||
/// Generates the render stack for UI nodes.
|
||||
///
|
||||
/// First generate a UI node tree (`StackingContext`) based on z-index.
|
||||
/// Then flatten that tree into back-to-front ordered `UiStack`.
|
||||
pub fn ui_stack_system(
|
||||
pub(crate) fn ui_stack_system(
|
||||
mut cache: Local<StackingContextCache>,
|
||||
mut ui_stack: ResMut<UiStack>,
|
||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||
zindex_query: Query<&ZIndex, With<Node>>,
|
||||
@ -38,11 +58,12 @@ pub fn ui_stack_system(
|
||||
mut update_query: Query<&mut Node>,
|
||||
) {
|
||||
// Generate `StackingContext` tree
|
||||
let mut global_context = StackingContext::default();
|
||||
let mut global_context = cache.pop();
|
||||
let mut total_entry_count: usize = 0;
|
||||
|
||||
for entity in &root_node_query {
|
||||
insert_context_hierarchy(
|
||||
&mut cache,
|
||||
&zindex_query,
|
||||
&children_query,
|
||||
entity,
|
||||
@ -55,7 +76,8 @@ pub fn ui_stack_system(
|
||||
// Flatten `StackingContext` into `UiStack`
|
||||
ui_stack.uinodes.clear();
|
||||
ui_stack.uinodes.reserve(total_entry_count);
|
||||
fill_stack_recursively(&mut ui_stack.uinodes, &mut global_context);
|
||||
fill_stack_recursively(&mut cache, &mut ui_stack.uinodes, &mut global_context);
|
||||
cache.push(global_context);
|
||||
|
||||
for (i, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||
if let Ok(mut node) = update_query.get_mut(*entity) {
|
||||
@ -66,6 +88,7 @@ pub fn ui_stack_system(
|
||||
|
||||
/// Generate z-index based UI node tree
|
||||
fn insert_context_hierarchy(
|
||||
cache: &mut StackingContextCache,
|
||||
zindex_query: &Query<&ZIndex, With<Node>>,
|
||||
children_query: &Query<&Children>,
|
||||
entity: Entity,
|
||||
@ -73,7 +96,7 @@ fn insert_context_hierarchy(
|
||||
parent_context: Option<&mut StackingContext>,
|
||||
total_entry_count: &mut usize,
|
||||
) {
|
||||
let mut new_context = StackingContext::default();
|
||||
let mut new_context = cache.pop();
|
||||
|
||||
if let Ok(children) = children_query.get(entity) {
|
||||
// Reserve space for all children. In practice, some may not get pushed since
|
||||
@ -82,6 +105,7 @@ fn insert_context_hierarchy(
|
||||
|
||||
for entity in children {
|
||||
insert_context_hierarchy(
|
||||
cache,
|
||||
zindex_query,
|
||||
children_query,
|
||||
*entity,
|
||||
@ -108,16 +132,21 @@ fn insert_context_hierarchy(
|
||||
}
|
||||
|
||||
/// Flatten `StackingContext` (z-index based UI node tree) into back-to-front entities list
|
||||
fn fill_stack_recursively(result: &mut Vec<Entity>, stack: &mut StackingContext) {
|
||||
fn fill_stack_recursively(
|
||||
cache: &mut StackingContextCache,
|
||||
result: &mut Vec<Entity>,
|
||||
stack: &mut StackingContext,
|
||||
) {
|
||||
// Sort entries by ascending z_index, while ensuring that siblings
|
||||
// with the same local z_index will keep their ordering. This results
|
||||
// in `back-to-front` ordering, low z_index = back; high z_index = front.
|
||||
stack.entries.sort_by_key(|e| e.z_index);
|
||||
|
||||
for entry in &mut stack.entries {
|
||||
for mut entry in stack.entries.drain(..) {
|
||||
// Parent node renders before/behind child nodes
|
||||
result.push(entry.entity);
|
||||
fill_stack_recursively(result, &mut entry.stack);
|
||||
fill_stack_recursively(cache, result, &mut entry.stack);
|
||||
cache.push(entry.stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user