mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 17:58:13 +08:00
Unify the layout metadata model and collapse the single-row entry emitters
This commit is contained in:
@@ -249,20 +249,34 @@ pub fn empty_layout() -> &'static Layout {
|
|||||||
/// calls it over the proto graph instead.
|
/// calls it over the proto graph instead.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LayoutMeta {
|
pub struct LayoutMeta {
|
||||||
/// Whether the node derives its layout from a carrier input; `false` writes
|
/// Input indices whose layouts union to form the base: `[]` writes a fresh
|
||||||
/// a fresh record from `Layout::default`.
|
/// record, `[i]` derives from a single carrier, `[i, j, ..]` unions routing
|
||||||
pub carrier: bool,
|
/// sources.
|
||||||
/// The output element: a concrete write, or carried through from the carrier.
|
pub sources: Vec<u8>,
|
||||||
|
/// Attributes read from each input. Unused by [`fold`](LayoutMeta::fold);
|
||||||
|
/// recorded for later compiler analysis (read-offset resolution, per-name
|
||||||
|
/// cache dependencies, residency).
|
||||||
|
pub reads: Vec<InputReads>,
|
||||||
|
/// The output element: a concrete write, or carried through from the base.
|
||||||
pub element: ElementSpec,
|
pub element: ElementSpec,
|
||||||
/// The attributes the node writes at its acting level.
|
/// The attributes the node writes at its acting level.
|
||||||
pub writes: Vec<FieldWrite>,
|
pub writes: Vec<FieldWrite>,
|
||||||
/// The attributes removed from the carrier's layout, as `(name, level)`.
|
/// The attributes removed from the base layout, as `(name, level)`.
|
||||||
pub removes: Vec<(&'static str, u8)>,
|
pub removes: Vec<(&'static str, u8)>,
|
||||||
/// The depth change the node applies to its carrier: `0` for elementwise and
|
/// The depth change the node applies: `0` for elementwise and flip nodes,
|
||||||
/// flip nodes, `+1` for a creator, `-1` for a reducer.
|
/// `+1` for a creator, `-1` for a reducer.
|
||||||
pub level_delta: i8,
|
pub level_delta: i8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The attributes a node reads from one input, recorded on [`LayoutMeta`] for
|
||||||
|
/// later compiler analysis. A read and a write of an attribute carry the same
|
||||||
|
/// [`FieldWrite`] descriptor; the direction is the position on the node.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InputReads {
|
||||||
|
pub input: u8,
|
||||||
|
pub reads: Vec<FieldWrite>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Where a node's output element comes from, for [`LayoutMeta`].
|
/// Where a node's output element comes from, for [`LayoutMeta`].
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ElementSpec {
|
pub enum ElementSpec {
|
||||||
@@ -273,16 +287,21 @@ pub enum ElementSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMeta {
|
impl LayoutMeta {
|
||||||
/// Folds the node's output layout from its carrier's, reproducing what the
|
/// Folds the node's output layout from its inputs', reproducing what the
|
||||||
/// node's constructor derives at wiring. `carrier` is the carrier input's
|
/// node's constructor derives at wiring. `inputs` is indexed by proto-input
|
||||||
/// layout, or `None` when the node writes a fresh record.
|
/// position; [`sources`](LayoutMeta::sources) selects the base layouts, which
|
||||||
pub fn fold(&self, carrier: Option<&Layout>) -> Layout {
|
/// union (empty writes a fresh record).
|
||||||
let carrier = carrier.filter(|_| self.carrier);
|
pub fn fold(&self, inputs: &[Option<&Layout>]) -> Layout {
|
||||||
let base = carrier.map_or_else(Layout::default, |c| c.without(&self.removes));
|
let sources: Vec<&Layout> = self.sources.iter().map(|&i| inputs[i as usize].expect("layout fold source input has no layout")).collect();
|
||||||
let depth = (carrier.map_or(0, |c| c.depth) as i8 + self.level_delta).max(0) as u8;
|
let base = match sources.as_slice() {
|
||||||
|
[] => Layout::default(),
|
||||||
|
sources => Layout::union(sources),
|
||||||
|
}
|
||||||
|
.without(&self.removes);
|
||||||
|
let depth = (base.depth as i8 + self.level_delta).max(0) as u8;
|
||||||
let element = match &self.element {
|
let element = match &self.element {
|
||||||
ElementSpec::Concrete(element) => *element,
|
ElementSpec::Concrete(element) => *element,
|
||||||
ElementSpec::Carried => carrier.map_or_else(ElementWrite::default, |c| c.element),
|
ElementSpec::Carried => base.element,
|
||||||
};
|
};
|
||||||
base.with_writes(depth, element, &self.writes)
|
base.with_writes(depth, element, &self.writes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1747,10 +1747,25 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
Some(ty) => quote!(#core_types::record::ElementSpec::Concrete(#core_types::record::element_write::<#ty>())),
|
Some(ty) => quote!(#core_types::record::ElementSpec::Concrete(#core_types::record::element_write::<#ty>())),
|
||||||
None => quote!(#core_types::record::ElementSpec::Carried),
|
None => quote!(#core_types::record::ElementSpec::Carried),
|
||||||
};
|
};
|
||||||
|
let sources = if carrier_present { quote!(::std::vec![0u8]) } else { quote!(::std::vec![]) };
|
||||||
|
let reads_meta: Vec<TokenStream2> = regular_fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, field)| !field.attribute_reads.is_empty())
|
||||||
|
.map(|(index, field)| {
|
||||||
|
let descs = field.attribute_reads.iter().map(|read| {
|
||||||
|
let marker = &read.marker;
|
||||||
|
quote!(#core_types::record::FieldWrite::of::<#marker>(0))
|
||||||
|
});
|
||||||
|
let index = index as u8;
|
||||||
|
quote!(#core_types::record::InputReads { input: #index, reads: ::std::vec![#(#descs),*] })
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
let layout_meta_def = quote! {
|
let layout_meta_def = quote! {
|
||||||
#vis fn #layout_meta_fn() -> #core_types::record::LayoutMeta {
|
#vis fn #layout_meta_fn() -> #core_types::record::LayoutMeta {
|
||||||
#core_types::record::LayoutMeta {
|
#core_types::record::LayoutMeta {
|
||||||
carrier: #carrier_present,
|
sources: #sources,
|
||||||
|
reads: ::std::vec![#(#reads_meta),*],
|
||||||
element: #element_spec,
|
element: #element_spec,
|
||||||
writes: ::std::vec![#(#write_descs),*],
|
writes: ::std::vec![#(#write_descs),*],
|
||||||
removes: ::std::vec![#(#remove_pairs),*],
|
removes: ::std::vec![#(#remove_pairs),*],
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ pub(crate) fn entries_tokens(parsed: &ParsedNodeFn, class: &Class, struct_name:
|
|||||||
return quote!();
|
return quote!();
|
||||||
}
|
}
|
||||||
match class {
|
match class {
|
||||||
Class::RecordIo(_) => record_entries_tokens(parsed, struct_name, regular_fields),
|
|
||||||
Class::Routing(_) => routing_entries_tokens(parsed, struct_name, regular_fields),
|
|
||||||
Class::Flip { .. } => flip_entries_tokens(parsed, struct_name, regular_fields),
|
Class::Flip { .. } => flip_entries_tokens(parsed, struct_name, regular_fields),
|
||||||
Class::Opaque => record_opaque_entries_tokens(parsed, struct_name, regular_fields),
|
Class::RecordIo(_) | Class::Routing(_) | Class::Opaque => single_row_entries(parsed, class, struct_name, regular_fields),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +55,7 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
|
|||||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
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 node_underscores: Vec<TokenStream2> = regular_fields.iter().map(|_| quote!(_)).collect();
|
||||||
let carrier_present = flip_carrier(parsed);
|
let carrier_present = flip_carrier(parsed);
|
||||||
|
let sources = if carrier_present { quote!(::std::vec![0u8]) } else { quote!(::std::vec![]) };
|
||||||
|
|
||||||
// Shorthand associated types in the output only resolve against the
|
// Shorthand associated types in the output only resolve against the
|
||||||
// generics' bounds, so rows name the output through a bounded alias. Only
|
// generics' bounds, so rows name the output through a bounded alias. Only
|
||||||
@@ -146,7 +145,8 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
|
|||||||
Some(quote! {
|
Some(quote! {
|
||||||
gcore::registry::RegistryEntry {
|
gcore::registry::RegistryEntry {
|
||||||
layout_meta: Some(gcore::record::LayoutMeta {
|
layout_meta: Some(gcore::record::LayoutMeta {
|
||||||
carrier: #carrier_present,
|
sources: #sources,
|
||||||
|
reads: ::std::vec::Vec::new(),
|
||||||
element: gcore::record::ElementSpec::Concrete(gcore::record::element_write::<#row_output>()),
|
element: gcore::record::ElementSpec::Concrete(gcore::record::element_write::<#row_output>()),
|
||||||
writes: ::std::vec::Vec::new(),
|
writes: ::std::vec::Vec::new(),
|
||||||
removes: ::std::vec::Vec::new(),
|
removes: ::std::vec::Vec::new(),
|
||||||
@@ -182,303 +182,223 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The registry row of a routing node: one instance covers every element,
|
/// Which record wire an input claims and how its value is recovered. Base slots
|
||||||
/// sources claim generic record edges, and the constructor wraps each source
|
/// are the record edges whose layouts form the output; value slots are record
|
||||||
/// in its union translation and stores the union as the node's layout.
|
/// edges read for their layout; plain and lazy slots are ordinary edges.
|
||||||
fn routing_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
enum SlotKind {
|
||||||
let Some(routing) = routing_io(parsed) else {
|
/// A generic record edge whose element is only known at runtime; the runtime
|
||||||
return quote!();
|
/// type is captured for the output wrap or the union.
|
||||||
};
|
BaseGeneric(String),
|
||||||
let is_source = |field: &ParsedField| {
|
/// A concrete record carrier read for its layout.
|
||||||
let ty = match &field.ty {
|
BaseConcrete(Type),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type,
|
/// A concrete record edge read for its layout only.
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,
|
Value(Type),
|
||||||
};
|
/// A plain value edge.
|
||||||
matches!(ty, Type::Path(path) if path.path.get_ident() == Some(&routing.generic))
|
Plain(Type),
|
||||||
};
|
/// A lazy node edge.
|
||||||
let values_concrete = regular_fields.iter().filter(|field| !is_source(field)).all(|field| {
|
Lazy(Type),
|
||||||
let (ty, lend) = match &field.ty {
|
}
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => (ty, lend.is_some()),
|
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => (output_type, false),
|
impl SlotKind {
|
||||||
};
|
fn is_base(&self) -> bool {
|
||||||
!contains_open_generic(parsed, ty) && (lend || !type_disqualifies(ty))
|
matches!(self, SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_))
|
||||||
});
|
|
||||||
if !values_concrete {
|
|
||||||
return quote!();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The single registry row shared by record-io, routing, and opaque nodes: one
|
||||||
|
/// instance covers the wire, each input's edge type and downcast follow its
|
||||||
|
/// slot, and the output layout folds from the base slots.
|
||||||
|
fn single_row_entries(parsed: &ParsedNodeFn, class: &Class, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
||||||
let fn_name = &parsed.fn_name;
|
let fn_name = &parsed.fn_name;
|
||||||
let entries_name = format_ident!("{}_entries", fn_name);
|
let lend = |field: &ParsedField| matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. }));
|
||||||
let arity = regular_fields.len();
|
|
||||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
|
||||||
let token_name = routing.generic.to_string();
|
|
||||||
|
|
||||||
let input_types = regular_fields.iter().map(|field| {
|
let slots: Vec<SlotKind> = match class {
|
||||||
if is_source(field) {
|
Class::RecordIo(shape) => {
|
||||||
return quote!(gcore::registry::generic_record_edge_type(#token_name));
|
let carrier_in_fields = !shape.skips_carrier();
|
||||||
}
|
regular_fields
|
||||||
match &field.ty {
|
.iter()
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(gcore::registry::record_edge_type::<#ty>()),
|
.enumerate()
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(gcore::registry::edge_type::<#output_type>()),
|
.map(|(index, field)| {
|
||||||
}
|
let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else {
|
||||||
});
|
unreachable!("record nodes take no lazy inputs")
|
||||||
let source_layouts: Vec<Ident> = regular_fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, field)| is_source(field))
|
|
||||||
.map(|(index, _)| format_ident!("__layout_{index}"))
|
|
||||||
.collect();
|
|
||||||
let downcasts = regular_fields.iter().enumerate().map(|(index, field)| {
|
|
||||||
let name = &field.pat_ident.ident;
|
|
||||||
if is_source(field) {
|
|
||||||
let layout = format_ident!("__layout_{index}");
|
|
||||||
let handle = format_ident!("__handle_{index}");
|
|
||||||
let ty = format_ident!("__ty_{index}");
|
|
||||||
return quote! {
|
|
||||||
let #handle = inputs.next().unwrap();
|
|
||||||
let #ty = #handle.ty().clone();
|
|
||||||
let Some(#layout) = #handle.layout().cloned() else {
|
|
||||||
return Err(gcore::registry::ConstructionError::MissingLayout);
|
|
||||||
};
|
|
||||||
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
match &field.ty {
|
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => {
|
|
||||||
let handle = format_ident!("__handle_{index}");
|
|
||||||
let layout = format_ident!("__in_layout_{index}");
|
|
||||||
quote! {
|
|
||||||
let #handle = inputs.next().unwrap();
|
|
||||||
let Some(#layout) = #handle.layout().cloned() else {
|
|
||||||
return Err(gcore::registry::ConstructionError::MissingLayout);
|
|
||||||
};
|
};
|
||||||
let #name = #handle.downcast_record::<#ty>()?;
|
if carrier_in_fields && index == 0 {
|
||||||
}
|
return match &shape.carrier {
|
||||||
}
|
RecordCarrier::Token(token) => SlotKind::BaseGeneric(token.to_string()),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#output_type>()?;),
|
RecordCarrier::Read(carrier_ty) => SlotKind::BaseConcrete(carrier_ty.clone()),
|
||||||
}
|
RecordCarrier::None => unreachable!(),
|
||||||
});
|
};
|
||||||
let value_layout_args = regular_fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, field)| !is_source(field) && matches!(field.ty, ParsedFieldType::Regular(_)))
|
|
||||||
.map(|(index, _)| {
|
|
||||||
let layout = format_ident!("__in_layout_{index}");
|
|
||||||
quote!(&#layout,)
|
|
||||||
});
|
|
||||||
let source_wraps = regular_fields.iter().enumerate().filter(|(_, field)| is_source(field)).map(|(index, field)| {
|
|
||||||
let name = &field.pat_ident.ident;
|
|
||||||
let layout = format_ident!("__layout_{index}");
|
|
||||||
quote!(let #name = gcore::record::RecordSource::new(#name, &#layout, &__union);)
|
|
||||||
});
|
|
||||||
let first_source_ty = regular_fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, field)| is_source(field))
|
|
||||||
.map(|(index, _)| format_ident!("__ty_{index}"))
|
|
||||||
.expect("routing nodes have a source");
|
|
||||||
|
|
||||||
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)))),
|
|
||||||
vec![#(#input_types),*],
|
|
||||||
),
|
|
||||||
constructor: |inputs| {
|
|
||||||
if inputs.len() != #arity {
|
|
||||||
return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() });
|
|
||||||
}
|
}
|
||||||
let mut inputs = inputs.into_iter();
|
match field.attribute_reads.is_empty() {
|
||||||
#(#downcasts)*
|
false => SlotKind::Value(ty.clone()),
|
||||||
let __union = gcore::record::Layout::union(&[#(&#source_layouts),*]);
|
true => SlotKind::Plain(ty.clone()),
|
||||||
#(#source_wraps)*
|
}
|
||||||
let __node = #struct_name::new(#(#names,)* &__union, #(#value_layout_args)*);
|
})
|
||||||
Ok(gcore::registry::EdgeHandle::new_erased(
|
.collect()
|
||||||
::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>,
|
|
||||||
#first_source_ty,
|
|
||||||
))
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}
|
Class::Routing(routing) => {
|
||||||
}
|
let is_source = |field: &ParsedField| {
|
||||||
|
let ty = match &field.ty {
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type,
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,
|
||||||
|
};
|
||||||
|
matches!(ty, Type::Path(path) if path.path.get_ident() == Some(&routing.generic))
|
||||||
|
};
|
||||||
|
regular_fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
if is_source(field) {
|
||||||
|
return SlotKind::BaseGeneric(routing.generic.to_string());
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => SlotKind::Value(ty.clone()),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => SlotKind::Lazy(output_type.clone()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Class::Opaque => {
|
||||||
|
let is_record = |field: &ParsedField| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type));
|
||||||
|
regular_fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
if is_record(field) {
|
||||||
|
return SlotKind::BaseGeneric("T".to_string());
|
||||||
|
}
|
||||||
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => SlotKind::Plain(ty.clone()),
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => SlotKind::Lazy(output_type.clone()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
Class::Flip { .. } => unreachable!("flip has its own multi-row emitter"),
|
||||||
|
};
|
||||||
|
|
||||||
fn record_opaque_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
// Every non-base value/plain/lazy input must be concrete.
|
||||||
let is_record = |field: &ParsedField| matches!(&field.ty, ParsedFieldType::Node(NodeParsedField { output_type, .. }) if is_record_value(output_type));
|
let values_concrete = regular_fields.iter().zip(&slots).all(|(field, slot)| match slot {
|
||||||
let values_concrete = regular_fields.iter().filter(|field| !is_record(field)).all(|field| {
|
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => true,
|
||||||
let (ty, lend) = match &field.ty {
|
SlotKind::Value(ty) | SlotKind::Plain(ty) | SlotKind::Lazy(ty) => !contains_open_generic(parsed, ty) && (lend(field) || !type_disqualifies(ty)),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => (ty, lend.is_some()),
|
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => (output_type, false),
|
|
||||||
};
|
|
||||||
!contains_open_generic(parsed, ty) && (lend || !type_disqualifies(ty))
|
|
||||||
});
|
});
|
||||||
if !values_concrete {
|
if !values_concrete {
|
||||||
return quote!();
|
return quote!();
|
||||||
}
|
}
|
||||||
|
|
||||||
let fn_name = &parsed.fn_name;
|
|
||||||
let entries_name = format_ident!("{}_entries", fn_name);
|
let entries_name = format_ident!("{}_entries", fn_name);
|
||||||
let arity = regular_fields.len();
|
let arity = regular_fields.len();
|
||||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
||||||
|
|
||||||
let input_types = regular_fields.iter().map(|field| {
|
let input_types = slots.iter().map(|slot| match slot {
|
||||||
if is_record(field) {
|
SlotKind::BaseGeneric(name) => quote!(gcore::registry::generic_record_edge_type(#name)),
|
||||||
return quote!(gcore::registry::generic_record_edge_type("T"));
|
SlotKind::BaseConcrete(ty) | SlotKind::Value(ty) => quote!(gcore::registry::record_edge_type::<#ty>()),
|
||||||
}
|
SlotKind::Plain(ty) | SlotKind::Lazy(ty) => quote!(gcore::registry::edge_type::<#ty>()),
|
||||||
match &field.ty {
|
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(gcore::registry::edge_type::<#ty>()),
|
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(gcore::registry::edge_type::<#ty>()),
|
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(gcore::registry::edge_type::<#output_type>()),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
let downcasts = regular_fields.iter().enumerate().map(|(index, field)| {
|
|
||||||
let name = &field.pat_ident.ident;
|
let downcasts = names.iter().zip(&slots).enumerate().map(|(index, (name, slot))| {
|
||||||
if is_record(field) {
|
let handle = format_ident!("__handle_{index}");
|
||||||
let layout = format_ident!("__layout_{index}");
|
let layout = format_ident!("__layout_{index}");
|
||||||
let handle = format_ident!("__handle_{index}");
|
let ty = format_ident!("__ty_{index}");
|
||||||
let ty = format_ident!("__ty_{index}");
|
match slot {
|
||||||
return quote! {
|
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => quote! {
|
||||||
let #handle = inputs.next().unwrap();
|
let #handle = inputs.next().unwrap();
|
||||||
let #ty = #handle.ty().clone();
|
let #ty = #handle.ty().clone();
|
||||||
let Some(#layout) = #handle.layout().cloned() else {
|
let Some(#layout) = #handle.layout().cloned() else {
|
||||||
return Err(gcore::registry::ConstructionError::MissingLayout);
|
return Err(gcore::registry::ConstructionError::MissingLayout);
|
||||||
};
|
};
|
||||||
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
let #name = #handle.downcast_erased::<gcore::registry::ErasedRecordNode>(#ty.clone())?;
|
||||||
};
|
},
|
||||||
}
|
SlotKind::Value(value_ty) => quote! {
|
||||||
match &field.ty {
|
let #handle = inputs.next().unwrap();
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;),
|
let Some(#layout) = #handle.layout().cloned() else {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;),
|
return Err(gcore::registry::ConstructionError::MissingLayout);
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => quote!(let #name = inputs.next().unwrap().downcast::<#output_type>()?;),
|
};
|
||||||
|
let #name = #handle.downcast_record::<#value_ty>()?;
|
||||||
|
},
|
||||||
|
SlotKind::Plain(value_ty) | SlotKind::Lazy(value_ty) => quote!(let #name = inputs.next().unwrap().downcast::<#value_ty>()?;),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let first_record = regular_fields.iter().position(|field| is_record(field)).expect("record-opaque nodes have a record input");
|
|
||||||
let record_layout = format_ident!("__layout_{first_record}");
|
|
||||||
let record_ty = format_ident!("__ty_{first_record}");
|
|
||||||
|
|
||||||
quote! {
|
let base_indices: Vec<usize> = slots.iter().enumerate().filter(|(_, slot)| slot.is_base()).map(|(index, _)| index).collect();
|
||||||
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
|
let value_indices: Vec<usize> = slots.iter().enumerate().filter(|(_, slot)| matches!(slot, SlotKind::Value(_))).map(|(index, _)| index).collect();
|
||||||
vec![gcore::registry::RegistryEntry {
|
let value_layout_args = value_indices.iter().map(|index| {
|
||||||
layout_meta: None,
|
let layout = format_ident!("__layout_{index}");
|
||||||
io: gcore::registry::NodeIOTypes::new(
|
quote!(&#layout,)
|
||||||
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
});
|
||||||
gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed("T")))),
|
|
||||||
vec![#(#input_types),*],
|
let carried_meta = |sources: &[usize]| {
|
||||||
),
|
let sources = sources.iter().map(|index| *index as u8);
|
||||||
constructor: |inputs| {
|
quote! {
|
||||||
if inputs.len() != #arity {
|
Some(gcore::record::LayoutMeta {
|
||||||
return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() });
|
sources: ::std::vec![#(#sources),*],
|
||||||
}
|
reads: ::std::vec::Vec::new(),
|
||||||
let mut inputs = inputs.into_iter();
|
element: gcore::record::ElementSpec::Carried,
|
||||||
#(#downcasts)*
|
writes: ::std::vec::Vec::new(),
|
||||||
let __node = #struct_name::new(#(#names,)* &#record_layout);
|
removes: ::std::vec::Vec::new(),
|
||||||
Ok(gcore::registry::EdgeHandle::new_erased(
|
level_delta: 0,
|
||||||
::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>,
|
})
|
||||||
#record_ty,
|
|
||||||
))
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields: &[&ParsedField]) -> TokenStream2 {
|
|
||||||
let Some(shape) = record_shape(parsed) else {
|
|
||||||
return quote!();
|
|
||||||
};
|
};
|
||||||
let carrier_in_fields = !shape.skips_carrier();
|
|
||||||
let values_concrete = regular_fields.iter().skip(carrier_in_fields as usize).all(|field| match &field.ty {
|
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend, .. }) => !contains_open_generic(parsed, ty) && (lend.is_some() || !type_disqualifies(ty)),
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
if !values_concrete {
|
|
||||||
return quote!();
|
|
||||||
}
|
|
||||||
|
|
||||||
let fn_name = &parsed.fn_name;
|
let (io_output, wrap, prelude, new_layout_args, layout_meta) = match class {
|
||||||
let entries_name = format_ident!("{}_entries", fn_name);
|
Class::RecordIo(shape) => {
|
||||||
let arity = regular_fields.len();
|
let carrier_arg = (!shape.skips_carrier()).then(|| quote!(&__layout_0,));
|
||||||
let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect();
|
let (io_output, wrap) = match (&shape.carrier, &shape.element_write) {
|
||||||
let reading_secondaries = reading_secondary_indices(regular_fields, &shape);
|
(RecordCarrier::Token(token), _) => {
|
||||||
|
let token_name = token.to_string();
|
||||||
let input_types = regular_fields.iter().enumerate().map(|(index, field)| {
|
(
|
||||||
let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else {
|
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#token_name))))),
|
||||||
unreachable!("record nodes take no lazy inputs")
|
quote!(Ok(gcore::registry::EdgeHandle::new_erased(::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>, __ty_0))),
|
||||||
};
|
)
|
||||||
if carrier_in_fields && index == 0 {
|
|
||||||
return match &shape.carrier {
|
|
||||||
RecordCarrier::Token(token) => {
|
|
||||||
let name = token.to_string();
|
|
||||||
quote!(gcore::registry::generic_record_edge_type(#name))
|
|
||||||
}
|
}
|
||||||
RecordCarrier::Read(carrier_ty) => quote!(gcore::registry::record_edge_type::<#carrier_ty>()),
|
(_, Some(element)) => (
|
||||||
RecordCarrier::None => unreachable!(),
|
quote!(gcore::registry::record_type::<#element>()),
|
||||||
|
quote!(Ok(gcore::registry::EdgeHandle::new_record::<#element>(::std::sync::Arc::new(__node)))),
|
||||||
|
),
|
||||||
|
(_, None) => unreachable!("non-token record nodes write an element"),
|
||||||
};
|
};
|
||||||
|
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
|
||||||
|
(io_output, wrap, quote!(), quote!(#carrier_arg #(#value_layout_args)*), quote!(Some(self::#layout_meta_fn())))
|
||||||
}
|
}
|
||||||
match field.attribute_reads.is_empty() {
|
Class::Routing(routing) => {
|
||||||
true => quote!(gcore::registry::edge_type::<#ty>()),
|
let token_name = routing.generic.to_string();
|
||||||
false => quote!(gcore::registry::record_edge_type::<#ty>()),
|
let source_layouts = base_indices.iter().map(|index| format_ident!("__layout_{index}"));
|
||||||
}
|
let source_wraps = base_indices.iter().map(|index| {
|
||||||
});
|
let name = names[*index];
|
||||||
let downcasts = regular_fields.iter().enumerate().map(|(index, field)| {
|
let layout = format_ident!("__layout_{index}");
|
||||||
let name = &field.pat_ident.ident;
|
quote!(let #name = gcore::record::RecordSource::new(#name, &#layout, &__union);)
|
||||||
let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else {
|
});
|
||||||
unreachable!("record nodes take no lazy inputs")
|
let first_source_ty = format_ident!("__ty_{}", base_indices[0]);
|
||||||
};
|
let prelude = quote! {
|
||||||
if carrier_in_fields && index == 0 {
|
let __union = gcore::record::Layout::union(&[#(&#source_layouts),*]);
|
||||||
return quote! {
|
#(#source_wraps)*
|
||||||
let __carrier_handle = inputs.next().unwrap();
|
|
||||||
let __carrier_ty = __carrier_handle.ty().clone();
|
|
||||||
let Some(__carrier_layout) = __carrier_handle.layout().cloned() else {
|
|
||||||
return Err(gcore::registry::ConstructionError::MissingLayout);
|
|
||||||
};
|
|
||||||
let #name = __carrier_handle.downcast_erased::<gcore::registry::ErasedRecordNode>(__carrier_ty.clone())?;
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
if !field.attribute_reads.is_empty() {
|
|
||||||
let layout_local = format_ident!("__in_layout_{index}");
|
|
||||||
return quote! {
|
|
||||||
let __in_handle = inputs.next().unwrap();
|
|
||||||
let __in_ty = __in_handle.ty().clone();
|
|
||||||
let Some(#layout_local) = __in_handle.layout().cloned() else {
|
|
||||||
return Err(gcore::registry::ConstructionError::MissingLayout);
|
|
||||||
};
|
|
||||||
let #name = __in_handle.downcast_erased::<gcore::registry::ErasedRecordNode>(__in_ty)?;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;)
|
|
||||||
});
|
|
||||||
let wire_layout_arg = carrier_in_fields.then(|| quote!(&__carrier_layout,)).into_iter();
|
|
||||||
let input_layout_args = reading_secondaries.iter().map(|index| {
|
|
||||||
let layout_local = format_ident!("__in_layout_{index}");
|
|
||||||
quote!(&#layout_local,)
|
|
||||||
});
|
|
||||||
let (io_output, construct_output) = match (&shape.carrier, &shape.element_write) {
|
|
||||||
(RecordCarrier::Token(token), _) => {
|
|
||||||
let name = token.to_string();
|
|
||||||
(
|
(
|
||||||
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#name))))),
|
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed(#token_name))))),
|
||||||
quote!(Ok(gcore::registry::EdgeHandle::new_erased(
|
quote!(Ok(gcore::registry::EdgeHandle::new_erased(::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>, #first_source_ty))),
|
||||||
::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>,
|
prelude,
|
||||||
__carrier_ty,
|
quote!(&__union, #(#value_layout_args)*),
|
||||||
))),
|
carried_meta(&base_indices),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(_, Some(element)) => (
|
Class::Opaque => {
|
||||||
quote!(gcore::registry::record_type::<#element>()),
|
let first_record = base_indices[0];
|
||||||
quote!(Ok(gcore::registry::EdgeHandle::new_record::<#element>(::std::sync::Arc::new(__node)))),
|
let record_layout = format_ident!("__layout_{first_record}");
|
||||||
),
|
let record_ty = format_ident!("__ty_{first_record}");
|
||||||
(_, None) => unreachable!("non-token record nodes write an element"),
|
(
|
||||||
|
quote!(gcore::Type::Record(Box::new(gcore::Type::Generic(::std::borrow::Cow::Borrowed("T"))))),
|
||||||
|
quote!(Ok(gcore::registry::EdgeHandle::new_erased(::std::sync::Arc::new(__node) as ::std::sync::Arc<gcore::registry::ErasedRecordNode>, #record_ty))),
|
||||||
|
quote!(),
|
||||||
|
quote!(&#record_layout),
|
||||||
|
carried_meta(&[first_record]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Class::Flip { .. } => unreachable!("flip has its own multi-row emitter"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
|
|
||||||
quote! {
|
quote! {
|
||||||
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
|
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
|
||||||
vec![gcore::registry::RegistryEntry {
|
vec![gcore::registry::RegistryEntry {
|
||||||
layout_meta: Some(self::#layout_meta_fn()),
|
layout_meta: #layout_meta,
|
||||||
io: gcore::registry::NodeIOTypes::new(
|
io: gcore::registry::NodeIOTypes::new(
|
||||||
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
gcore::concrete!(gcore::context::ContextImpl<'static>),
|
||||||
#io_output,
|
#io_output,
|
||||||
@@ -490,8 +410,9 @@ fn record_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fie
|
|||||||
}
|
}
|
||||||
let mut inputs = inputs.into_iter();
|
let mut inputs = inputs.into_iter();
|
||||||
#(#downcasts)*
|
#(#downcasts)*
|
||||||
let __node = #struct_name::new(#(#names,)* #(#wire_layout_arg)* #(#input_layout_args)*);
|
#prelude
|
||||||
#construct_output
|
let __node = #struct_name::new(#(#names,)* #new_layout_args);
|
||||||
|
#wrap
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn layout_meta_folds_to_construction() {
|
||||||
|
let base = f64_layout(&[]);
|
||||||
|
let carried = multiply_opacity_layout(&base);
|
||||||
|
|
||||||
|
assert_eq!(multiply_opacity_layout_meta().fold(&[Some(&base)]), multiply_opacity_layout(&base));
|
||||||
|
assert_eq!(multiply_opacity_layout_meta().fold(&[Some(&carried)]), multiply_opacity_layout(&carried));
|
||||||
|
assert_eq!(measure_layout_meta().fold(&[Some(&base)]), measure_layout(&base));
|
||||||
|
assert_eq!(strip_opacity_layout_meta().fold(&[Some(&carried)]), strip_opacity_layout(&carried));
|
||||||
|
assert_eq!(relength_layout_meta().fold(&[Some(&base)]), relength_layout(&base));
|
||||||
|
assert_eq!(transfer_opacity_layout_meta().fold(&[Some(&base), None]), transfer_opacity_layout(&base));
|
||||||
|
assert_eq!(source_opacity_layout_meta().fold(&[]), source_opacity_layout());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn defaults_then_modify_then_stack() {
|
fn defaults_then_modify_then_stack() {
|
||||||
let arena = Arena::new(1024).unwrap();
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user