diff --git a/node-graph/libraries/core-types/src/node.rs b/node-graph/libraries/core-types/src/node.rs index b660c1d5f7..fc4df24799 100644 --- a/node-graph/libraries/core-types/src/node.rs +++ b/node-graph/libraries/core-types/src/node.rs @@ -71,12 +71,12 @@ pub trait Node { None } - /// The record layout of this node's output; `None` for element-only - /// producers. Consumers read their carrier's layout through this at - /// wiring, and the wiring layer derives stack sizing from the same + /// The record layout of this node's output; the shared empty layout for + /// element-only producers. Consumers read their carrier's layout through + /// this at wiring, and the wiring layer derives stack sizing from the same /// layouts, in the dynamic executor and exported source alike. - fn layout(&self) -> Option<&crate::record::Layout> { - None + fn layout(&self) -> &crate::record::Layout { + crate::record::empty_layout() } fn eval_batch<'a>(&self, input: &'a Input, range: Range, scratch: Option<&'a mut [MaybeUninit]>) -> BatchStatus<'a, Self::Output> @@ -141,7 +141,7 @@ where (**self).serialize() } - fn layout(&self) -> Option<&crate::record::Layout> { + fn layout(&self) -> &crate::record::Layout { (**self).layout() } @@ -171,7 +171,7 @@ where (**self).serialize() } - fn layout(&self) -> Option<&crate::record::Layout> { + fn layout(&self) -> &crate::record::Layout { (**self).layout() } @@ -201,6 +201,10 @@ where (**self).serialize() } + fn layout(&self) -> &crate::record::Layout { + (**self).layout() + } + fn eval_batch<'a>(&self, input: &'a Input, range: Range, scratch: Option<&'a mut [MaybeUninit]>) -> BatchStatus<'a, Self::Output> where Input: InjectIndex + Copy, diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index fecf404359..327b35f8f4 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -234,6 +234,14 @@ impl Layout { #[derive(Clone, Copy, Debug, Default)] pub struct ElToken; +/// The shared empty layout: `depth` 0, no element, no fields, so `frame_bytes` +/// is 0. The `Node::layout` default returns it for element-only and test nodes, +/// which carry no record. +pub fn empty_layout() -> &'static Layout { + static EMPTY: std::sync::OnceLock = std::sync::OnceLock::new(); + EMPTY.get_or_init(Layout::default) +} + /// A view of one record: a pointer whose layout is proven at wiring. #[derive(Clone, Copy, Debug)] pub struct Rec(*const u8); @@ -1081,8 +1089,8 @@ where lift_poll(self.edge.eval(input), &self.layout, input.arena()) } - fn layout(&self) -> Option<&Layout> { - Some(&self.layout) + fn layout(&self) -> &Layout { + &self.layout } } @@ -1139,8 +1147,8 @@ where } } - fn layout(&self) -> Option<&Layout> { - Some(&self.union) + fn layout(&self) -> &Layout { + &self.union } } diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 07f5ef2e58..a0ad8837b1 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -158,7 +158,7 @@ where #[cfg(debug_assertions)] debug_assert_eq!( crate::record::stack::sp(), - sp_before + self.layout().map_or(0, |layout| layout.frame_bytes()), + sp_before + self.layout().frame_bytes(), "{} left the record stack misaligned", std::any::type_name::(), ); @@ -175,7 +175,7 @@ where unsafe { self.ptr.as_ref() }.serialize() } - fn layout(&self) -> Option<&crate::record::Layout> { + fn layout(&self) -> &crate::record::Layout { // SAFETY: as in eval. unsafe { self.ptr.as_ref() }.layout() } @@ -228,7 +228,7 @@ impl EdgeHandle { node: Box::new(SharedEdge::new(node)), share: |edge| Box::new(edge.downcast_ref::>().expect("share hook matches the stored edge type").share()), serialize: |edge| Node::::serialize(edge.downcast_ref::>().expect("serialize hook matches the stored edge type")), - layout: |edge| Node::::layout(edge.downcast_ref::>().expect("layout hook matches the stored edge type")), + layout: |edge| Some(Node::::layout(edge.downcast_ref::>().expect("layout hook matches the stored edge type"))), ty, } } diff --git a/node-graph/libraries/core-types/src/value.rs b/node-graph/libraries/core-types/src/value.rs index c2c4f9f4c5..c06a33bc4b 100644 --- a/node-graph/libraries/core-types/src/value.rs +++ b/node-graph/libraries/core-types/src/value.rs @@ -40,8 +40,8 @@ where crate::record::lift_poll(crate::gpoll::GPoll::Final(self.value.clone()), &self.layout, input.arena()) } - fn layout(&self) -> Option<&crate::record::Layout> { - Some(&self.layout) + fn layout(&self) -> &crate::record::Layout { + &self.layout } } diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index db8faee94a..5535a9c213 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1702,8 +1702,8 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn let record_layout_impl = match record.is_some() || routing.is_some() || flip || opaque { true => quote! { - fn layout(&self) -> Option<&#core_types::record::Layout> { - Some(&self.__layout) + fn layout(&self) -> &#core_types::record::Layout { + &self.__layout } }, false => quote!(), diff --git a/node-graph/nodes/gcore/src/record.rs b/node-graph/nodes/gcore/src/record.rs index 0f27b09644..31e445fb3d 100644 --- a/node-graph/nodes/gcore/src/record.rs +++ b/node-graph/nodes/gcore/src/record.rs @@ -208,7 +208,7 @@ mod tests { fn lifted_value(value: T) -> (core_types::record::RecordLift>, Layout) { let lift = core_types::record::RecordLift::::new(ValueNode(value)); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); (lift, layout) } @@ -234,7 +234,7 @@ mod tests { reserve_for(&[&source_layout, &modified, &stacked]); let chain = MultiplyOpacityNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), ValueNode(0.5), &modified); - assert_eq!(chain.layout(), Some(&stacked)); + assert_eq!(chain.layout(), &stacked); let GPoll::Final(value) = chain.eval(&ctx) else { panic!("expected a final record"); }; @@ -361,7 +361,7 @@ mod tests { reserve_for(&[&layout]); let node = SourceOpacityNode::new(ValueNode(3.), ValueNode(0.25)); - assert_eq!(Node::::layout(&node), Some(&layout)); + assert_eq!(Node::::layout(&node), &layout); let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record"); }; @@ -490,7 +490,7 @@ mod tests { let source_layout = f64_layout(&["opacity"]); let factor = core_types::record::RecordLift::::new(ValueNode(3.)); - let factor_layout = Node::::layout(&factor).unwrap().clone(); + let factor_layout = Node::::layout(&factor).clone(); reserve_for(&[&source_layout]); let node = BoostNode::new( @@ -499,7 +499,7 @@ mod tests { &source_layout, &factor_layout, ); - let out_layout = Node::::layout(&node).unwrap().clone(); + let out_layout = Node::::layout(&node).clone(); let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through to the output"); let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record"); @@ -526,7 +526,7 @@ mod tests { &source_layout, &factor_layout, ); - let out_layout = Node::::layout(&node).unwrap().clone(); + let out_layout = Node::::layout(&node).clone(); let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through the poll kernel"); let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record"); @@ -554,7 +554,7 @@ mod tests { &carrier_layout, &by_layout, ); - let out_layout = Node::::layout(&node).unwrap().clone(); + let out_layout = Node::::layout(&node).clone(); let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record"); }; @@ -593,7 +593,7 @@ mod tests { &runtime_layout, &source_id_layout, ); - let out_layout = Node::::layout(&node).unwrap().clone(); + let out_layout = Node::::layout(&node).clone(); let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the carrier's fields pass through the async source"); let GPoll::Final(value) = node.eval(&ctx) else { @@ -619,14 +619,14 @@ mod tests { let ctx = ContextImpl::root(&scope); let unit = core_types::record::RecordLift::<(), _>::new(ValueNode(())); - let unit_layout = Node::::layout(&unit).unwrap().clone(); + let unit_layout = Node::::layout(&unit).clone(); let content_layout = f64_layout(&["opacity"]); reserve_for(&[&content_layout]); let run = |opacity: Option| { let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)); let alternate = core_types::record::RecordLift::::new(CountingValue(evals.clone())); - let alternate_layout = Node::::layout(&alternate).unwrap().clone(); + let alternate_layout = Node::::layout(&alternate).clone(); let (content_layout, fields) = match opacity { Some(value) => (content_layout.clone(), vec![(content_layout.offset_of("opacity", 0).unwrap(), value)]), None => (f64_layout(&[]), vec![]), @@ -642,7 +642,7 @@ mod tests { let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record"); }; - let element = unsafe { Node::::layout(&node).unwrap().rec(&value).element::() }; + let element = unsafe { Node::::layout(&node).rec(&value).element::() }; (element, evals.load(std::sync::atomic::Ordering::Relaxed)) }; @@ -892,7 +892,7 @@ mod tests { let probed = |features: ContextFeatures| { let (modification, modification_layout) = lifted_value(ContextModification::from_sources(features, &[])); let node = crate::context_modification::ContextModificationNode::new(RealTimeProbe { layout: layout.clone() }, modification, &layout, &modification_layout); - assert_eq!(Node::::layout(&node), Some(&layout)); + assert_eq!(Node::::layout(&node), &layout); let GPoll::Final(value) = node.eval(&ctx) else { panic!("expected a final record"); }; @@ -944,7 +944,7 @@ mod tests { let ctx = ContextImpl::root(&scope); let lift = core_types::record::RecordLift::::new(ValueNode(String::from("parked"))); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let chain = core_types::record::RecordExtract::::new(lift, &layout); let GPoll::Final(text) = chain.eval(&ctx) else { @@ -1029,7 +1029,7 @@ mod tests { let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)); let lift = core_types::record::RecordLift::::new(CountingValue(evals.clone())); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let memo = crate::memo::MemoizeNode::new(lift, &layout); let GPoll::Final(value) = memo.eval(&ctx) else { diff --git a/node-graph/nodes/repeat/src/repeat_nodes.rs b/node-graph/nodes/repeat/src/repeat_nodes.rs index cea55b66b9..f0f5992a47 100644 --- a/node-graph/nodes/repeat/src/repeat_nodes.rs +++ b/node-graph/nodes/repeat/src/repeat_nodes.rs @@ -269,7 +269,7 @@ mod test { let x_translations = |values: [f64; 3]| values.map(|x| DVec2::new(x, 0.)).to_vec(); let lift = RecordLift::, _>::new(IndexProbe); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let forward = super::repeat(&ctx, ElementLazyInput::new(&lift, &cell, 0, &layout), 3, false).unwrap(); assert_eq!(row_translations(&forward, ATTR_TRANSFORM), x_translations([0., 1., 2.])); @@ -285,7 +285,7 @@ mod test { let count = 3; let lift = RecordLift::, _>::new(ValueNode(single_default_vector())); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let repeated = super::repeat_array(&ctx, ElementLazyInput::new(&lift, &cell, 0, &layout), direction, 0., count).unwrap(); assert_eq!(repeated.len(), count as usize); @@ -300,7 +300,7 @@ mod test { test_ctx!(ctx, cell); let lift = RecordLift::, _>::new(ValueNode(single_default_vector())); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let repeated = super::repeat_array(&ctx, ElementLazyInput::, _>::new(&lift, &cell, 0, &layout), DVec2::new(12., 10.), 45., 1).unwrap(); assert_eq!(repeated.len(), 1); @@ -314,7 +314,7 @@ mod test { let (radius, count) = (5., 4); let lift = RecordLift::, _>::new(ValueNode(single_default_vector())); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let repeated = super::repeat_radial(&ctx, ElementLazyInput::, _>::new(&lift, &cell, 0, &layout), 0., radius, count).unwrap(); assert_eq!(repeated.len(), count as usize); @@ -332,7 +332,7 @@ mod test { let points = List::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false))); let lift = RecordLift::, _>::new(PositionProbe); - let layout = Node::::layout(&lift).unwrap().clone(); + let layout = Node::::layout(&lift).clone(); let generated = super::repeat_on_points(&ctx, points.clone(), ElementLazyInput::new(&lift, &cell, 0, &layout), false).unwrap(); assert_eq!(row_translations(&generated, ATTR_TRANSFORM), positions.to_vec());