Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions benches/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,38 @@ fn morphorm_benchmarks(c: &mut Criterion) {
});

group.finish();

let mut group = c.benchmark_group("Steady State Tree");
group.sample_size(20);

let children_per_node = 10;
let depth = 6usize;
let mut world_baseline = World::default();
let root_baseline = build_tree(&mut world_baseline, None, children_per_node, depth);

let mut world_cached = World::default();
let root_cached = build_tree(&mut world_cached, None, children_per_node, depth);
world_cached.cache.enable_cross_pass_memoization(true);
world_cached.cache.set_layout_revision(1);
Comment thread
geom3trik marked this conversation as resolved.
Outdated

let benchmark_label = format!(
"Steady state bench. {children_per_node} children per node, depth: {depth}. Total nodes: {}.",
compute_node_count(children_per_node, depth, &mut 0)
);

group.bench_function(format!("{} [baseline]", benchmark_label), |b| {
b.iter(|| {
root_baseline.layout(&mut world_baseline.cache, &world_baseline.tree, &world_baseline.store, &mut ());
})
});

group.bench_function(format!("{} [cross-pass memo]", benchmark_label), |b| {
b.iter(|| {
root_cached.layout(&mut world_cached.cache, &world_cached.tree, &world_cached.store, &mut ());
})
});

group.finish();
}

criterion_group!(benches, morphorm_benchmarks);
Expand Down
107 changes: 106 additions & 1 deletion ecs/src/implementations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ impl Node for Entity {
store.position_type.get(*self).copied()
}

fn direction(&self, store: &Store) -> Option<Direction> {
store.direction.get(*self).copied()
}

fn wrap(&self, store: &Store) -> Option<LayoutWrap> {
store.wrap.get(*self).copied()
}

fn alignment(&self, store: &Store) -> Option<Alignment> {
store.alignment.get(*self).copied()
}
Expand Down Expand Up @@ -186,23 +194,61 @@ pub struct Rect {
pub height: f32,
}

#[derive(Default, Debug)]
pub struct NodeCache {
// Computed size and position of nodes.
pub rect: SecondaryMap<Entity, Rect>,
// Memoized layout sizes for the current invalidation generation.
layout_memo: SecondaryMap<Entity, LayoutMemo>,
layout_generation: u64,
memoization_enabled: bool,
}

#[derive(Default, Debug, Clone, Copy)]
struct LayoutMemo {
parent_layout_type: LayoutType,
parent_main: f32,
parent_cross: f32,
size: Size,
generation: u64,
valid: bool,
}

#[inline]
fn same_f32(a: f32, b: f32) -> bool {
a.to_bits() == b.to_bits()
}

impl NodeCache {
pub fn enable_layout_memoization(&mut self, enabled: bool) {
self.memoization_enabled = enabled;
}

pub fn enable_cross_pass_memoization(&mut self, enabled: bool) {
self.enable_layout_memoization(enabled);
}

pub fn set_layout_revision(&mut self, revision: u64) {
self.layout_generation = revision;
}

pub fn bump_layout_revision(&mut self) {
self.layout_generation = self.layout_generation.wrapping_add(1);
}

pub fn add(&mut self, entity: Entity) {
self.rect.insert(entity, Default::default());
self.layout_memo.insert(entity, Default::default());
}

pub fn remove(&mut self, entity: Entity) {
self.rect.remove(entity);
self.layout_memo.remove(entity);
}

pub fn clear(&mut self) {
self.rect.clear();
self.layout_memo.clear();
self.layout_generation = 0;
}

pub fn bounds(&self, entity: Entity) -> Option<&Rect> {
Expand All @@ -213,6 +259,54 @@ impl NodeCache {
impl Cache for NodeCache {
type Node = Entity;

fn get_layout_result(
&self,
node: &Self::Node,
parent_layout_type: LayoutType,
parent_main: f32,
parent_cross: f32,
) -> Option<Size> {
if !self.memoization_enabled {
return None;
}

let memo = self.layout_memo.get(*node)?;
if !memo.valid || memo.generation != self.layout_generation {
return None;
}

if memo.parent_layout_type == parent_layout_type
&& same_f32(memo.parent_main, parent_main)
&& same_f32(memo.parent_cross, parent_cross)
{
Some(memo.size)
} else {
None
}
}

fn set_layout_result(
&mut self,
node: &Self::Node,
parent_layout_type: LayoutType,
parent_main: f32,
parent_cross: f32,
size: Size,
) {
if !self.memoization_enabled {
return;
}

if let Some(memo) = self.layout_memo.get_mut(*node) {
memo.parent_layout_type = parent_layout_type;
memo.parent_main = parent_main;
memo.parent_cross = parent_cross;
memo.size = size;
memo.generation = self.layout_generation;
memo.valid = true;
}
}

fn set_bounds(&mut self, node: &Self::Node, posx: f32, posy: f32, width: f32, height: f32) {
if let Some(rect) = self.rect.get_mut(*node) {
rect.posx = posx;
Expand Down Expand Up @@ -254,3 +348,14 @@ impl Cache for NodeCache {
0.0
}
}

impl Default for NodeCache {
fn default() -> Self {
Self {
rect: SecondaryMap::new(),
layout_memo: SecondaryMap::new(),
layout_generation: 0,
memoization_enabled: true,
Comment thread
geom3trik marked this conversation as resolved.
Outdated
}
}
}
7 changes: 6 additions & 1 deletion ecs/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Part of a very simple ECS for demonstration purposes only.

use crate::{entity::Entity, TextWrap};
use morphorm::{LayoutType, PositionType, Units, Alignment};
use morphorm::{Alignment, Direction, LayoutType, LayoutWrap, PositionType, Units};
use slotmap::SecondaryMap;

type ContentSizeType<S> = Box<dyn Fn(&S, Option<f32>, Option<f32>) -> (f32, f32)>;
Expand All @@ -13,7 +13,9 @@ pub struct Store {

pub layout_type: SecondaryMap<Entity, LayoutType>,
pub position_type: SecondaryMap<Entity, PositionType>,
pub direction: SecondaryMap<Entity, Direction>,
pub alignment: SecondaryMap<Entity, Alignment>,
pub wrap: SecondaryMap<Entity, LayoutWrap>,

pub grid_columns: SecondaryMap<Entity, Vec<Units>>,
pub grid_rows: SecondaryMap<Entity, Vec<Units>>,
Expand Down Expand Up @@ -72,6 +74,8 @@ impl Store {
self.visible.remove(entity);
self.layout_type.remove(entity);
self.position_type.remove(entity);
self.direction.remove(entity);
self.wrap.remove(entity);
self.left.remove(entity);
self.right.remove(entity);
self.top.remove(entity);
Expand Down Expand Up @@ -110,6 +114,7 @@ impl Store {
self.visible.clear();
self.layout_type.clear();
self.position_type.clear();
self.direction.clear();
Comment thread
geom3trik marked this conversation as resolved.
self.left.clear();
self.right.clear();
self.top.clear();
Expand Down
Loading
Loading