mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Add measurement cache
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
use super::MeasureCache;
|
||||||
use crate::{
|
use crate::{
|
||||||
AtomKind, AtomLayout, FontSelection, Id, IntoSizedArgs, IntoSizedResult, SizedAtom, Ui,
|
AtomKind, AtomLayout, FontSelection, Id, IntoSizedArgs, IntoSizedResult, SizedAtom, Ui,
|
||||||
};
|
};
|
||||||
@@ -124,6 +125,7 @@ impl<'a> Atom<'a> {
|
|||||||
mut available_size: Vec2,
|
mut available_size: Vec2,
|
||||||
mut wrap_mode: Option<TextWrapMode>,
|
mut wrap_mode: Option<TextWrapMode>,
|
||||||
fallback_font: FontSelection,
|
fallback_font: FontSelection,
|
||||||
|
cache: &mut MeasureCache<'a>,
|
||||||
) -> SizedAtom<'a> {
|
) -> SizedAtom<'a> {
|
||||||
if !self.shrink && self.max_size.x.is_infinite() {
|
if !self.shrink && self.max_size.x.is_infinite() {
|
||||||
wrap_mode = Some(TextWrapMode::Extend);
|
wrap_mode = Some(TextWrapMode::Extend);
|
||||||
@@ -149,6 +151,7 @@ impl<'a> Atom<'a> {
|
|||||||
wrap_mode,
|
wrap_mode,
|
||||||
fallback_font,
|
fallback_font,
|
||||||
},
|
},
|
||||||
|
cache,
|
||||||
);
|
);
|
||||||
|
|
||||||
let size = self
|
let size = self
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ impl<'a> AtomKind<'a> {
|
|||||||
wrap_mode,
|
wrap_mode,
|
||||||
fallback_font,
|
fallback_font,
|
||||||
}: IntoSizedArgs,
|
}: IntoSizedArgs,
|
||||||
|
cache: &mut super::MeasureCache<'a>,
|
||||||
) -> IntoSizedResult<'a> {
|
) -> IntoSizedResult<'a> {
|
||||||
match self {
|
match self {
|
||||||
AtomKind::Text(text) => {
|
AtomKind::Text(text) => {
|
||||||
@@ -131,8 +132,10 @@ impl<'a> AtomKind<'a> {
|
|||||||
AtomKind::Layout(layout) => {
|
AtomKind::Layout(layout) => {
|
||||||
// Measure at the natural size for the parent's sizing, but keep a shared handle to
|
// Measure at the natural size for the parent's sizing, but keep a shared handle to
|
||||||
// the original layout so a grown atom can be re-measured at its painted size in
|
// the original layout so a grown atom can be re-measured at its painted size in
|
||||||
// `paint_at` (cheap `Arc` clone, no deep copy).
|
// `paint_at` (cheap `Rc` clone, no deep copy). `measure_rc` shares the `cache`
|
||||||
let sized = layout.measure(ui, available_size);
|
// (keyed by the `Rc`'s identity) so a deep tree of `grow` layouts doesn't
|
||||||
|
// re-measure its descendants exponentially.
|
||||||
|
let sized = AtomLayout::measure_rc(layout, ui, available_size, cache);
|
||||||
IntoSizedResult {
|
IntoSizedResult {
|
||||||
intrinsic_size: sized.intrinsic_size,
|
intrinsic_size: sized.intrinsic_size,
|
||||||
sized: SizedAtomKind::Layout {
|
sized: SizedAtomKind::Layout {
|
||||||
|
|||||||
@@ -6,9 +6,19 @@ use emath::{Align2, GuiRounding as _, NumExt as _, Rect, Vec2};
|
|||||||
use epaint::text::TextWrapMode;
|
use epaint::text::TextWrapMode;
|
||||||
use epaint::{Color32, Galley};
|
use epaint::{Color32, Galley};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Frame-pass-local memoization cache for [`AtomLayout::measure_rc`].
|
||||||
|
///
|
||||||
|
/// Keyed by an [`Rc::as_ptr`] identity plus the available-size bits. Both are stable within a
|
||||||
|
/// single top-level measure pass (nested layouts are held alive via `Rc`), so repeatedly measuring
|
||||||
|
/// the same nested layout at the same size — which a deep tree of `grow` layouts does `O(2^depth)`
|
||||||
|
/// times — becomes a cache hit instead of a full re-measure.
|
||||||
|
pub(crate) type MeasureCache<'a> = HashMap<(usize, u64), SizedAtomLayout<'a>>;
|
||||||
|
|
||||||
/// The `(main, cross)` axis indices for `direction`, for indexing a [`Vec2`] (0 = x, 1 = y).
|
/// The `(main, cross)` axis indices for `direction`, for indexing a [`Vec2`] (0 = x, 1 = y).
|
||||||
#[inline]
|
#[inline]
|
||||||
fn main_cross_axis(direction: Direction) -> (usize, usize) {
|
fn main_cross_axis(direction: Direction) -> (usize, usize) {
|
||||||
@@ -341,6 +351,44 @@ impl<'a> AtomLayout<'a> {
|
|||||||
/// clamped by `max_size`/`min_size`, exactly like [`Self::allocate`] does with
|
/// clamped by `max_size`/`min_size`, exactly like [`Self::allocate`] does with
|
||||||
/// [`Ui::available_size`].
|
/// [`Ui::available_size`].
|
||||||
pub fn measure(&self, ui: &Ui, available_size: Vec2) -> SizedAtomLayout<'a> {
|
pub fn measure(&self, ui: &Ui, available_size: Vec2) -> SizedAtomLayout<'a> {
|
||||||
|
self.measure_impl(ui, available_size, &mut MeasureCache::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Measure a nested layout held by an [`Rc`], memoizing the result in `cache`.
|
||||||
|
///
|
||||||
|
/// A grown nested `Layout` atom is re-measured (the cross-after-main reflow) at its grown
|
||||||
|
/// size, recursively. Without memoization a deep tree of `grow` layouts re-measures its
|
||||||
|
/// descendants `O(2^depth)` times. Keyed by the layout's [`Rc::as_ptr`] identity and the
|
||||||
|
/// available size — both stable within a pass — repeated `(layout, size)` measures become
|
||||||
|
/// cache hits. The `Rc` is held by the caller (the `Layout` atom / reflow source), which is
|
||||||
|
/// why the identity lives here rather than in [`Self::measure_impl`].
|
||||||
|
pub(crate) fn measure_rc(
|
||||||
|
layout: &Rc<Self>,
|
||||||
|
ui: &Ui,
|
||||||
|
available_size: Vec2,
|
||||||
|
cache: &mut MeasureCache<'a>,
|
||||||
|
) -> SizedAtomLayout<'a> {
|
||||||
|
let key = (
|
||||||
|
Rc::as_ptr(layout) as usize,
|
||||||
|
(u64::from(available_size.x.to_bits()) << 32) | u64::from(available_size.y.to_bits()),
|
||||||
|
);
|
||||||
|
if let Some(cached) = cache.get(&key) {
|
||||||
|
return cached.clone();
|
||||||
|
}
|
||||||
|
let result = layout.measure_impl(ui, available_size, cache);
|
||||||
|
cache.insert(key, result.clone());
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The measure body. Threads `cache` so nested [`Rc`] layouts are memoized via
|
||||||
|
/// [`Self::measure_rc`]; it does not memoize its own result (a top-level layout is measured
|
||||||
|
/// once, and a nested one is keyed by its `Rc` at the call site).
|
||||||
|
pub(crate) fn measure_impl(
|
||||||
|
&self,
|
||||||
|
ui: &Ui,
|
||||||
|
available_size: Vec2,
|
||||||
|
cache: &mut MeasureCache<'a>,
|
||||||
|
) -> SizedAtomLayout<'a> {
|
||||||
let atoms = &self.atoms;
|
let atoms = &self.atoms;
|
||||||
let frame = self.frame;
|
let frame = self.frame;
|
||||||
let sense = self.sense;
|
let sense = self.sense;
|
||||||
@@ -444,6 +492,7 @@ impl<'a> AtomLayout<'a> {
|
|||||||
available_inner_size,
|
available_inner_size,
|
||||||
Some(wrap_mode),
|
Some(wrap_mode),
|
||||||
fallback_font.clone(),
|
fallback_font.clone(),
|
||||||
|
cache,
|
||||||
);
|
);
|
||||||
let size = sized.size;
|
let size = sized.size;
|
||||||
|
|
||||||
@@ -473,6 +522,7 @@ impl<'a> AtomLayout<'a> {
|
|||||||
available_size_for_shrink_item,
|
available_size_for_shrink_item,
|
||||||
Some(wrap_mode),
|
Some(wrap_mode),
|
||||||
fallback_font,
|
fallback_font,
|
||||||
|
cache,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let mut item = item.clone();
|
let mut item = item.clone();
|
||||||
@@ -482,6 +532,7 @@ impl<'a> AtomLayout<'a> {
|
|||||||
available_size_for_shrink_item,
|
available_size_for_shrink_item,
|
||||||
Some(wrap_mode),
|
Some(wrap_mode),
|
||||||
fallback_font,
|
fallback_font,
|
||||||
|
cache,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let size = sized.size;
|
let size = sized.size;
|
||||||
@@ -553,7 +604,7 @@ impl<'a> AtomLayout<'a> {
|
|||||||
sized.size[main_axis] + grow_main,
|
sized.size[main_axis] + grow_main,
|
||||||
available_inner_size[cross_axis],
|
available_inner_size[cross_axis],
|
||||||
);
|
);
|
||||||
let remeasured = source.measure(ui, grown);
|
let remeasured = AtomLayout::measure_rc(source, ui, grown, cache);
|
||||||
sized.size[cross_axis] = remeasured.outer_size[cross_axis];
|
sized.size[cross_axis] = remeasured.outer_size[cross_axis];
|
||||||
**inner = remeasured;
|
**inner = remeasured;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -594,6 +594,7 @@ impl TextEdit<'_> {
|
|||||||
Vec2::new(available_inner_width, f32::INFINITY),
|
Vec2::new(available_inner_width, f32::INFINITY),
|
||||||
Some(TextWrapMode::Extend),
|
Some(TextWrapMode::Extend),
|
||||||
FontSelection::default(),
|
FontSelection::default(),
|
||||||
|
&mut Default::default(),
|
||||||
)
|
)
|
||||||
.size
|
.size
|
||||||
.x
|
.x
|
||||||
|
|||||||
Reference in New Issue
Block a user