Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 12 additions & 12 deletions crates/grida-canvas/src/runtime/invalidation/change_kind.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
//! [`ChangeKind`], [`Damage`], and [`GlobalFlag`] taxonomy.
//!
//! Four per-node variants (`None`, `Geometry`, `Paint`, `Full`) plus a
//! Four per-node variants (`None`, `Layout`, `Paint`, `Full`) plus a
//! small set of global flags for orthogonal invalidations. Splitting
//! `Full` further (`Size` / `Layout` / `Structural`) or `Paint`
//! (`PaintFill` / `PaintEffect`) is future work; the classifier
//! contract in [`super::scene_dirty`] absorbs those additions without
//! rewiring downstream consumers.
//! `Full` or `Paint` further (`PaintFill` / `PaintEffect`) is future
//! work; the classifier contract in [`super::scene_dirty`] absorbs
//! those additions without rewiring downstream consumers.

/// What changed on a node.
///
/// Produced by the differ (or directly emitted by a mutation API when
/// the caller already knows the change shape, e.g.
/// `MutationCommand::Translate` → `Geometry`). Consumed by
/// `MutationCommand::Translate` → `Layout`). Consumed by
/// [`super::SceneDirty::apply`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ChangeKind {
Expand All @@ -24,16 +23,17 @@ pub enum ChangeKind {
/// on `Container`/`Tray`. Both are surfaced uniformly by
/// [`super::lens::motion_of`].
///
/// Downstream work: per-subtree geometry update (world transforms +
/// bounds) + in-place layer-base patch + R-tree rebounds. No
/// layout recompute, no effect-tree rebuild, no layer-list
/// rebuild.
Geometry,
/// Semantically a layout change: a node's position in space is
/// a layout concern, regardless of whether a flex recompute is
/// needed. The downstream cache-refresh strategy (whether to
/// re-run Taffy, whether to rebuild geometry per-subtree, etc.)
/// is a separate concern owned by the dispatcher.
Layout,

/// Paint-only properties changed (fill, stroke, blend mode,
/// opacity, compatible effects).
///
/// No layout, no geometry, no effect-tree rebuild. The downstream
/// No layout, no geometry, no effect-tree rebuild. Downstream
/// work is LayerList rebuild (paint fields are cached on layer
/// structs) plus per-node picture-cache invalidation + viewport
/// damage.
Expand Down
10 changes: 5 additions & 5 deletions crates/grida-canvas/src/runtime/invalidation/differ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//!
//! Motion and paint detection are **lens-based** — the functions in
//! [`super::lens`] give a uniform view of those fields across all
//! node variants, so the Geometry and Paint fast paths cover every
//! node variants, so the Layout and Paint fast paths cover every
//! variant that has those fields without per-variant dispatch.
//!
//! What varies per variant is the "other" category — shape-specific
Expand All @@ -16,11 +16,11 @@
//! Variants whose "other" fields can't be cheaply compared (complex
//! non-`PartialEq` types like `TextStyleRec`, `AttributedString`) are
//! conservative: they always report `true`. In practice that means
//! Geometry/Paint fast paths don't fire for text today, only for
//! Layout/Paint fast paths don't fire for text today, only for
//! shapes and containers. Text fast paths can be added later by
//! extending `PartialEq` coverage of the text-style types.
//!
//! Guarantee: whenever the differ returns [`ChangeKind::Geometry`]
//! Guarantee: whenever the differ returns [`ChangeKind::Layout`]
//! or [`ChangeKind::Paint`], the non-matching field must be within
//! that category. When in doubt → [`ChangeKind::Full`].

Expand Down Expand Up @@ -48,7 +48,7 @@ pub fn diff_node(old: &Node, new: &Node) -> ChangeKind {
fn classify(motion: bool, paint: bool, other: bool) -> ChangeKind {
match (motion, paint, other) {
(false, false, false) => ChangeKind::None,
(true, false, false) => ChangeKind::Geometry,
(true, false, false) => ChangeKind::Layout,
(false, true, false) => ChangeKind::Paint,
_ => ChangeKind::Full,
}
Expand Down Expand Up @@ -259,7 +259,7 @@ fn group_other_differs(a: &GroupNodeRec, b: &GroupNodeRec) -> bool {

fn container_other_differs(a: &ContainerNodeRec, b: &ContainerNodeRec) -> bool {
// Note: `rotation` and `position` live in the Motion lens (see
// lens::motion_of) — a change in either alone counts as Geometry,
// lens::motion_of) — a change in either alone counts as Layout,
// not Full.
a.active != b.active
|| a.mask != b.mask
Expand Down
6 changes: 3 additions & 3 deletions crates/grida-canvas/src/runtime/invalidation/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//!
//! [`Motion`] uniformly covers both positioning models — leaf nodes'
//! `AffineTransform` and `Container`/`Tray` `position + rotation` —
//! so a Container position change registers as the same "Geometry"
//! so a Container position change registers as the same "Layout"
//! motion as a leaf transform change.

use crate::cg::prelude::*;
Expand All @@ -33,7 +33,7 @@ use math2::transform::AffineTransform;
/// expresses that placement as an `AffineTransform` (leaf variants) or
/// as a `position + rotation` pair (Container, Tray).
///
/// This lens drives the Geometry fast path. Whenever it reports a
/// This lens drives the Layout classifier. Whenever it reports a
/// difference but every other field of the node is unchanged, the
/// change is a rigid-body move — children's local placement is
/// unaffected, only their world transforms need re-deriving.
Expand Down Expand Up @@ -236,7 +236,7 @@ pub fn paint_of(node: &Node) -> PaintLens<'_> {
///
/// For leaf variants this is the `AffineTransform` field; for
/// Container/Tray it's the `(position, rotation)` pair. Drives the
/// [`ChangeKind::Geometry`](super::ChangeKind::Geometry) classifier.
/// [`ChangeKind::Layout`](super::ChangeKind::Layout) classifier.
pub fn motion_differs(a: &Node, b: &Node) -> bool {
motion_of(a) != motion_of(b)
}
Expand Down
10 changes: 5 additions & 5 deletions crates/grida-canvas/src/runtime/invalidation/scene_dirty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! | ---------- | :----: | :------: | :---------: | :--------: | :----: |
//! | None | | | | | |
//! | Paint | | | | | Full |
//! | Geometry | | id | | | Full |
//! | Layout | | id | | | Full |
//! | Full | id | id | id | ✓ | Full |

use std::collections::HashSet;
Expand Down Expand Up @@ -69,7 +69,7 @@ impl SceneDirty {
self.paint_touched.insert(id);
}

ChangeKind::Geometry => {
ChangeKind::Layout => {
self.geometry.insert(id);
self.damage = self.damage.merge(Damage::Full);
self.paint_touched.insert(id);
Expand Down Expand Up @@ -161,9 +161,9 @@ mod tests {
}

#[test]
fn geometry_sets_geometry_and_damage() {
fn layout_sets_geometry_and_damage() {
let mut d = SceneDirty::new();
d.apply(1, ChangeKind::Geometry);
d.apply(1, ChangeKind::Layout);
assert!(d.layout.is_empty());
assert!(d.geometry.contains(&1));
assert!(d.effect_tree.is_empty());
Expand Down Expand Up @@ -196,7 +196,7 @@ mod tests {
#[test]
fn multiple_kinds_accumulate() {
let mut d = SceneDirty::new();
d.apply(1, ChangeKind::Geometry);
d.apply(1, ChangeKind::Layout);
d.apply(2, ChangeKind::Paint);
d.apply(3, ChangeKind::Full);
assert!(d.geometry.contains(&1));
Expand Down
17 changes: 9 additions & 8 deletions crates/grida-canvas/src/runtime/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2047,7 +2047,7 @@ impl Renderer {
/// [`ChangeKind`].
///
/// Use when the mutation site already knows what changed (e.g.
/// `Translate` → `Geometry`). For `replace_node`-style mutations
/// `Translate` → `Layout`). For `replace_node`-style mutations
/// where the caller doesn't know, run
/// [`invalidation::diff_node`](super::invalidation::diff_node)
/// on the old and new values first.
Expand Down Expand Up @@ -2106,11 +2106,12 @@ impl Renderer {
}
self.rebuild_scene_caches();
} else if needs_geometry {
// Geometry fast path: per-subtree geometry update +
// partial layer/R-tree patch. Skips Taffy layout and
// effect-tree rebuild entirely. Covers both classical
// transform and layout-position changes via the
// [`super::invalidation::lens::motion_of`] lens.
// Layout fast path (ChangeKind::Layout): per-subtree
// geometry-cache update + partial layer/R-tree patch.
// Skips Taffy recompute and effect-tree rebuild entirely.
// Covers both classical transform and layout-position
// changes via the [`super::invalidation::lens::motion_of`]
// lens.
if super::invalidation::log_enabled() {
eprintln!(
"[invalidation] geometry dirty={} (fast path)",
Expand Down Expand Up @@ -2313,7 +2314,7 @@ impl Renderer {
/// `SceneDirty::geometry`).
/// - The caller is responsible for verifying no non-motion field
/// changed (the classifier ensures this by only calling this
/// path for `ChangeKind::Geometry`).
/// path for `ChangeKind::Layout`).
pub fn rebuild_geometry_and_layers_partial(
&mut self,
roots: &std::collections::HashSet<NodeId>,
Expand Down Expand Up @@ -2388,7 +2389,7 @@ impl Renderer {
/// Fast-path rebuild: geometry + layers only, skipping layout and
/// effect-tree rebuild.
///
/// Used when the classifier reports [`ChangeKind::Geometry`] — a
/// Used when the classifier reports [`ChangeKind::Layout`] — a
/// motion-only mutation does not alter layout constraints or
/// render-surface membership, only world transforms and bounds.
/// Skips two of the four phases `rebuild_scene_caches` runs.
Expand Down
14 changes: 14 additions & 0 deletions crates/grida-dev/src/bench/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ pub struct BenchArgs {
/// (or leave empty) to pick the first root node.
#[arg(long = "translate")]
pub translate: Option<String>,
/// Run a mutation benchmark of a specific kind.
///
/// Kinds: `translate-root`, `translate-leaf`, `resize`, `paint`, `delete`, `all`.
///
/// - `translate-root` — drag the first root container (largest subtree).
/// - `translate-leaf` — drag the deepest leaf (smallest subtree, realistic drag).
/// - `resize` — alternate width/height on the first resizable leaf.
/// - `paint` — alternate fill color on the first paintable leaf.
/// - `delete` — delete + reinsert a subtree each cycle (pairs deletion with insertion).
/// - `all` — run every kind sequentially, reporting each.
///
/// When omitted, and `--translate` is also omitted, standard camera passes run.
#[arg(long = "mutation")]
pub mutation: Option<String>,
}

#[derive(Args, Debug)]
Expand Down
2 changes: 1 addition & 1 deletion crates/grida-dev/src/bench/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub struct ScenarioParams {
}

/// Unified pass stats with per-stage breakdown (used for both pan and zoom).
#[derive(serde::Serialize, Clone)]
#[derive(serde::Serialize, Clone, Default)]
pub struct PassStats {
pub avg_us: u64,
pub fps: f64,
Expand Down
Loading
Loading