forked from 1b-agent/opencadstudio-upstream
perf(ribbon): stop building and laying out 5 panel renderings per frame
Each ribbon panel was rendered five ways every frame so CollapsePanels could roll back to any density at layout time: full, compact, button, tight and a full-size flyout that duplicated the full rendering. The layout pass ran four full measure-layouts per panel even in forced modes, and the degradation decision was an untested u8 inline in Ribbon::view. Now the choice is a pure, unit-tested decide_levels() whose levels are a typed Level enum (Full/Compact/Collapsed/Tight). Panel carries its four renderings as elements: [Element; 4] indexed by Level, with a slot(i, level) helper for the tree stride. Forced modes (Full/Compact/Collapsed) measure nothing at all and derive the row height from the placed panel. The open flyout draws the panel's own `full` rendering inside a self-drawn box (identical weakest-bg + 1px neutral-border to the container it replaced), so no duplicate render_group is built per panel per frame. Level, Panel, Widths, CollapsePanels and decide_levels are pub(crate); CollapseMode stays pub (re-exported). Tests (collapse.rs tests module, added RED first): - forced_modes_pin_every_panel - auto_huge_width_keeps_everything_full - auto_degrades_from_the_right_one_panel_at_a_time - auto_stops_degrading_once_the_row_fits - auto_escalates_a_single_panel_past_collapsed_to_tight - auto_cascade_drops_every_collapsed_panel_to_tight - auto_runs_the_compact_phase_across_the_row_first - no_panels_stays_empty - single_panel_walks_the_whole_ladder - zero_widths_never_overflow - endless_row_degrades_everything_to_tight - unbounded_row_keeps_everything_full - nan_budget_degrades_both_phases_but_no_tight_cascade - exact_fit_at_full_stays_full - matches_the_pre_refactor_algorithm: byte-equivalence sweep against the verbatim pre-refactor u8 algorithm (280 width/budget combos incl. the degenerate 0/0/inf/NaN inputs), proving the decision logic is unchanged. Measured with the committed #[ignore]d benchmark (`cargo test --lib --release -- --ignored --nocapture bench_ribbon_view_construction`) run as one instrument across pre-change / this change: Ribbon::view() construction ~676 -> ~425 us/frame (~37% of view construction saved); forced modes run 1 layout call per panel per frame instead of 5. Verification: cargo build clean; cargo test --lib = 382 passed (367 baseline + 15 new ribbon tests) / 2 known pre-existing failures unchanged (save_then_open_round_trips, tilted_ucs_places_planar_entities_with_the_plane_normal).
This commit is contained in:
1 parent
6df23ad8c2
commit
32c3844526
2 files changed
+572
-192
No files matched your search
+77
-30
@@ -586,7 +586,7 @@ impl Ribbon {
|
||||
let ts = self.toggle_state(show_block_palette);
|
||||
Panel {
|
||||
id: g.title.to_string(),
|
||||
full: render_group(
|
||||
elements: [render_group(
|
||||
false,
|
||||
g,
|
||||
&self.active_tool,
|
||||
@@ -600,7 +600,7 @@ impl Ribbon {
|
||||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
),
|
||||
compact: render_group(
|
||||
render_group(
|
||||
true,
|
||||
g,
|
||||
&self.active_tool,
|
||||
@@ -614,7 +614,7 @@ impl Ribbon {
|
||||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
),
|
||||
button: collapse_button(
|
||||
collapse_button(
|
||||
g,
|
||||
self.last_panel_tool.get(g.title).copied(),
|
||||
&self.active_tool,
|
||||
@@ -629,7 +629,7 @@ impl Ribbon {
|
||||
&style_ctx,
|
||||
false,
|
||||
),
|
||||
tight: collapse_button(
|
||||
collapse_button(
|
||||
g,
|
||||
self.last_panel_tool.get(g.title).copied(),
|
||||
&self.active_tool,
|
||||
@@ -644,32 +644,7 @@ impl Ribbon {
|
||||
&style_ctx,
|
||||
true,
|
||||
),
|
||||
flyout: container(render_group(
|
||||
false,
|
||||
g,
|
||||
&self.active_tool,
|
||||
&self.open_dropdown,
|
||||
&self.last_cmd,
|
||||
ts,
|
||||
&self.layer_infos,
|
||||
&self.active_layer,
|
||||
self.active_color,
|
||||
&self.active_linetype,
|
||||
self.active_lineweight,
|
||||
&style_ctx,
|
||||
))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weakest.color,
|
||||
)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.into(),
|
||||
],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -1578,4 +1553,76 @@ mod tests {
|
||||
let off = ribbon.toggle_state(false);
|
||||
assert!(!is_active_tool("BLOCKPALETTE", &None, &off));
|
||||
}
|
||||
|
||||
/// Reproducible element-construction benchmark for the ribbon view. Run with:
|
||||
/// `cargo test --lib --release -- --ignored --nocapture bench_ribbon_view_construction`
|
||||
///
|
||||
/// Times `Ribbon::view()` element construction — the dominant cost in the
|
||||
/// cheap (non-render) part of a ribbon frame, and the thing Mission #4
|
||||
/// reworked. `#[ignore]`d so normal runs stay silent; state is populated so
|
||||
/// Auto-mode panels actually have four densities to build.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn bench_ribbon_view_construction() {
|
||||
use std::time::Instant;
|
||||
use super::*;
|
||||
|
||||
let mut ribbon = Ribbon::new();
|
||||
ribbon.set_styles(
|
||||
vec![
|
||||
"Standard".to_string(),
|
||||
"Title".to_string(),
|
||||
"Annotative".to_string(),
|
||||
],
|
||||
"Standard",
|
||||
vec!["Standard".to_string()],
|
||||
"Standard",
|
||||
vec!["Standard".to_string()],
|
||||
"Standard",
|
||||
vec!["Standard".to_string()],
|
||||
"Standard",
|
||||
);
|
||||
ribbon.set_layers(
|
||||
vec![
|
||||
LayerInfo {
|
||||
name: "0".to_string(),
|
||||
color: Color::TRANSPARENT,
|
||||
visible: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
},
|
||||
LayerInfo {
|
||||
name: "DRAWING".to_string(),
|
||||
color: Color::TRANSPARENT,
|
||||
visible: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
},
|
||||
],
|
||||
"0",
|
||||
);
|
||||
ribbon.set_available_linetypes(vec![
|
||||
LinetypeItem {
|
||||
name: "Continuous".to_string(),
|
||||
art: String::new(),
|
||||
},
|
||||
LinetypeItem {
|
||||
name: "DASHED".to_string(),
|
||||
art: String::new(),
|
||||
},
|
||||
]);
|
||||
|
||||
let n = 200u32;
|
||||
// Warm-up for allocator/tree-slot settling before timing begins.
|
||||
let _ = ribbon.view(false, false, 0, 0, false);
|
||||
let start = Instant::now();
|
||||
for _ in 0..n {
|
||||
let _ = ribbon.view(false, false, 0, 0, false);
|
||||
}
|
||||
let per_frame = start.elapsed() / n;
|
||||
println!(
|
||||
"bench_ribbon_view_construction: {per_frame:?} per `Ribbon::view()` \
|
||||
element build (n = {n}, release measurement recommended)"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user