mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Emit declarative LayoutMeta from the node macro into registry entries
This commit is contained in:
@@ -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>(),
|
||||
|
||||
@@ -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<FieldWrite>,
|
||||
/// 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);
|
||||
|
||||
@@ -170,6 +170,11 @@ where
|
||||
unsafe { self.ptr.as_ref() }.extent(input)
|
||||
}
|
||||
|
||||
fn extent_at(&self, input: &Input, level: u8) -> crate::gpoll::GPoll<crate::gpoll::Extent> {
|
||||
// SAFETY: as in eval.
|
||||
unsafe { self.ptr.as_ref() }.extent_at(input, level)
|
||||
}
|
||||
|
||||
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
// SAFETY: as in eval.
|
||||
unsafe { self.ptr.as_ref() }.serialize()
|
||||
@@ -278,6 +283,10 @@ pub type NodeConstructor = fn(Vec<EdgeHandle>) -> Result<EdgeHandle, Constructio
|
||||
pub struct RegistryEntry {
|
||||
pub io: NodeIOTypes,
|
||||
pub constructor: NodeConstructor,
|
||||
/// Declarative record-io metadata for the compiler layout pass; `None` for
|
||||
/// nodes whose layout the pass does not yet fold (routing/opaque, hand-written
|
||||
/// rows), which keep the construction-time path.
|
||||
pub layout_meta: Option<crate::record::LayoutMeta>,
|
||||
}
|
||||
|
||||
pub fn construct(entry: &RegistryEntry, inputs: Vec<EdgeHandle>) -> Result<EdgeHandle, ConstructionError> {
|
||||
@@ -502,6 +511,7 @@ mod tests {
|
||||
Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc<ErasedNode<u32>>))
|
||||
}
|
||||
let entry = RegistryEntry {
|
||||
layout_meta: None,
|
||||
io: NodeIOTypes::new(concrete!(Context), concrete!(u32), vec![edge_type::<String>()]),
|
||||
constructor: construct_strlen,
|
||||
};
|
||||
|
||||
@@ -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,)*> {
|
||||
|
||||
@@ -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<TokenStream2> = 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<gcore::registry::RegistryEntry> {
|
||||
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<gcore::registry::RegistryEntry> {
|
||||
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<gcore::registry::RegistryEntry> {
|
||||
vec![gcore::registry::RegistryEntry {
|
||||
layout_meta: Some(self::#layout_meta_fn()),
|
||||
io: gcore::registry::NodeIOTypes::new(
|
||||
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||
#io_output,
|
||||
|
||||
Reference in New Issue
Block a user