diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 4758d66c40..6fdbdda6af 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -179,6 +179,7 @@ mod node_registry_macros { ( ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]), RegistryEntry { + layout_meta: None, io: NodeIOTypes::new( concrete!(Context), core_types::registry::record_type::<$to>(), @@ -246,6 +247,7 @@ mod node_registry_macros { ( ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]), RegistryEntry { + layout_meta: None, io: NodeIOTypes::new( concrete!(Context), core_types::registry::record_type::<$to>(), @@ -291,6 +293,7 @@ mod node_registry_macros { ( ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]), RegistryEntry { + layout_meta: None, io: NodeIOTypes::new( concrete!(Context), core_types::registry::record_type::<$to>(), diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 327b35f8f4..1b1da89d4b 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -242,6 +242,52 @@ pub fn empty_layout() -> &'static Layout { EMPTY.get_or_init(Layout::default) } +/// Declarative record-io metadata for a node type, emitted by the macro into +/// its registry entry so the compiler can fold each wire's layout without +/// running the node's constructor. [`fold`](LayoutMeta::fold) reproduces the +/// layout the constructor derives at wiring today; the compiler layout pass +/// calls it over the proto graph instead. +#[derive(Clone, Debug)] +pub struct LayoutMeta { + /// Whether the node derives its layout from a carrier input; `false` writes + /// a fresh record from `Layout::default`. + pub carrier: bool, + /// The output element: a concrete write, or carried through from the carrier. + pub element: ElementSpec, + /// The attributes the node writes at its acting level. + pub writes: Vec, + /// The attributes removed from the carrier's layout, as `(name, level)`. + pub removes: Vec<(&'static str, u8)>, + /// The depth change the node applies to its carrier: `0` for elementwise and + /// flip nodes, `+1` for a creator, `-1` for a reducer. + pub level_delta: i8, +} + +/// Where a node's output element comes from, for [`LayoutMeta`]. +#[derive(Clone, Debug)] +pub enum ElementSpec { + /// The node writes this concrete element. + Concrete(ElementWrite), + /// The node carries the carrier's element through unchanged. + Carried, +} + +impl LayoutMeta { + /// Folds the node's output layout from its carrier's, reproducing what the + /// node's constructor derives at wiring. `carrier` is the carrier input's + /// layout, or `None` when the node writes a fresh record. + pub fn fold(&self, carrier: Option<&Layout>) -> Layout { + let carrier = carrier.filter(|_| self.carrier); + let base = carrier.map_or_else(Layout::default, |c| c.without(&self.removes)); + let depth = (carrier.map_or(0, |c| c.depth) as i8 + self.level_delta).max(0) as u8; + let element = match &self.element { + ElementSpec::Concrete(element) => *element, + ElementSpec::Carried => carrier.map_or_else(ElementWrite::default, |c| c.element), + }; + base.with_writes(depth, element, &self.writes) + } +} + /// A view of one record: a pointer whose layout is proven at wiring. #[derive(Clone, Copy, Debug)] pub struct Rec(*const u8); diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index b25cff2c4b..bbbaa1d71c 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -170,6 +170,11 @@ where unsafe { self.ptr.as_ref() }.extent(input) } + fn extent_at(&self, input: &Input, level: u8) -> crate::gpoll::GPoll { + // SAFETY: as in eval. + unsafe { self.ptr.as_ref() }.extent_at(input, level) + } + fn serialize(&self) -> Option> { // SAFETY: as in eval. unsafe { self.ptr.as_ref() }.serialize() @@ -278,6 +283,10 @@ pub type NodeConstructor = fn(Vec) -> Result, } pub fn construct(entry: &RegistryEntry, inputs: Vec) -> Result { @@ -502,6 +511,7 @@ mod tests { Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc>)) } let entry = RegistryEntry { + layout_meta: None, io: NodeIOTypes::new(concrete!(Context), concrete!(u32), vec![edge_type::()]), constructor: construct_strlen, }; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index b5b6450578..ad9bb7430f 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1741,6 +1741,23 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn } }, }; + let layout_meta_fn = format_ident!("{}_layout_meta", fn_name); + let carrier_present = !shape.skips_carrier(); + let element_spec = match &shape.element_write { + Some(ty) => quote!(#core_types::record::ElementSpec::Concrete(#core_types::record::element_write::<#ty>())), + None => quote!(#core_types::record::ElementSpec::Carried), + }; + let layout_meta_def = quote! { + #vis fn #layout_meta_fn() -> #core_types::record::LayoutMeta { + #core_types::record::LayoutMeta { + carrier: #carrier_present, + element: #element_spec, + writes: ::std::vec![#(#write_descs),*], + removes: ::std::vec![#(#remove_pairs),*], + level_delta: 0, + } + } + }; let reading_secondaries = reading_secondary_indices(®ular_fields, shape); let edge_args = regular_fields.iter().zip(&node_generics).map(|(field, generic)| { let name = &field.pat_ident.ident; @@ -1790,6 +1807,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn let write_names = (0..shape.write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot,)); quote! { #layout_def + #layout_meta_def #[automatically_derived] impl<#(#data_field_generic_idents,)* #(#node_generics,)*> #mod_name::#struct_name<#(#struct_type_params,)*> { diff --git a/node-graph/node-macro/src/codegen/entries.rs b/node-graph/node-macro/src/codegen/entries.rs index 5621417051..72b432891b 100644 --- a/node-graph/node-macro/src/codegen/entries.rs +++ b/node-graph/node-macro/src/codegen/entries.rs @@ -56,6 +56,7 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field let arity = regular_fields.len(); let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect(); let node_underscores: Vec = regular_fields.iter().map(|_| quote!(_)).collect(); + let carrier_present = flip_carrier(parsed); // Shorthand associated types in the output only resolve against the // generics' bounds, so rows name the output through a bounded alias. Only @@ -144,6 +145,13 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field }); Some(quote! { gcore::registry::RegistryEntry { + layout_meta: Some(gcore::record::LayoutMeta { + carrier: #carrier_present, + element: gcore::record::ElementSpec::Concrete(gcore::record::element_write::<#row_output>()), + writes: ::std::vec::Vec::new(), + removes: ::std::vec::Vec::new(), + level_delta: 0, + }), io: gcore::registry::NodeIOTypes::new( gcore::concrete!(gcore::context::ContextImpl<'static>), gcore::registry::record_type::<#row_output>(), @@ -273,6 +281,7 @@ fn routing_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fi quote! { pub fn #entries_name() -> ::std::vec::Vec { vec![gcore::registry::RegistryEntry { + layout_meta: None, io: gcore::registry::NodeIOTypes::new( gcore::concrete!(gcore::context::ContextImpl<'static>), gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#token_name)))), @@ -353,6 +362,7 @@ fn record_opaque_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regu quote! { pub fn #entries_name() -> ::std::vec::Vec { vec![gcore::registry::RegistryEntry { + layout_meta: None, io: gcore::registry::NodeIOTypes::new( gcore::concrete!(gcore::context::ContextImpl<'static>), gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed("T")))), @@ -464,9 +474,11 @@ fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fie (_, None) => unreachable!("non-token record nodes write an element"), }; + let layout_meta_fn = format_ident!("{}_layout_meta", fn_name); quote! { pub fn #entries_name() -> ::std::vec::Vec { vec![gcore::registry::RegistryEntry { + layout_meta: Some(self::#layout_meta_fn()), io: gcore::registry::NodeIOTypes::new( gcore::concrete!(gcore::context::ContextImpl<'static>), #io_output,