From 5599f1d652e04c581de60f7f71363d4b0e6c1438 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Fri, 11 Sep 2026 15:04:21 +0000 Subject: [PATCH] Give VectorRow a top lane and thread it through the walk recursion --- .../graphic-types/src/graphic/walk.rs | 73 ++++++++++++++++++- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/node-graph/libraries/graphic-types/src/graphic/walk.rs b/node-graph/libraries/graphic-types/src/graphic/walk.rs index b28436fb13..bffb4be056 100644 --- a/node-graph/libraries/graphic-types/src/graphic/walk.rs +++ b/node-graph/libraries/graphic-types/src/graphic/walk.rs @@ -252,6 +252,10 @@ pub struct VectorRow<'w> { scale: FlattenScale, layer_path: Option<&'w [NodeId]>, paint: LanePaint<'w>, + /// The lane of the walk's OWN top level whose subtree produced this row. + /// A leaf at any depth reports the top-level row it descends from, which is + /// the lane whose columns a consumer carries onto it. + top_lane: usize, } enum RowSourceRef<'w> { @@ -262,6 +266,15 @@ enum RowSourceRef<'w> { } impl VectorRow<'_> { + /// The lane of the walked level's top row this row descends from. + /// + /// Constant for every leaf under one top-level row, however deep the leaf + /// sits, which is what lets a consumer carry ONE lane's columns onto all of + /// them and resolve its output layout from the top level alone. + pub fn top_lane(&self) -> usize { + self.top_lane + } + /// The row's vector, borrowed. pub fn element(&self) -> &Vector { match &self.source { @@ -319,7 +332,16 @@ impl VectorRow<'_> { } } -fn walk_rows_of_run(item: &core_types::record::GroupItem, scale: FlattenScale, layer_path: Option<&[NodeId]>, paint: LanePaint<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) -> RowStep { +/// `top_lane` is `None` only when the run IS the walk's top level, where each +/// lane is its own top row; nested runs inherit the row they descend from. +fn walk_rows_of_run( + item: &core_types::record::GroupItem, + scale: FlattenScale, + layer_path: Option<&[NodeId]>, + paint: LanePaint<'_>, + top_lane: Option, + visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep, +) -> RowStep { let Some(run) = core_types::record::RunView::::new(item) else { return RowStep::Continue; }; @@ -329,6 +351,7 @@ fn walk_rows_of_run(item: &core_types::record::GroupItem, scale: FlattenScale, l scale, layer_path, paint, + top_lane: top_lane.unwrap_or(lane), }) { return RowStep::Stop; } @@ -342,14 +365,18 @@ fn walk_rows_of_run(item: &core_types::record::GroupItem, scale: FlattenScale, l /// level's parent layer path overwrites its rows, and non-vector content is /// discarded. A de-tabled leaf's row is its lane, attributes included. pub fn walk_vector_rows(level: GraphicLevel<'_>, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep) { - walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, visit); + walk_vector_rows_impl(level, FlattenScale::ROOT, None, PaintReach::NONE, None, visit); } +/// `top_lane` carries the walk's own top-level row down the recursion: `None` +/// at the top, where each lane names itself, and `Some(row)` thereafter so every +/// leaf beneath a row reports that row rather than its immediate parent's index. fn walk_vector_rows_impl<'a>( level: GraphicLevel<'a>, scale: FlattenScale, parent_layer_path: Option<&'a [NodeId]>, inherited: PaintReach<'a>, + top_lane: Option, visit: &mut dyn FnMut(VectorRow<'_>) -> RowStep, ) -> RowStep { if let GraphicLevel::Run(item) = level { @@ -359,7 +386,7 @@ fn walk_vector_rows_impl<'a>( true => inherited.paint, false => LanePaint::NONE, }; - return walk_rows_of_run(item, scale, parent_layer_path, paint, visit); + return walk_rows_of_run(item, scale, parent_layer_path, paint, top_lane, visit); } } let columns = PaintColumns::new(&level); @@ -370,30 +397,35 @@ fn walk_vector_rows_impl<'a>( true => reach.paint, false => LanePaint::NONE, }; + // At the top level this lane IS the row every leaf under it reports. + let row_top = top_lane.unwrap_or(index); let step = match element { Graphic::Vector(_) => visit(VectorRow { source: RowSourceRef::Lane(level, index), scale, layer_path: parent_layer_path, paint: row_paint, + top_lane: row_top, }), Graphic::Graphic(children) => walk_vector_rows_impl( GraphicLevel::Legacy(children), scale.composed(&level, index), level.try_attr::(index), reach.nested(), + Some(row_top), visit, ), Graphic::Group(group) => { let item = &group.content; if item.typed_lanes::().is_some() { - walk_rows_of_run(item, scale.composed(&level, index), level.try_attr::(index), row_paint, visit) + walk_rows_of_run(item, scale.composed(&level, index), level.try_attr::(index), row_paint, Some(row_top), visit) } else if item.typed_lanes::().is_some() { walk_vector_rows_impl( GraphicLevel::Run(item), scale.composed(&level, index), level.try_attr::(index), reach.into_group_graphics(), + Some(row_top), visit, ) } else { @@ -477,6 +509,39 @@ mod run_tests { use crate::graphic::{IntoGraphicList, map_groups_to_legacy, run_to_legacy_list}; use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed}; + #[test] + fn leaves_at_different_depths_report_the_same_top_row() { + // One top-level row holding a leaf at depth 1 AND a leaf at depth 2, so + // "which lane's columns does this row carry" cannot depend on the depth + // the leaf was found at. That independence is what lets an adopter's + // output layout resolve from the input's TOP-LEVEL layout alone instead + // of being discovered per leaf. + // + // The nesting sits at top row 1 on purpose: both inner leaves have index + // 0 within their own parents, so an implementation reporting the + // immediate lane rather than the top row would answer 0 and be caught, + // where a fixture nested at row 0 would have agreed by coincidence. + let deep = List::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(3., 3.)))); + let mut mixed = List::new(); + mixed.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ZERO)))); + mixed.push(Item::new_from_element(Graphic::Graphic(deep))); + + let mut top = List::new(); + top.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::new(9., 9.))))); + top.push(Item::new_from_element(Graphic::Graphic(mixed))); + + let mut reported = Vec::new(); + walk_vector_rows(GraphicLevel::Legacy(&top), &mut |row| { + reported.push(row.top_lane()); + RowStep::Continue + }); + + assert_eq!(reported.len(), 3, "one top-level leaf plus two leaves under row 1"); + assert_eq!(reported[0], 0, "a top-level leaf reports its own lane"); + assert_eq!(reported[1], 1, "the depth-1 leaf reports the top row it descends from"); + assert_eq!(reported[2], 1, "the depth-2 leaf reports the SAME top row, not its own index within its parent"); + } + #[test] fn the_vector_row_walk_matches_the_legacy_flatten() { let inner_vector = unit_square_at(DVec2::ZERO);