// =============================================================================
// Layout Engine Benchmarks
// =============================================================================
///|
/// Create a simple flat block layout with N children
fn create_flat_block(n : Int) -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < n; i = i + 1 {
let child_style = { ..default_style, height: @types.Length(20.0) }
children.push(@node.Node::leaf("child\{i}", child_style))
}
let root_style = { ..default_style, width: @types.Length(400.0) }
@node.Node::new("root", root_style, children)
}
///|
/// Create a nested block layout with depth D and children per level
fn create_nested_block(depth : Int, children_per_level : Int) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
let leaf_style = { ..default_style, height: @types.Length(10.0) }
return @node.Node::leaf("leaf", leaf_style)
}
let children : Array[@node.Node] = []
for i = 0; i < children_per_level; i = i + 1 {
children.push(build(d - 1))
}
let style = {
..default_style,
padding: {
left: @types.Length(5.0),
right: @types.Length(5.0),
top: @types.Length(5.0),
bottom: @types.Length(5.0),
},
}
@node.Node::new("node_d\{d}", style, children)
}
let root_style = { ..default_style, width: @types.Length(800.0) }
@node.Node::new("root", root_style, [build(depth)])
}
///|
/// Create a flex row layout with N children
fn create_flex_row(n : Int) -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < n; i = i + 1 {
let child_style = {
..default_style,
flex_grow: 1.0,
height: @types.Length(50.0),
}
children.push(@node.Node::leaf("child\{i}", child_style))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
width: @types.Length(800.0),
height: @types.Length(100.0),
}
@node.Node::new("root", root_style, children)
}
///|
/// Create a complex mixed layout (flex container with nested blocks)
fn create_complex_layout() -> @node.Node {
let default_style = @style.Style::default()
// Create sidebar with block children
let sidebar_children : Array[@node.Node] = []
for i = 0; i < 10; i = i + 1 {
let item_style = {
..default_style,
height: @types.Length(30.0),
margin: {
left: @types.Length(5.0),
right: @types.Length(5.0),
top: @types.Length(5.0),
bottom: @types.Length(5.0),
},
}
sidebar_children.push(@node.Node::leaf("sidebar_item\{i}", item_style))
}
let sidebar = @node.Node::new(
"sidebar",
{ ..default_style, width: @types.Length(200.0) },
sidebar_children,
)
// Create main content with nested structure
let content_children : Array[@node.Node] = []
for i = 0; i < 5; i = i + 1 {
// Each section has a header and items
let section_items : Array[@node.Node] = []
let header_style = { ..default_style, height: @types.Length(40.0) }
section_items.push(@node.Node::leaf("header\{i}", header_style))
for j = 0; j < 3; j = j + 1 {
let item_style = {
..default_style,
height: @types.Length(25.0),
margin: {
left: @types.Length(10.0),
right: @types.Length(10.0),
top: @types.Length(0.0),
bottom: @types.Length(10.0),
},
}
section_items.push(@node.Node::leaf("item\{i}_\{j}", item_style))
}
let section_style = {
..default_style,
padding: {
left: @types.Length(10.0),
right: @types.Length(10.0),
top: @types.Length(10.0),
bottom: @types.Length(10.0),
},
}
content_children.push(
@node.Node::new("section\{i}", section_style, section_items),
)
}
let content = @node.Node::new(
"content",
{ ..default_style, flex_grow: 1.0 },
content_children,
)
// Root flex container
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
width: @types.Length(1200.0),
height: @types.Length(800.0),
padding: {
left: @types.Length(20.0),
right: @types.Length(20.0),
top: @types.Length(20.0),
bottom: @types.Length(20.0),
},
}
@node.Node::new("root", root_style, [sidebar, content])
}
///|
/// Create a deeply nested flex layout
fn create_nested_flex(depth : Int) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int, is_row : Bool) -> @node.Node {
if d == 0 {
let leaf_style = {
..default_style,
width: @types.Length(20.0),
height: @types.Length(20.0),
}
return @node.Node::leaf("leaf", leaf_style)
}
let children : Array[@node.Node] = []
for i = 0; i < 3; i = i + 1 {
children.push(build(d - 1, not(is_row)))
}
let style = {
..default_style,
display: @style.Flex,
flex_direction: if is_row {
@style.Row
} else {
@style.Column
},
}
@node.Node::new("flex_d\{d}", style, children)
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
width: @types.Length(600.0),
height: @types.Length(400.0),
}
@node.Node::new("root", root_style, [build(depth, true)])
}
// =============================================================================
// Benchmarks
// =============================================================================
///|
test "bench_flat_block_10" (b : @bench.T) {
let node = create_flat_block(10)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="flat_block_10", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_flat_block_100" (b : @bench.T) {
let node = create_flat_block(100)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="flat_block_100", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_nested_block_depth5" (b : @bench.T) {
let node = create_nested_block(5, 2)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="nested_block_d5", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_flex_row_10" (b : @bench.T) {
let node = create_flex_row(10)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="flex_row_10", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_flex_row_50" (b : @bench.T) {
let node = create_flex_row(50)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="flex_row_50", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_complex_layout" (b : @bench.T) {
let node = create_complex_layout()
let viewport = @types.Size::new(1200.0, 800.0)
b.bench(name="complex_layout", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_nested_flex_depth4" (b : @bench.T) {
let node = create_nested_flex(4)
let viewport = @types.Size::new(600.0, 400.0)
b.bench(name="nested_flex_d4", fn() { b.keep(compute_layout(node, viewport)) })
}
// =============================================================================
// Intrinsic Sizing Benchmarks (for tracking performance during implementation)
// =============================================================================
///|
/// Create a layout that requires intrinsic sizing (auto width flex items)
fn create_intrinsic_sizing_test() -> @node.Node {
let default_style = @style.Style::default()
// Create items with explicit sizes (baseline for comparison)
let explicit_children : Array[@node.Node] = []
for i = 0; i < 5; i = i + 1 {
let child_style = {
..default_style,
width: @types.Length(100.0),
height: @types.Length(50.0),
}
explicit_children.push(@node.Node::leaf("explicit\{i}", child_style))
}
let explicit_container = @node.Node::new(
"explicit_container",
{
..default_style,
display: @style.Flex,
width: @types.Length(600.0),
height: @types.Length(100.0),
},
explicit_children,
)
// Root
@node.Node::new(
"root",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(800.0),
height: @types.Length(600.0),
},
[explicit_container],
)
}
///|
/// Create deeply nested layout (stress test for recursive intrinsic sizing)
fn create_deep_nested_intrinsic(depth : Int) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
// Leaf with explicit size
return @node.Node::leaf("leaf", {
..default_style,
width: @types.Length(50.0),
height: @types.Length(30.0),
})
}
// Container with explicit size wrapping child
let child = build(d - 1)
@node.Node::new(
"container_d\{d}",
{
..default_style,
display: @style.Flex,
width: @types.Length(100.0 + d.to_double() * 20.0),
height: @types.Length(60.0 + d.to_double() * 10.0),
},
[child],
)
}
build(depth)
}
///|
test "bench_intrinsic_explicit_sizes" (b : @bench.T) {
let node = create_intrinsic_sizing_test()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="intrinsic_explicit", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_deep_nested_d6" (b : @bench.T) {
let node = create_deep_nested_intrinsic(6)
let viewport = @types.Size::new(400.0, 300.0)
b.bench(name="deep_nested_d6", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_deep_nested_d10" (b : @bench.T) {
let node = create_deep_nested_intrinsic(10)
let viewport = @types.Size::new(600.0, 400.0)
b.bench(name="deep_nested_d10", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Grid Layout Benchmarks
// =============================================================================
///|
/// Create a simple 3x3 grid layout
fn create_grid_3x3() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < 9; i = i + 1 {
children.push(@node.Node::leaf("cell\{i}", default_style))
}
let root_style = {
..default_style,
display: @style.Grid,
width: @types.Length(300.0),
height: @types.Length(300.0),
grid_template_columns: [
@style.Length(100.0),
@style.Length(100.0),
@style.Length(100.0),
],
grid_template_rows: [
@style.Length(100.0),
@style.Length(100.0),
@style.Length(100.0),
],
}
@node.Node::new("grid", root_style, children)
}
///|
/// Create a larger 10x10 grid layout
fn create_grid_10x10() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < 100; i = i + 1 {
children.push(@node.Node::leaf("cell\{i}", default_style))
}
let columns : Array[@style.TrackSizingFunction] = []
let rows : Array[@style.TrackSizingFunction] = []
for j = 0; j < 10; j = j + 1 {
columns.push(@style.Length(50.0))
rows.push(@style.Length(50.0))
}
let root_style = {
..default_style,
display: @style.Grid,
width: @types.Length(500.0),
height: @types.Length(500.0),
grid_template_columns: columns,
grid_template_rows: rows,
}
@node.Node::new("grid", root_style, children)
}
///|
/// Create a grid with fr units
fn create_grid_fr() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < 12; i = i + 1 {
children.push(@node.Node::leaf("cell\{i}", default_style))
}
let root_style = {
..default_style,
display: @style.Grid,
width: @types.Length(600.0),
height: @types.Length(400.0),
grid_template_columns: [@style.Fr(1.0), @style.Fr(2.0), @style.Fr(1.0)],
grid_template_rows: [
@style.Fr(1.0),
@style.Fr(1.0),
@style.Fr(1.0),
@style.Fr(1.0),
],
}
@node.Node::new("grid", root_style, children)
}
///|
/// Create a grid with auto tracks
fn create_grid_auto() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < 20; i = i + 1 {
let child_style = {
..default_style,
width: @types.Length(50.0),
height: @types.Length(30.0),
}
children.push(@node.Node::leaf("cell\{i}", child_style))
}
let root_style = {
..default_style,
display: @style.Grid,
width: @types.Length(500.0),
grid_template_columns: [
@style.Auto,
@style.Auto,
@style.Auto,
@style.Auto,
@style.Auto,
],
}
@node.Node::new("grid", root_style, children)
}
///|
test "bench_grid_3x3" (b : @bench.T) {
let node = create_grid_3x3()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="grid_3x3", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_grid_10x10" (b : @bench.T) {
let node = create_grid_10x10()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="grid_10x10", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_grid_fr" (b : @bench.T) {
let node = create_grid_fr()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="grid_fr", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_grid_auto" (b : @bench.T) {
let node = create_grid_auto()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="grid_auto", fn() { b.keep(compute_layout(node, viewport)) })
}
// =============================================================================
// Complex Case Benchmarks
// =============================================================================
///|
/// Create a flex layout with wrapping (many items that wrap to multiple lines)
fn create_flex_wrap_many() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
// 50 items, each 100px wide, will wrap in a 600px container
for i = 0; i < 50; i = i + 1 {
let child_style = {
..default_style,
width: @types.Length(100.0),
height: @types.Length(50.0),
}
children.push(@node.Node::leaf("item\{i}", child_style))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
flex_wrap: @style.Wrap,
width: @types.Length(600.0),
height: @types.Length(600.0),
}
@node.Node::new("root", root_style, children)
}
///|
/// Create flex wrap with varying item sizes (more complex wrapping)
fn create_flex_wrap_varied() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
let widths = [80.0, 120.0, 60.0, 150.0, 90.0, 100.0, 70.0, 130.0]
for i = 0; i < 40; i = i + 1 {
let child_style = {
..default_style,
width: @types.Length(widths[i % 8]),
height: @types.Length(40.0),
}
children.push(@node.Node::leaf("item\{i}", child_style))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
flex_wrap: @style.Wrap,
width: @types.Length(500.0),
row_gap: @types.Length(10.0),
column_gap: @types.Length(10.0),
}
@node.Node::new("root", root_style, children)
}
///|
test "bench_flex_wrap_50" (b : @bench.T) {
let node = create_flex_wrap_many()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="flex_wrap_50", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_flex_wrap_varied" (b : @bench.T) {
let node = create_flex_wrap_varied()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="flex_wrap_varied", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Min/Max Constraints Benchmarks
// =============================================================================
///|
/// Create a layout with min/max constraints that are active
fn create_minmax_constraints() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < 20; i = i + 1 {
// Items with flex_grow but constrained by min/max
let child_style = {
..default_style,
flex_grow: 1.0,
min_width: @types.Length(50.0),
max_width: @types.Length(150.0),
height: @types.Length(40.0),
}
children.push(@node.Node::leaf("item\{i}", child_style))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
flex_wrap: @style.Wrap,
width: @types.Length(800.0),
height: @types.Length(400.0),
}
@node.Node::new("root", root_style, children)
}
///|
/// Create nested layout with percentage and min/max constraints
fn create_nested_percent_constraints() -> @node.Node {
let default_style = @style.Style::default()
// Create columns with percentage width and min-width constraints
let columns : Array[@node.Node] = []
for i = 0; i < 4; i = i + 1 {
let column_children : Array[@node.Node] = []
for j = 0; j < 5; j = j + 1 {
let item_style = {
..default_style,
height: @types.Percent(0.2),
min_height: @types.Length(30.0),
}
column_children.push(@node.Node::leaf("item\{i}_\{j}", item_style))
}
let column_style = {
..default_style,
width: @types.Percent(0.25),
min_width: @types.Length(100.0),
max_width: @types.Length(300.0),
}
columns.push(@node.Node::new("col\{i}", column_style, column_children))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
width: @types.Length(1000.0),
height: @types.Length(500.0),
}
@node.Node::new("root", root_style, columns)
}
///|
test "bench_minmax_constraints" (b : @bench.T) {
let node = create_minmax_constraints()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="minmax_constraints", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_nested_percent" (b : @bench.T) {
let node = create_nested_percent_constraints()
let viewport = @types.Size::new(1000.0, 500.0)
b.bench(name="nested_percent", fn() { b.keep(compute_layout(node, viewport)) })
}
// =============================================================================
// Complex Alignment Benchmarks
// =============================================================================
///|
/// Create flex layout with space-between and center alignment
fn create_complex_alignment() -> @node.Node {
let default_style = @style.Style::default()
let children : Array[@node.Node] = []
for i = 0; i < 15; i = i + 1 {
let child_style = {
..default_style,
width: @types.Length(60.0 + (i % 3).to_double() * 20.0),
height: @types.Length(40.0 + (i % 4).to_double() * 10.0),
}
children.push(@node.Node::leaf("item\{i}", child_style))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
flex_wrap: @style.Wrap,
justify_content: @style.SpaceBetween,
align_items: @style.Center,
align_content: @style.SpaceAround,
width: @types.Length(500.0),
height: @types.Length(400.0),
row_gap: @types.Length(15.0),
column_gap: @types.Length(10.0),
}
@node.Node::new("root", root_style, children)
}
///|
test "bench_complex_alignment" (b : @bench.T) {
let node = create_complex_alignment()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="complex_alignment", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Mixed Nested Layout Benchmarks
// =============================================================================
///|
/// Create a mixed layout: flex container with grid children and block grandchildren
fn create_mixed_flex_grid() -> @node.Node {
let default_style = @style.Style::default()
// Create grid sections
let grids : Array[@node.Node] = []
for i = 0; i < 3; i = i + 1 {
let grid_children : Array[@node.Node] = []
for j = 0; j < 9; j = j + 1 {
let cell_children : Array[@node.Node] = []
for k = 0; k < 2; k = k + 1 {
cell_children.push(
@node.Node::leaf("block\{i}_\{j}_\{k}", {
..default_style,
height: @types.Length(15.0),
}),
)
}
grid_children.push(
@node.Node::new("cell\{i}_\{j}", default_style, cell_children),
)
}
let grid_style = {
..default_style,
display: @style.Grid,
flex_grow: 1.0,
grid_template_columns: [@style.Fr(1.0), @style.Fr(1.0), @style.Fr(1.0)],
grid_template_rows: [@style.Fr(1.0), @style.Fr(1.0), @style.Fr(1.0)],
row_gap: @types.Length(5.0),
column_gap: @types.Length(5.0),
}
grids.push(@node.Node::new("grid\{i}", grid_style, grid_children))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
width: @types.Length(900.0),
height: @types.Length(400.0),
column_gap: @types.Length(20.0),
}
@node.Node::new("root", root_style, grids)
}
///|
test "bench_mixed_flex_grid" (b : @bench.T) {
let node = create_mixed_flex_grid()
let viewport = @types.Size::new(900.0, 400.0)
b.bench(name="mixed_flex_grid", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Realistic UI Layout Benchmarks
// =============================================================================
///|
/// Create a dashboard-like layout with sidebar, header, and main content grid
fn create_dashboard_layout() -> @node.Node {
let default_style = @style.Style::default()
// Sidebar with nav items
let nav_items : Array[@node.Node] = []
for i = 0; i < 12; i = i + 1 {
nav_items.push(
@node.Node::leaf("nav\{i}", {
..default_style,
height: @types.Length(40.0),
margin: {
left: @types.Length(8.0),
right: @types.Length(8.0),
top: @types.Length(4.0),
bottom: @types.Length(4.0),
},
}),
)
}
let sidebar = @node.Node::new(
"sidebar",
{
..default_style,
width: @types.Length(240.0),
padding: {
left: @types.Length(16.0),
right: @types.Length(16.0),
top: @types.Length(16.0),
bottom: @types.Length(16.0),
},
},
nav_items,
)
// Header
let header_items : Array[@node.Node] = []
for i = 0; i < 5; i = i + 1 {
header_items.push(
@node.Node::leaf("header_item\{i}", {
..default_style,
width: @types.Length(100.0),
height: @types.Length(36.0),
}),
)
}
let header = @node.Node::new(
"header",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
justify_content: @style.SpaceBetween,
align_items: @style.Center,
height: @types.Length(60.0),
padding: {
left: @types.Length(24.0),
right: @types.Length(24.0),
top: @types.Length(12.0),
bottom: @types.Length(12.0),
},
},
header_items,
)
// Main content grid (cards)
let cards : Array[@node.Node] = []
for i = 0; i < 12; i = i + 1 {
let card_content : Array[@node.Node] = []
card_content.push(
@node.Node::leaf("card_title\{i}", {
..default_style,
height: @types.Length(24.0),
}),
)
card_content.push(
@node.Node::leaf("card_body\{i}", {
..default_style,
height: @types.Length(80.0),
}),
)
card_content.push(
@node.Node::leaf("card_footer\{i}", {
..default_style,
height: @types.Length(32.0),
}),
)
cards.push(
@node.Node::new(
"card\{i}",
{
..default_style,
padding: {
left: @types.Length(16.0),
right: @types.Length(16.0),
top: @types.Length(16.0),
bottom: @types.Length(16.0),
},
},
card_content,
),
)
}
let main_grid = @node.Node::new(
"main_grid",
{
..default_style,
display: @style.Grid,
flex_grow: 1.0,
grid_template_columns: [
@style.Fr(1.0),
@style.Fr(1.0),
@style.Fr(1.0),
@style.Fr(1.0),
],
row_gap: @types.Length(16.0),
column_gap: @types.Length(16.0),
padding: {
left: @types.Length(24.0),
right: @types.Length(24.0),
top: @types.Length(24.0),
bottom: @types.Length(24.0),
},
},
cards,
)
// Main area (header + grid)
let main_area = @node.Node::new(
"main_area",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
flex_grow: 1.0,
},
[header, main_grid],
)
// Root
@node.Node::new(
"root",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Row,
width: @types.Length(1440.0),
height: @types.Length(900.0),
},
[sidebar, main_area],
)
}
///|
/// Create a form layout with labels, inputs, and validation messages
fn create_form_layout() -> @node.Node {
let default_style = @style.Style::default()
let form_fields : Array[@node.Node] = []
for i = 0; i < 10; i = i + 1 {
let field_children : Array[@node.Node] = []
// Label
field_children.push(
@node.Node::leaf("label\{i}", {
..default_style,
height: @types.Length(20.0),
margin: {
left: @types.Length(0.0),
right: @types.Length(0.0),
top: @types.Length(0.0),
bottom: @types.Length(4.0),
},
}),
)
// Input row (input + button)
let input_row_children : Array[@node.Node] = []
input_row_children.push(
@node.Node::leaf("input\{i}", {
..default_style,
flex_grow: 1.0,
height: @types.Length(36.0),
}),
)
if i % 3 == 0 {
input_row_children.push(
@node.Node::leaf("btn\{i}", {
..default_style,
width: @types.Length(80.0),
height: @types.Length(36.0),
margin: {
left: @types.Length(8.0),
right: @types.Length(0.0),
top: @types.Length(0.0),
bottom: @types.Length(0.0),
},
}),
)
}
let input_row = @node.Node::new(
"input_row\{i}",
{ ..default_style, display: @style.Flex, flex_direction: @style.Row },
input_row_children,
)
field_children.push(input_row)
// Validation message
field_children.push(
@node.Node::leaf("error\{i}", {
..default_style,
height: @types.Length(16.0),
margin: {
left: @types.Length(0.0),
right: @types.Length(0.0),
top: @types.Length(4.0),
bottom: @types.Length(0.0),
},
}),
)
form_fields.push(
@node.Node::new(
"field\{i}",
{
..default_style,
margin: {
left: @types.Length(0.0),
right: @types.Length(0.0),
top: @types.Length(0.0),
bottom: @types.Length(16.0),
},
},
field_children,
),
)
}
@node.Node::new(
"form",
{
..default_style,
width: @types.Length(400.0),
padding: {
left: @types.Length(24.0),
right: @types.Length(24.0),
top: @types.Length(24.0),
bottom: @types.Length(24.0),
},
},
form_fields,
)
}
///|
test "bench_dashboard_layout" (b : @bench.T) {
let node = create_dashboard_layout()
let viewport = @types.Size::new(1440.0, 900.0)
b.bench(name="dashboard_layout", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_form_layout" (b : @bench.T) {
let node = create_form_layout()
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="form_layout", fn() { b.keep(compute_layout(node, viewport)) })
}
// =============================================================================
// Stress Test Benchmarks
// =============================================================================
///|
/// Deep nesting with mixed display types
fn create_deep_mixed_nesting(depth : Int) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
return @node.Node::leaf("leaf", {
..default_style,
width: @types.Length(30.0),
height: @types.Length(20.0),
})
}
let children : Array[@node.Node] = []
for i = 0; i < 3; i = i + 1 {
children.push(build(d - 1))
}
// Alternate between flex, grid, and block
let display = match d % 3 {
0 => @style.Flex
1 => @style.Grid
_ => @style.Block
}
if display == @style.Grid {
return @node.Node::new(
"node_d\{d}",
{
..default_style,
display,
grid_template_columns: [
@style.Fr(1.0),
@style.Fr(1.0),
@style.Fr(1.0),
],
},
children,
)
}
@node.Node::new(
"node_d\{d}",
{
..default_style,
display,
flex_direction: if d % 2 == 0 {
@style.Row
} else {
@style.Column
},
},
children,
)
}
@node.Node::new(
"root",
{
..default_style,
display: @style.Flex,
width: @types.Length(800.0),
height: @types.Length(600.0),
},
[build(depth)],
)
}
///|
test "bench_deep_mixed_d5" (b : @bench.T) {
let node = create_deep_mixed_nesting(5)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="deep_mixed_d5", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_deep_mixed_d6" (b : @bench.T) {
let node = create_deep_mixed_nesting(6)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="deep_mixed_d6", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
/// Deep nesting with Flex only (for comparison)
fn create_deep_flex_nesting(depth : Int) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
return @node.Node::leaf("leaf", {
..default_style,
width: @types.Length(30.0),
height: @types.Length(20.0),
})
}
let children : Array[@node.Node] = []
for i = 0; i < 3; i = i + 1 {
children.push(build(d - 1))
}
@node.Node::new(
"node_d\{d}",
{
..default_style,
display: @style.Flex,
flex_direction: if d % 2 == 0 {
@style.Row
} else {
@style.Column
},
},
children,
)
}
@node.Node::new(
"root",
{
..default_style,
display: @style.Flex,
width: @types.Length(800.0),
height: @types.Length(600.0),
},
[build(depth)],
)
}
///|
test "bench_deep_flex_d5" (b : @bench.T) {
let node = create_deep_flex_nesting(5)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="deep_flex_d5", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
test "bench_deep_flex_d6" (b : @bench.T) {
let node = create_deep_flex_nesting(6)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="deep_flex_d6", fn() { b.keep(compute_layout(node, viewport)) })
}
///|
/// Deep nesting with fixed-size intermediate nodes (baseline for optimization)
fn create_deep_flex_fixed(depth : Int) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
return @node.Node::leaf("leaf", {
..default_style,
width: @types.Length(30.0),
height: @types.Length(20.0),
})
}
let children : Array[@node.Node] = []
for i = 0; i < 3; i = i + 1 {
children.push(build(d - 1))
}
@node.Node::new(
"node_d\{d}",
{
..default_style,
display: @style.Flex,
flex_direction: if d % 2 == 0 {
@style.Row
} else {
@style.Column
},
width: @types.Length(
// Fixed size to skip intrinsic calculation
100.0 * d.to_double(),
),
height: @types.Length(80.0 * d.to_double()),
},
children,
)
}
@node.Node::new(
"root",
{
..default_style,
display: @style.Flex,
width: @types.Length(800.0),
height: @types.Length(600.0),
},
[build(depth)],
)
}
///|
test "bench_deep_flex_fixed_d6" (b : @bench.T) {
let node = create_deep_flex_fixed(6)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="deep_flex_fixed_d6", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Grid Intrinsic Sizing Benchmarks
// =============================================================================
///|
/// Grid with auto-sized columns containing nested flex containers
fn create_grid_auto_nested_flex(depth : Int) -> @node.Node {
let default_style = @style.Style::default()
fn create_flex_tree(d : Int) -> @node.Node {
if d == 0 {
return @node.Node::leaf("leaf", {
..default_style,
width: @types.Length(30.0),
height: @types.Length(20.0),
})
}
let children : Array[@node.Node] = []
for i = 0; i < 2; i = i + 1 {
children.push(create_flex_tree(d - 1))
}
@node.Node::new(
"flex_d\{d}",
{
..default_style,
display: @style.Flex,
flex_direction: if d % 2 == 0 {
@style.Row
} else {
@style.Column
},
},
children,
)
}
// Create grid cells with nested flex trees
let children : Array[@node.Node] = []
for i = 0; i < 9; i = i + 1 {
children.push(create_flex_tree(depth))
}
@node.Node::new(
"grid_root",
{
..default_style,
display: @style.Grid,
width: @types.Length(600.0),
grid_template_columns: [@style.Auto, @style.Auto, @style.Auto],
grid_template_rows: [@style.Auto, @style.Auto, @style.Auto],
},
children,
)
}
///|
test "bench_grid_auto_nested_d3" (b : @bench.T) {
let node = create_grid_auto_nested_flex(3)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="grid_auto_nested_d3", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_grid_auto_nested_d4" (b : @bench.T) {
let node = create_grid_auto_nested_flex(4)
let viewport = @types.Size::new(800.0, 600.0)
b.bench(name="grid_auto_nested_d4", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Large-Scale Website Benchmark (~5000 nodes)
// =============================================================================
///|
/// Create a realistic large-scale web page layout (~5000 nodes)
fn create_large_website_node(
num_sidebar_items : Int,
num_content_sections : Int,
items_per_section : Int,
) -> @node.Node {
let default_style = @style.Style::default()
// Header
let header_items : Array[@node.Node] = []
for i = 0; i < 10; i = i + 1 {
header_items.push(
@node.Node::leaf("header_item\{i}", {
..default_style,
width: @types.Length(80.0),
height: @types.Length(40.0),
}),
)
}
let header = @node.Node::new(
"header",
{
..default_style,
display: @style.Flex,
width: @types.Percent(1.0),
height: @types.Length(60.0),
},
header_items,
)
// Sidebar
let sidebar_groups : Array[@node.Node] = []
for g = 0; g < 5; g = g + 1 {
let group_items : Array[@node.Node] = []
for i = 0; i < num_sidebar_items / 5; i = i + 1 {
let item = @node.Node::new(
"sidebar_g\{g}_item\{i}",
{
..default_style,
display: @style.Flex,
width: @types.Percent(1.0),
height: @types.Length(36.0),
},
[
@node.Node::leaf("icon_\{g}_\{i}", {
..default_style,
width: @types.Length(20.0),
height: @types.Length(20.0),
}),
@node.Node::leaf("label_\{g}_\{i}", {
..default_style,
width: @types.Auto,
height: @types.Length(20.0),
flex_grow: 1.0,
}),
],
)
group_items.push(item)
}
sidebar_groups.push(
@node.Node::new(
"sidebar_group\{g}",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Percent(1.0),
},
group_items,
),
)
}
let sidebar = @node.Node::new(
"sidebar",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(240.0),
},
sidebar_groups,
)
// Main content
let sections : Array[@node.Node] = []
for s = 0; s < num_content_sections; s = s + 1 {
let section_header = @node.Node::new(
"section_header\{s}",
{
..default_style,
display: @style.Flex,
width: @types.Percent(1.0),
height: @types.Length(48.0),
},
[
@node.Node::leaf("section_title\{s}", {
..default_style,
width: @types.Auto,
height: @types.Length(24.0),
flex_grow: 1.0,
}),
@node.Node::leaf("section_action\{s}", {
..default_style,
width: @types.Length(100.0),
height: @types.Length(32.0),
}),
],
)
let cards : Array[@node.Node] = []
for i = 0; i < items_per_section; i = i + 1 {
let card = @node.Node::new(
"card\{s}_\{i}",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(280.0),
},
[
@node.Node::leaf("card_img\{s}_\{i}", {
..default_style,
width: @types.Percent(1.0),
height: @types.Length(160.0),
}),
@node.Node::new(
"card_body\{s}_\{i}",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Percent(1.0),
},
[
@node.Node::leaf("card_title\{s}_\{i}", {
..default_style,
width: @types.Percent(1.0),
height: @types.Length(24.0),
}),
@node.Node::leaf("card_desc\{s}_\{i}", {
..default_style,
width: @types.Percent(1.0),
height: @types.Length(48.0),
}),
@node.Node::new(
"card_meta\{s}_\{i}",
{
..default_style,
display: @style.Flex,
width: @types.Percent(1.0),
height: @types.Length(20.0),
},
[
@node.Node::leaf("card_date\{s}_\{i}", {
..default_style,
width: @types.Auto,
height: @types.Length(16.0),
flex_grow: 1.0,
}),
@node.Node::leaf("card_author\{s}_\{i}", {
..default_style,
width: @types.Length(80.0),
height: @types.Length(16.0),
}),
],
),
],
),
@node.Node::new(
"card_actions\{s}_\{i}",
{
..default_style,
display: @style.Flex,
width: @types.Percent(1.0),
height: @types.Length(40.0),
},
[
@node.Node::leaf("card_btn1\{s}_\{i}", {
..default_style,
width: @types.Length(80.0),
height: @types.Length(32.0),
}),
@node.Node::leaf("card_btn2\{s}_\{i}", {
..default_style,
width: @types.Length(80.0),
height: @types.Length(32.0),
}),
],
),
],
)
cards.push(card)
}
let section_content = @node.Node::new(
"section_content\{s}",
{
..default_style,
display: @style.Flex,
flex_wrap: @style.Wrap,
width: @types.Percent(1.0),
},
cards,
)
sections.push(
@node.Node::new(
"section\{s}",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Percent(1.0),
},
[section_header, section_content],
),
)
}
let main_content = @node.Node::new(
"main_content",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
flex_grow: 1.0,
},
sections,
)
// Body
let body = @node.Node::new(
"body",
{ ..default_style, display: @style.Flex, flex_grow: 1.0 },
[sidebar, main_content],
)
// Footer
let footer_items : Array[@node.Node] = []
for i = 0; i < 20; i = i + 1 {
footer_items.push(
@node.Node::leaf("footer_link\{i}", {
..default_style,
width: @types.Length(100.0),
height: @types.Length(24.0),
}),
)
}
let footer = @node.Node::new(
"footer",
{
..default_style,
display: @style.Flex,
flex_wrap: @style.Wrap,
width: @types.Percent(1.0),
},
footer_items,
)
// Root
@node.Node::new(
"root",
{
..default_style,
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(1440.0),
height: @types.Length(900.0),
},
[header, body, footer],
)
}
///|
test "bench_large_website_5000" (b : @bench.T) {
// ~5000 nodes: 50 sidebar items, 20 sections, 20 cards each
let node = create_large_website_node(50, 20, 20)
let viewport = @types.Size::new(1440.0, 900.0)
b.bench(name="large_website_5000", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_large_website_2000" (b : @bench.T) {
// ~2000 nodes: 30 sidebar items, 10 sections, 15 cards each
let node = create_large_website_node(30, 10, 15)
let viewport = @types.Size::new(1440.0, 900.0)
b.bench(name="large_website_2000", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_large_website_10000" (b : @bench.T) {
// ~10000 nodes: 100 sidebar items, 40 sections, 20 cards each
let node = create_large_website_node(100, 40, 20)
let viewport = @types.Size::new(1440.0, 900.0)
b.bench(name="large_website_10000", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Incremental Layout Benchmarks
// =============================================================================
///|
/// Create LayoutNode tree for incremental benchmarks
fn create_large_layout_tree(
num_sidebar : Int,
num_sections : Int,
cards_per_section : Int,
) -> (@tree.LayoutTree, Array[@tree.LayoutNode]) {
let all_cards : Array[@tree.LayoutNode] = []
// Sidebar items
let sidebar_items : Array[@tree.LayoutNode] = []
for i = 0; i < num_sidebar; i = i + 1 {
sidebar_items.push(
@tree.LayoutNode::leaf("sidebar_" + i.to_string(), @style.Style::{
..@style.Style::default(),
width: @types.Length(180.0),
height: @types.Length(40.0),
}),
)
}
let sidebar = @tree.LayoutNode::new(
"sidebar",
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(200.0),
height: @types.Auto,
},
sidebar_items,
)
// Sections with cards
let sections : Array[@tree.LayoutNode] = []
for s = 0; s < num_sections; s = s + 1 {
let cards : Array[@tree.LayoutNode] = []
for c = 0; c < cards_per_section; c = c + 1 {
let card = @tree.LayoutNode::new(
"card_" + s.to_string() + "_" + c.to_string(),
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(280.0),
height: @types.Length(200.0),
},
[
@tree.LayoutNode::leaf("title", @style.Style::{
..@style.Style::default(),
width: @types.Percent(1.0),
height: @types.Length(24.0),
}),
@tree.LayoutNode::leaf("body", @style.Style::{
..@style.Style::default(),
width: @types.Percent(1.0),
height: @types.Length(100.0),
}),
@tree.LayoutNode::leaf("footer", @style.Style::{
..@style.Style::default(),
width: @types.Percent(1.0),
height: @types.Length(32.0),
}),
],
)
cards.push(card)
all_cards.push(card)
}
let section = @tree.LayoutNode::new(
"section_" + s.to_string(),
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_wrap: @style.Wrap,
width: @types.Percent(1.0),
height: @types.Auto,
},
cards,
)
sections.push(section)
}
let main = @tree.LayoutNode::new(
"main",
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Auto,
height: @types.Auto,
flex_grow: 1.0,
},
sections,
)
let body = @tree.LayoutNode::new(
"body",
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
width: @types.Percent(1.0),
height: @types.Auto,
flex_grow: 1.0,
},
[sidebar, main],
)
let root = @tree.LayoutNode::new(
"root",
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(1440.0),
height: @types.Length(900.0),
},
[body],
)
let tree = @tree.LayoutTree::new(root, 1440.0, 900.0)
(tree, all_cards)
}
///|
test "bench_incremental_5000_full" (b : @bench.T) {
@dispatch.setup()
let (tree, _) = create_large_layout_tree(50, 20, 20)
b.bench(name="incremental_5000_full", fn() {
// Mark root dirty to force full recompute
tree.mark_node_dirty(tree.root.uid)
b.keep(tree.compute_incremental())
})
}
///|
test "bench_incremental_5000_single_dirty" (b : @bench.T) {
@dispatch.setup()
let (tree, cards) = create_large_layout_tree(50, 20, 20)
// Initial full compute
let _ = tree.compute_incremental()
let target_card = cards[cards.length() / 2]
b.bench(name="incremental_5000_single", fn() {
// Mark single card dirty
tree.mark_node_dirty(target_card.uid)
b.keep(tree.compute_incremental())
})
}
///|
test "bench_incremental_5000_cached" (b : @bench.T) {
@dispatch.setup()
let (tree, _) = create_large_layout_tree(50, 20, 20)
// Initial full compute
let _ = tree.compute_incremental()
b.bench(name="incremental_5000_cached", fn() {
// No dirty nodes - pure cache hit
b.keep(tree.compute_incremental())
})
}
///|
test "bench_incremental_10000_full" (b : @bench.T) {
@dispatch.setup()
let (tree, _) = create_large_layout_tree(100, 40, 20)
b.bench(name="incremental_10000_full", fn() {
tree.mark_node_dirty(tree.root.uid)
b.keep(tree.compute_incremental())
})
}
///|
test "bench_incremental_10000_single_dirty" (b : @bench.T) {
@dispatch.setup()
let (tree, cards) = create_large_layout_tree(100, 40, 20)
let _ = tree.compute_incremental()
let target_card = cards[cards.length() / 2]
b.bench(name="incremental_10000_single", fn() {
tree.mark_node_dirty(target_card.uid)
b.keep(tree.compute_incremental())
})
}
///|
test "bench_incremental_10000_cached" (b : @bench.T) {
@dispatch.setup()
let (tree, _) = create_large_layout_tree(100, 40, 20)
let _ = tree.compute_incremental()
b.bench(name="incremental_10000_cached", fn() {
b.keep(tree.compute_incremental())
})
}
// =============================================================================
// CSS Containment Benchmarks
// =============================================================================
// These benchmarks measure the potential performance gains from CSS contain property.
// contain: size - allows skipping child layout when computing parent size
// contain: layout - isolates subtree layout from affecting parent
///|
/// Create a layout with many "cards" that could benefit from contain: size
/// Each card has a fixed size and contains complex nested content
fn create_contained_cards(
num_cards : Int,
items_per_card : Int,
use_containment : Bool,
) -> @node.Node {
let default_style = @style.Style::default()
let cards : Array[@node.Node] = []
for i = 0; i < num_cards; i = i + 1 {
// Each card has nested items
let items : Array[@node.Node] = []
for j = 0; j < items_per_card; j = j + 1 {
let item_style = { ..default_style, height: @types.Length(20.0) }
items.push(@node.Node::leaf("item_\{i}_\{j}", item_style))
}
// Card style with optional containment
let card_style = if use_containment {
{
..default_style,
width: @types.Length(200.0),
height: @types.Length(300.0),
contain: @style.Contain::strict(),
}
} else {
{
..default_style,
width: @types.Length(200.0),
height: @types.Length(300.0),
}
}
cards.push(@node.Node::new("card_\{i}", card_style, items))
}
let root_style = {
..default_style,
display: @style.Flex,
flex_wrap: @style.Wrap,
width: @types.Length(1200.0),
}
@node.Node::new("root", root_style, cards)
}
///|
/// Create deeply nested layout with containment at certain levels
fn create_nested_with_containment(
depth : Int,
children_per_level : Int,
containment_at_depth : Int,
use_containment : Bool,
) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
let leaf_style = { ..default_style, height: @types.Length(10.0) }
return @node.Node::leaf("leaf", leaf_style)
}
let children : Array[@node.Node] = []
for i = 0; i < children_per_level; i = i + 1 {
children.push(build(d - 1))
}
let style = if use_containment && d == containment_at_depth {
{
..default_style,
padding: {
left: @types.Length(5.0),
right: @types.Length(5.0),
top: @types.Length(5.0),
bottom: @types.Length(5.0),
},
contain: @style.Contain::content(),
}
} else {
{
..default_style,
padding: {
left: @types.Length(5.0),
right: @types.Length(5.0),
top: @types.Length(5.0),
bottom: @types.Length(5.0),
},
}
}
@node.Node::new("node_d\{d}", style, children)
}
let root_style = { ..default_style, width: @types.Length(800.0) }
@node.Node::new("root", root_style, [build(depth)])
}
///|
/// Benchmark: Cards WITHOUT containment (baseline)
test "bench_contain_cards_100_no_contain" (b : @bench.T) {
@dispatch.setup()
let node = create_contained_cards(100, 20, false)
let viewport = @types.Size::new(1200.0, 2000.0)
b.bench(name="cards_100_no_contain", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
/// Benchmark: Cards WITH contain: strict (should be faster when optimized)
test "bench_contain_cards_100_with_contain" (b : @bench.T) {
@dispatch.setup()
let node = create_contained_cards(100, 20, true)
let viewport = @types.Size::new(1200.0, 2000.0)
b.bench(name="cards_100_with_contain", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
/// Benchmark: Nested layout WITHOUT containment (baseline)
test "bench_contain_nested_no_contain" (b : @bench.T) {
@dispatch.setup()
// depth=6, 3 children per level = ~1000 nodes
let node = create_nested_with_containment(6, 3, 3, false)
let viewport = @types.Size::new(800.0, 2000.0)
b.bench(name="nested_no_contain", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
/// Benchmark: Nested layout WITH contain: content at middle level
test "bench_contain_nested_with_contain" (b : @bench.T) {
@dispatch.setup()
// depth=6, 3 children per level, containment at depth 3
let node = create_nested_with_containment(6, 3, 3, true)
let viewport = @types.Size::new(800.0, 2000.0)
b.bench(name="nested_with_contain", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
/// Create a layout where parent depends on children's intrinsic size
/// Cards have width: auto, so intrinsic calculation traverses deeply
/// When contain: size + explicit size, we skip deep traversal
fn create_intrinsic_dependent_layout(
num_cards : Int,
items_per_card : Int,
use_containment : Bool,
) -> @node.Node {
let default_style = @style.Style::default()
let cards : Array[@node.Node] = []
for i = 0; i < num_cards; i = i + 1 {
// Each card has nested items with complex content
let items : Array[@node.Node] = []
for j = 0; j < items_per_card; j = j + 1 {
// Items with nested children to create intrinsic size calculation
let nested : Array[@node.Node] = []
for k = 0; k < 5; k = k + 1 {
// Deeply nested structure
let deep : Array[@node.Node] = []
for l = 0; l < 3; l = l + 1 {
let leaf_style = {
..default_style,
width: @types.Length(50.0),
height: @types.Length(10.0),
}
deep.push(@node.Node::leaf("leaf_\{i}_\{j}_\{k}_\{l}", leaf_style))
}
let nested_style = { ..default_style }
nested.push(
@node.Node::new("nested_\{i}_\{j}_\{k}", nested_style, deep),
)
}
let item_style = { ..default_style }
items.push(@node.Node::new("item_\{i}_\{j}", item_style, nested))
}
// Card: containment version has fixed size, non-containment has width: auto
let card_style = if use_containment {
{
..default_style,
width: @types.Length(200.0),
height: @types.Length(300.0),
contain: @style.Contain::strict(),
}
} else {
// width: auto forces intrinsic size calculation through nested children
{ ..default_style, height: @types.Length(300.0) }
}
cards.push(@node.Node::new("card_\{i}", card_style, items))
}
// Parent with AUTO width - depends on children's intrinsic sizes
let root_style = { ..default_style, display: @style.Flex, width: @types.Auto }
@node.Node::new("root", root_style, cards)
}
///|
/// Benchmark: Intrinsic-dependent layout WITHOUT containment
test "bench_intrinsic_no_contain" (b : @bench.T) {
@dispatch.setup()
let node = create_intrinsic_dependent_layout(50, 10, false)
let viewport = @types.Size::new(2000.0, 2000.0)
b.bench(name="intrinsic_no_contain", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
/// Benchmark: Intrinsic-dependent layout WITH containment (should be faster)
test "bench_intrinsic_with_contain" (b : @bench.T) {
@dispatch.setup()
let node = create_intrinsic_dependent_layout(50, 10, true)
let viewport = @types.Size::new(2000.0, 2000.0)
b.bench(name="intrinsic_with_contain", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
/// Create simple deep tree to measure pure intrinsic calculation
fn create_pure_intrinsic_tree(
depth : Int,
width : Int,
use_contain : Bool,
) -> @node.Node {
let default_style = @style.Style::default()
fn build(d : Int) -> @node.Node {
if d == 0 {
return @node.Node::leaf("leaf", {
..default_style,
width: @types.Length(50.0),
height: @types.Length(20.0),
})
}
let children : Array[@node.Node] = []
for i = 0; i < width; i = i + 1 {
children.push(build(d - 1))
}
// At depth 2, apply containment if enabled
let style = if use_contain && d == 2 {
{
..default_style,
width: @types.Length(200.0),
height: @types.Length(100.0),
contain: @style.Contain::strict(),
}
} else {
default_style
}
@node.Node::new("node_\{d}", style, children)
}
// Root with width: auto triggers intrinsic calculation
let root = build(depth)
@node.Node::new(
"root",
{ ..default_style, display: @style.Flex, width: @types.Auto },
[root],
)
}
///|
test "bench_pure_intrinsic_no_contain" (b : @bench.T) {
@dispatch.setup()
// depth 5, width 4 = 1365 nodes
let node = create_pure_intrinsic_tree(5, 4, false)
let viewport = @types.Size::new(2000.0, 2000.0)
b.bench(name="pure_intrinsic_no", fn() {
b.keep(compute_layout(node, viewport))
})
}
///|
test "bench_pure_intrinsic_with_contain" (b : @bench.T) {
@dispatch.setup()
// Same tree but with contain: size at depth 2
let node = create_pure_intrinsic_tree(5, 4, true)
let viewport = @types.Size::new(2000.0, 2000.0)
b.bench(name="pure_intrinsic_yes", fn() {
b.keep(compute_layout(node, viewport))
})
}
// =============================================================================
// Incremental Layout + CSS Containment Benchmarks
// =============================================================================
// These benchmarks test the dirty propagation optimization with contain: layout
// Key: when marking a deep node dirty, contain: layout stops propagation at boundary
// This means fewer ancestor nodes need recomputation
///|
/// Create a deep tree for incremental layout testing with containment
/// Structure: root -> sections (with/without containment) -> cards -> items
fn create_incremental_containment_tree(
num_sections : Int,
cards_per_section : Int,
items_per_card : Int,
use_containment : Bool,
) -> (@tree.LayoutTree, Array[@tree.LayoutNode]) {
let deep_items : Array[@tree.LayoutNode] = []
let sections : Array[@tree.LayoutNode] = []
for s = 0; s < num_sections; s = s + 1 {
let cards : Array[@tree.LayoutNode] = []
for c = 0; c < cards_per_section; c = c + 1 {
let items : Array[@tree.LayoutNode] = []
for i = 0; i < items_per_card; i = i + 1 {
let item = @tree.LayoutNode::leaf(
"item_" + s.to_string() + "_" + c.to_string() + "_" + i.to_string(),
@style.Style::{
..@style.Style::default(),
width: @types.Percent(1.0),
height: @types.Length(20.0),
},
)
items.push(item)
deep_items.push(item)
}
let card = @tree.LayoutNode::new(
"card_" + s.to_string() + "_" + c.to_string(),
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(280.0),
height: @types.Length(200.0),
},
items,
)
cards.push(card)
}
// Section: with containment or without
let section_style = if use_containment {
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_wrap: @style.Wrap,
width: @types.Percent(1.0),
contain: @style.Contain::content(), // layout + paint + style
}
} else {
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_wrap: @style.Wrap,
width: @types.Percent(1.0),
}
}
let section = @tree.LayoutNode::new(
"section_" + s.to_string(),
section_style,
cards,
)
sections.push(section)
}
let main = @tree.LayoutNode::new(
"main",
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Percent(1.0),
flex_grow: 1.0,
},
sections,
)
let root = @tree.LayoutNode::new(
"root",
@style.Style::{
..@style.Style::default(),
display: @style.Flex,
flex_direction: @style.Column,
width: @types.Length(1440.0),
height: @types.Length(900.0),
},
[main],
)
let tree = @tree.LayoutTree::new(root, 1440.0, 900.0)
(tree, deep_items)
}
///|
/// Benchmark: Incremental layout after marking deep item dirty - WITHOUT containment
/// Dirty flag propagates all the way to root, so more nodes need to be checked
test "bench_incremental_contain_dirty_no" (b : @bench.T) {
@dispatch.setup()
// 10 sections, 10 cards each, 5 items per card = 500 items
let (tree, items) = create_incremental_containment_tree(10, 10, 5, false)
// Initial full compute
let _ = tree.compute_incremental()
// Target item in middle section
let target = items[items.length() / 2]
b.bench(name="incr_contain_dirty_no", fn() {
// Mark deep item dirty - propagates to root
tree.mark_node_dirty(target.uid)
b.keep(tree.compute_incremental())
})
}
///|
/// Benchmark: Incremental layout after marking deep item dirty - WITH containment
/// Dirty flag stops at section boundary due to contain: layout
test "bench_incremental_contain_dirty_yes" (b : @bench.T) {
@dispatch.setup()
// Same structure but sections have contain: content (includes layout)
let (tree, items) = create_incremental_containment_tree(10, 10, 5, true)
// Initial full compute
let _ = tree.compute_incremental()
// Target item in middle section
let target = items[items.length() / 2]
b.bench(name="incr_contain_dirty_yes", fn() {
// Mark deep item dirty - propagation stops at section boundary
tree.mark_node_dirty(target.uid)
b.keep(tree.compute_incremental())
})
}
///|
/// Benchmark: Multiple deep items marked dirty - WITHOUT containment
/// All dirty flags propagate to root, causing wide re-layout
test "bench_incremental_contain_multi_no" (b : @bench.T) {
@dispatch.setup()
let (tree, items) = create_incremental_containment_tree(10, 10, 5, false)
let _ = tree.compute_incremental()
// Mark items in different sections dirty
let targets = [
items[50], // Section 1
items[150], // Section 3
items[250], // Section 5
items[350], // Section 7
items[450],
] // Section 9
b.bench(name="incr_contain_multi_no", fn() {
for t in targets {
tree.mark_node_dirty(t.uid)
}
b.keep(tree.compute_incremental())
})
}
///|
/// Benchmark: Multiple deep items marked dirty - WITH containment
/// Each dirty section is isolated, only affected sections need re-layout
test "bench_incremental_contain_multi_yes" (b : @bench.T) {
@dispatch.setup()
let (tree, items) = create_incremental_containment_tree(10, 10, 5, true)
let _ = tree.compute_incremental()
let targets = [items[50], items[150], items[250], items[350], items[450]]
b.bench(name="incr_contain_multi_yes", fn() {
for t in targets {
tree.mark_node_dirty(t.uid)
}
b.keep(tree.compute_incremental())
})
}
///|
/// Larger scale: 50 sections to see more dramatic difference
test "bench_incremental_contain_large_no" (b : @bench.T) {
@dispatch.setup()
// 50 sections, 5 cards each, 4 items per card = 1000 items
let (tree, items) = create_incremental_containment_tree(50, 5, 4, false)
let _ = tree.compute_incremental()
let target = items[items.length() / 2]
b.bench(name="incr_contain_large_no", fn() {
tree.mark_node_dirty(target.uid)
b.keep(tree.compute_incremental())
})
}
///|
test "bench_incremental_contain_large_yes" (b : @bench.T) {
@dispatch.setup()
let (tree, items) = create_incremental_containment_tree(50, 5, 4, true)
let _ = tree.compute_incremental()
let target = items[items.length() / 2]
b.bench(name="incr_contain_large_yes", fn() {
tree.mark_node_dirty(target.uid)
b.keep(tree.compute_incremental())
})
}