mirror of
https://github.com/lexmount/moli.git
synced 2026-11-03 16:07:36 +00:00
perf(layout): keep numeric scratch out of retained boxes
Keep pass-local Taffy caches on the working world and store resolved Grid tracks and collapsed borders only for their actual owners. Retain construction objects within the unchanged document budget without retaining empty numeric slots.
This commit is contained in:
@@ -253,7 +253,7 @@ where
|
||||
if !layout_box.style.is_visible() {
|
||||
return PaintCullBounds::Empty;
|
||||
}
|
||||
let Some(borders) = layout_box.collapsed_table_borders.as_ref() else {
|
||||
let Some(borders) = projection.world.collapsed_table_borders.get(&id) else {
|
||||
return PaintCullBounds::Empty;
|
||||
};
|
||||
let mut bounds = PaintCullBounds::Empty;
|
||||
|
||||
@@ -20,7 +20,7 @@ pub(super) fn project_collapsed_table_borders<N>(
|
||||
if !layout_box.style.is_visible() {
|
||||
return;
|
||||
}
|
||||
let Some(borders) = layout_box.collapsed_table_borders.as_ref() else {
|
||||
let Some(borders) = projection.world.collapsed_table_borders.get(&id) else {
|
||||
return;
|
||||
};
|
||||
let geometry = &projection.boxes[id.index()];
|
||||
|
||||
@@ -969,7 +969,10 @@ where
|
||||
},
|
||||
horizontal: layout_box.style.uses_horizontal_writing_mode(),
|
||||
});
|
||||
let resolved_grid_tracks = layout_box.resolved_grid_tracks.clone();
|
||||
let resolved_grid_tracks = world
|
||||
.resolved_grid_tracks
|
||||
.get(&LayoutBoxId::from_index(geometry.id.index()))
|
||||
.cloned();
|
||||
FrozenLayoutBox {
|
||||
geometry,
|
||||
scroll_extent,
|
||||
|
||||
@@ -113,7 +113,7 @@ fn emit_context<N>(
|
||||
kind: UnitKind::Contents,
|
||||
sequence: next_sequence(sequence),
|
||||
});
|
||||
if world.boxes[root.index()].collapsed_table_borders.is_some() {
|
||||
if world.collapsed_table_borders.contains_key(&root) {
|
||||
collection.table_collapsed_borders.push(PaintUnit {
|
||||
id: root,
|
||||
kind: UnitKind::TableCollapsedBorders,
|
||||
@@ -245,7 +245,7 @@ fn collect_subtree<N>(
|
||||
for child in ordered_children(world, id) {
|
||||
collect_subtree(world, child, descendant_group, sequence, collection);
|
||||
}
|
||||
if layout_box.collapsed_table_borders.is_some() {
|
||||
if world.collapsed_table_borders.contains_key(&id) {
|
||||
push_unit(
|
||||
collection,
|
||||
group,
|
||||
|
||||
@@ -468,7 +468,7 @@ where
|
||||
{
|
||||
world.boxes[part.index()].collapsed_table_border_part = true;
|
||||
}
|
||||
world.boxes[root.index()].collapsed_table_borders = Some(borders);
|
||||
world.collapsed_table_borders.insert(root, borders);
|
||||
}
|
||||
|
||||
pub(super) fn set_collapsed_border_geometry<N>(
|
||||
@@ -479,7 +479,7 @@ pub(super) fn set_collapsed_border_geometry<N>(
|
||||
) where
|
||||
N: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
if let Some(borders) = world.boxes[root.index()].collapsed_table_borders.as_mut() {
|
||||
if let Some(borders) = world.collapsed_table_borders.get_mut(&root) {
|
||||
borders.set_geometry(columns, rows);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use parley::{AlignmentOptions, PositionedLayoutItem, YieldData};
|
||||
use style::Atom;
|
||||
use taffy::{
|
||||
AbsoluteAxis, AlignContent, AlignContentKeyword, AlignmentSafety, AutoSizeBehavior,
|
||||
AvailableSpace, BlockContext, BlockFormattingContext, BoxSizing, CacheTree, Clear,
|
||||
AvailableSpace, BlockContext, BlockFormattingContext, BoxSizing, Cache, CacheTree, Clear,
|
||||
DetailedGridInfo, Dimension, Display, FlexDirection, FloatDirection, Layout,
|
||||
LayoutBlockContainer, LayoutFlexboxContainer, LayoutGridContainer, LayoutInput, LayoutOutput,
|
||||
LayoutPartialTree, LeafLayoutContext, Line, MaybeMath, MaybeResolve, NodeId, Point,
|
||||
@@ -114,7 +114,7 @@ impl PreparedWorldLayout {
|
||||
}
|
||||
self.feedback_invalidation_marks[id.index()] = true;
|
||||
invalidated.push(id);
|
||||
world.boxes[id.index()].clear_layout_caches();
|
||||
world.clear_layout_caches(id);
|
||||
if let Some(parent) = world.boxes[id.index()].layout_parent {
|
||||
self.feedback_invalidation_worklist.push(parent);
|
||||
}
|
||||
@@ -140,15 +140,16 @@ where
|
||||
N: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
world.prepare_inline_storage();
|
||||
for layout_box in &mut world.boxes {
|
||||
layout_box.clear_layout_caches();
|
||||
world.resolved_grid_tracks.clear();
|
||||
for index in 0..world.boxes.len() {
|
||||
world.clear_layout_caches(LayoutBoxId::from_index(index));
|
||||
let layout_box = &mut world.boxes[index];
|
||||
layout_box.unrounded_layout = Layout::with_order(0);
|
||||
layout_box.final_layout = Layout::with_order(0);
|
||||
layout_box.layout_parent = None;
|
||||
layout_box.layout_children.clear();
|
||||
layout_box.positioned_containing_block = None;
|
||||
layout_box.inline_static_position = None;
|
||||
layout_box.resolved_grid_tracks = None;
|
||||
}
|
||||
|
||||
world.viewport_layout.children.clear();
|
||||
@@ -1720,9 +1721,9 @@ where
|
||||
if self.is_viewport_taffy_node(node_id) {
|
||||
self.viewport_layout.cache.get(inputs)
|
||||
} else {
|
||||
self.boxes[LayoutBoxId::from_taffy(node_id).index()]
|
||||
.cache
|
||||
.get(inputs)
|
||||
self.box_caches
|
||||
.get(LayoutBoxId::from_taffy(node_id).index())
|
||||
.and_then(|cache| cache.get(inputs))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,7 +1738,10 @@ where
|
||||
// first-class overflow dependencies.
|
||||
self.record_numeric_layout_touch(id);
|
||||
}
|
||||
self.boxes[id.index()].cache.store(inputs, output);
|
||||
if self.box_caches.len() <= id.index() {
|
||||
self.box_caches.resize_with(self.boxes.len(), Cache::new);
|
||||
}
|
||||
self.box_caches[id.index()].store(inputs, output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1745,7 +1749,7 @@ where
|
||||
if self.is_viewport_taffy_node(node_id) {
|
||||
self.viewport_layout.cache.clear();
|
||||
} else {
|
||||
self.boxes[LayoutBoxId::from_taffy(node_id).index()].clear_layout_caches();
|
||||
self.clear_layout_caches(LayoutBoxId::from_taffy(node_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1876,8 +1880,8 @@ where
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.boxes[LayoutBoxId::from_taffy(node_id).index()].resolved_grid_tracks =
|
||||
Some(resolved_grid_tracks);
|
||||
self.resolved_grid_tracks
|
||||
.insert(LayoutBoxId::from_taffy(node_id), resolved_grid_tracks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -375,3 +375,51 @@ fn inline_measurement_width_dependencies_remain_in_the_key() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numeric_cache_growth_keeps_box_viewport_and_invalidation_owners_distinct() {
|
||||
let mut world = text_world(false);
|
||||
let output = measure(&mut world, inputs(), true);
|
||||
let input = LayoutInput {
|
||||
run_mode: RunMode::PerformLayout,
|
||||
..inputs()
|
||||
};
|
||||
let root = world.root().to_taffy();
|
||||
assert_eq!(world.cache_get(root, &input), None);
|
||||
world.cache_store(root, &input, output);
|
||||
assert_eq!(world.cache_get(root, &input), Some(output));
|
||||
assert_eq!(
|
||||
world.cache_get(
|
||||
root,
|
||||
&LayoutInput {
|
||||
sizing_purpose: SizingPurpose::IntrinsicContribution,
|
||||
..input
|
||||
}
|
||||
),
|
||||
None
|
||||
);
|
||||
|
||||
// The old viewport ID becomes a real box ID when numeric preparation
|
||||
// appends a box. Its cache must start empty, without losing viewport work.
|
||||
let sibling = world.viewport_taffy_node();
|
||||
let mut viewport_output = output;
|
||||
viewport_output.size.width += 10.0;
|
||||
world.cache_store(sibling, &input, viewport_output);
|
||||
let mut extra = text_world(false).boxes.remove(0);
|
||||
extra.source = Some(1);
|
||||
world.boxes.push(extra);
|
||||
assert_eq!(world.cache_get(sibling, &input), None);
|
||||
let viewport = world.viewport_taffy_node();
|
||||
assert_eq!(world.cache_get(viewport, &input), Some(viewport_output));
|
||||
|
||||
let mut sibling_output = output;
|
||||
sibling_output.size.width += 20.0;
|
||||
world.cache_store(sibling, &input, sibling_output);
|
||||
world.cache_clear(root);
|
||||
assert_eq!(world.cache_get(root, &input), None);
|
||||
assert_eq!(world.cache_get(sibling, &input), Some(sibling_output));
|
||||
assert_eq!(world.cache_get(viewport, &input), Some(viewport_output));
|
||||
world.cache_clear(viewport);
|
||||
assert_eq!(world.cache_get(viewport, &input), None);
|
||||
assert_eq!(world.cache_get(sibling, &input), Some(sibling_output));
|
||||
}
|
||||
|
||||
@@ -96,10 +96,7 @@ where
|
||||
layout_box.replaced_context = None;
|
||||
layout_box.replaced_image = None;
|
||||
layout_box.css_images = Default::default();
|
||||
layout_box.collapsed_table_borders = None;
|
||||
layout_box.collapsed_table_border_part = false;
|
||||
layout_box.resolved_grid_tracks = None;
|
||||
layout_box.cache.clear();
|
||||
layout_box.table_measure_cache = None;
|
||||
layout_box.unrounded_layout = taffy::Layout::with_order(0);
|
||||
layout_box.final_layout = taffy::Layout::with_order(0);
|
||||
|
||||
+23
-22
@@ -261,23 +261,11 @@ pub struct LayoutBox<N> {
|
||||
pub(crate) replaced_context: Option<ReplacedContext>,
|
||||
pub(crate) replaced_image: Option<crate::LayoutImageResource>,
|
||||
pub(crate) css_images: crate::source::LayoutCssImageResources,
|
||||
/// Winning collapsed-table edges owned by the table wrapper for this pass.
|
||||
///
|
||||
/// The record contains only resolved numeric/color/style data. It is
|
||||
/// produced before Taffy sizing, completed with grid-line geometry after
|
||||
/// sizing, and consumed once by immutable paint projection.
|
||||
pub(crate) collapsed_table_borders: Option<crate::table::CollapsedTableBorders>,
|
||||
/// Suppresses ordinary per-box border paint for parts participating in a
|
||||
/// collapsed table. Their authored borders have already entered the table
|
||||
/// owner's conflict-resolution grid.
|
||||
pub(crate) collapsed_table_border_part: bool,
|
||||
pub(crate) inline_formatting_context: bool,
|
||||
/// Used Grid tracks produced by the numeric layout pass.
|
||||
///
|
||||
/// This is browser-owned canonical geometry rather than a retained Taffy
|
||||
/// object. It is frozen with the rest of the layout tree for CSSOM reads.
|
||||
pub(crate) resolved_grid_tracks: Option<LayoutResolvedGridTracks>,
|
||||
pub(crate) cache: Cache,
|
||||
pub(crate) table_measure_cache: Option<Box<crate::table::TableMeasureCache>>,
|
||||
pub(crate) unrounded_layout: Layout,
|
||||
pub(crate) final_layout: Layout,
|
||||
@@ -549,13 +537,6 @@ impl Default for ViewportLayoutState {
|
||||
}
|
||||
|
||||
impl<N> LayoutBox<N> {
|
||||
pub(crate) fn clear_layout_caches(&mut self) {
|
||||
self.cache.clear();
|
||||
if let Some(cache) = &mut self.table_measure_cache {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> LayoutBoxKind {
|
||||
self.kind
|
||||
}
|
||||
@@ -649,6 +630,17 @@ where
|
||||
/// large empty slots out of persistent box objects. Retention extracts
|
||||
/// only actual owners into the existing sparse working-set storage.
|
||||
pub(crate) inline_layouts: Vec<Option<InlineFormattingContext>>,
|
||||
/// Numeric caches live for this world only. Allocate dense slots on the
|
||||
/// first actual store, after construction and numeric-only box growth.
|
||||
/// Keeping empty caches out of LayoutBox avoids retaining their fixed
|
||||
/// measurement arrays with an otherwise reusable construction graph.
|
||||
pub(crate) box_caches: Vec<Cache>,
|
||||
/// Winning collapsed-table edges, computed by the shared table formatter
|
||||
/// and consumed by paint projection. Only actual table owners have entries.
|
||||
pub(crate) collapsed_table_borders: HashMap<LayoutBoxId, crate::table::CollapsedTableBorders>,
|
||||
/// Used Grid tracks from this numeric pass. Freeze the actual owners'
|
||||
/// results for CSSOM; retain neither these records nor empty per-box slots.
|
||||
pub(crate) resolved_grid_tracks: HashMap<LayoutBoxId, LayoutResolvedGridTracks>,
|
||||
pub(crate) source_mapping: HashMap<N, LayoutBoxId>,
|
||||
pub(crate) display_contents_mapping: HashMap<N, Vec<LayoutBoxId>>,
|
||||
pub(crate) root: LayoutBoxId,
|
||||
@@ -695,6 +687,9 @@ where
|
||||
Self {
|
||||
boxes,
|
||||
inline_layouts: Vec::new(),
|
||||
box_caches: Vec::new(),
|
||||
collapsed_table_borders: HashMap::new(),
|
||||
resolved_grid_tracks: HashMap::new(),
|
||||
source_mapping,
|
||||
display_contents_mapping: HashMap::new(),
|
||||
root: LayoutBoxId::from_index(0),
|
||||
@@ -718,6 +713,15 @@ where
|
||||
self.inline_layouts.resize_with(self.boxes.len(), || None);
|
||||
}
|
||||
|
||||
pub(crate) fn clear_layout_caches(&mut self, id: LayoutBoxId) {
|
||||
if let Some(cache) = self.box_caches.get_mut(id.index()) {
|
||||
cache.clear();
|
||||
}
|
||||
if let Some(cache) = &mut self.boxes[id.index()].table_measure_cache {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root(&self) -> LayoutBoxId {
|
||||
self.root
|
||||
}
|
||||
@@ -1260,11 +1264,8 @@ where
|
||||
replaced_context: None,
|
||||
replaced_image: None,
|
||||
css_images: crate::source::LayoutCssImageResources::default(),
|
||||
collapsed_table_borders: None,
|
||||
collapsed_table_border_part: false,
|
||||
inline_formatting_context: false,
|
||||
resolved_grid_tracks: None,
|
||||
cache: Cache::new(),
|
||||
table_measure_cache: None,
|
||||
unrounded_layout: Layout::with_order(0),
|
||||
final_layout: Layout::with_order(0),
|
||||
|
||||
@@ -51,6 +51,7 @@ fn realtime_table_reuses_unchanged_text_results_within_the_default_budget() {
|
||||
let cold = main_stats(&vm).unwrap();
|
||||
assert!(cold.retained_boxes >= 2 * ITEMS, "{cold:?}");
|
||||
assert!(cold.estimated_construction_bytes > 0, "{cold:?}");
|
||||
assert_eq!(cold.retained_owned_boxes, cold.retained_boxes, "{cold:?}");
|
||||
assert!(cold.estimated_retained_bytes <= moli_layout::DEFAULT_DOCUMENT_WORKING_SET_BYTES);
|
||||
let builds = vm.layout_pass_observability_for_test().1;
|
||||
vm.eval("sheet.querySelector('.item').style.width='333px'")
|
||||
|
||||
Reference in New Issue
Block a user