diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index d315314606..548e26b278 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -284,6 +284,10 @@ pub struct LayoutMeta { /// The depth change the node applies: `0` for elementwise and flip nodes, /// `+1` for a creator, `-1` for a reducer. pub level_delta: i8, + /// The materialized subject a reducer folds, as `(input, levels)`: the + /// output keeps the subject's levels above the folded ones, so the depth + /// derives from its layout even though it contributes no fields. + pub folded: Option<(u8, u8)>, } /// The attributes a node reads from one input, recorded on [`LayoutMeta`] for @@ -314,6 +318,7 @@ impl LayoutMeta { writes: Vec::new(), removes: Vec::new(), level_delta: 0, + folded: None, } } @@ -328,7 +333,13 @@ impl LayoutMeta { sources => Layout::union(sources), } .without(&self.removes); - let depth = (base.depth as i8 + self.level_delta).max(0) as u8; + let depth = match self.folded { + Some((input, levels)) => { + let subject = inputs[input as usize].expect("layout fold folded input has no layout"); + (subject.depth.saturating_sub(levels) as i8 + self.level_delta).max(0) as u8 + } + None => (base.depth as i8 + self.level_delta).max(0) as u8, + }; let element = match &self.element { ElementSpec::Concrete(element) => *element, ElementSpec::Carried => base.element, diff --git a/node-graph/node-macro/src/codegen/ir.rs b/node-graph/node-macro/src/codegen/ir.rs index a66bccbcbd..cb206e8101 100644 --- a/node-graph/node-macro/src/codegen/ir.rs +++ b/node-graph/node-macro/src/codegen/ir.rs @@ -236,6 +236,10 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t quote!((<#marker as #core_types::attribute::Attribute>::NAME, #level)) }); let level_delta = level_delta(node); + let folded = match folded_subject(node) { + Some((index, levels)) => quote!(::core::option::Option::Some((#index, #levels))), + None => quote!(::core::option::Option::None), + }; quote! { #core_types::record::LayoutMeta { sources: ::std::vec![#(#sources),*], @@ -244,10 +248,20 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t writes: ::std::vec![#(#writes),*], removes: ::std::vec![#(#removes),*], level_delta: #level_delta, + folded: #folded, } } } +/// The materialized subject a node folds, as `(input, levels)`. +pub(crate) fn folded_subject(node: &Node) -> Option<(u8, u8)> { + node.inputs + .iter() + .enumerate() + .find(|(index, input)| input.subject && materialized_levels(node, *index) > 0) + .map(|(index, _)| (index as u8, materialized_levels(node, index))) +} + fn field_writes(attrs: &[LevelAttr], core_types: &TokenStream2) -> Vec { attrs .iter() diff --git a/node-graph/nodes/gcore/src/record.rs b/node-graph/nodes/gcore/src/record.rs index 66dbc1815a..b02f26f969 100644 --- a/node-graph/nodes/gcore/src/record.rs +++ b/node-graph/nodes/gcore/src/record.rs @@ -636,6 +636,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 1, + folded: None, }; let node = install( RepeatNode::new(RecordSource::new(bare_source(&base, 7.), &base, &base), count_edge, reverse_edge, &base, &count_layout, &reverse_layout), @@ -671,6 +672,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 1, + folded: None, }; let repeat = install( RepeatNode::new(RecordSource::new(IndexSourceNode { layout: base.clone() }, &base, &base), count_edge, reverse_edge, &base, &count_layout, &reverse_layout), @@ -712,6 +714,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 1, + folded: None, }; let repeat = install( RepeatNode::new(RecordSource::new(IndexSourceNode { layout: base.clone() }, &base, &base), count_edge, reverse_edge, &base, &count_layout, &reverse_layout), @@ -755,6 +758,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 1, + folded: None, }; let repeat = install( RepeatNode::new(RecordSource::new(content, &leveled_content, &leveled_content), count_edge, reverse_edge, &leveled_content, &count_layout, &reverse_layout), @@ -854,6 +858,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 0, + folded: None, }; let node = install( ExtendNode::new(RecordSource::new(base, &base_layout, &union), RecordSource::new(new, &new_layout, &union), &union), @@ -903,6 +908,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 0, + folded: None, }; let build = |index: f64| { let content = LeveledSourceNode { @@ -958,6 +964,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 0, + folded: None, }; install( IndexElementsNode::new(RecordSource::new(content, &layout, &layout), index_edge, &layout, &index_layout), @@ -1174,6 +1181,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 1, + folded: None, }; let nested = install( RepeatNode::new(RecordSource::new(content, &leveled_content, &leveled_content), count_edge, reverse_edge, &leveled_content, &count_layout, &reverse_layout), @@ -1234,6 +1242,7 @@ mod tests { writes: vec![], removes: vec![], level_delta: 1, + folded: None, }; let repeat = install( RepeatNode::new(RecordSource::new(IndexSourceNode { layout: base.clone() }, &base, &base), count_edge, reverse_edge, &base, &count_layout, &reverse_layout), diff --git a/node-graph/nodes/graphic/src/record.rs b/node-graph/nodes/graphic/src/record.rs index c2285ac42e..7092f6d0e8 100644 --- a/node-graph/nodes/graphic/src/record.rs +++ b/node-graph/nodes/graphic/src/record.rs @@ -409,6 +409,7 @@ mod tests { writes: vec![], removes: vec![], level_delta, + folded: None, } }