Install record layouts from the pass via set_layout

This commit is contained in:
Dennis Kobert
2026-08-15 11:20:01 +00:00
parent bfa48439c8
commit f9e30c4c77
10 changed files with 302 additions and 165 deletions
+2 -2
View File
@@ -372,7 +372,7 @@ macro_rules! tagged_value {
} }
$( $(
if ty == core_types::registry::record_edge_type::<$ty>() { if ty == core_types::registry::record_edge_type::<$ty>() {
let layout = handle.layout().ok_or_else(|| "a record edge must carry its layout".to_string())?.clone(); let layout = handle.layout().clone();
return Ok(handle return Ok(handle
.downcast_record::<$ty>() .downcast_record::<$ty>()
.map_err(|e| format!("{e:?}"))? .map_err(|e| format!("{e:?}"))?
@@ -381,7 +381,7 @@ macro_rules! tagged_value {
} }
)* )*
if ty == core_types::registry::record_edge_type::<RenderOutput>() { if ty == core_types::registry::record_edge_type::<RenderOutput>() {
let layout = handle.layout().ok_or_else(|| "a record edge must carry its layout".to_string())?.clone(); let layout = handle.layout().clone();
return Ok(handle return Ok(handle
.downcast_record::<RenderOutput>() .downcast_record::<RenderOutput>()
.map_err(|e| format!("{e:?}"))? .map_err(|e| format!("{e:?}"))?
+13 -5
View File
@@ -128,7 +128,7 @@ impl ConstructionArgs {
pub(crate) struct Resolved { pub(crate) struct Resolved {
pub io: Option<NodeIOTypes>, pub io: Option<NodeIOTypes>,
pub layout_meta: Option<core_types::record::LayoutMeta>, pub layout_meta: Option<core_types::record::LayoutMeta>,
pub layout: Option<core_types::record::Layout>, pub layout: Option<core_types::record::RecordLayout>,
} }
impl PartialEq for Resolved { impl PartialEq for Resolved {
@@ -221,7 +221,7 @@ impl ProtoNode {
} }
} }
pub fn resolved_layout(&self) -> Option<&core_types::record::Layout> { pub fn resolved_layout(&self) -> Option<&core_types::record::RecordLayout> {
self.resolved.layout.as_ref() self.resolved.layout.as_ref()
} }
} }
@@ -372,10 +372,18 @@ impl ProtoNetwork {
let layout = { let layout = {
let node = &self.nodes[index].1; let node = &self.nodes[index].1;
match &node.construction_args { match &node.construction_args {
ConstructionArgs::Value(value) => value.element_write().map(|element| core_types::record::Layout::default().with_writes(0, element, &[])), ConstructionArgs::Value(value) => value.element_write().map(|element| {
let layout = core_types::record::Layout::default().with_writes(0, element, &[]);
core_types::record::RecordLayout {
frame_bytes: layout.frame_bytes(),
plan: Vec::new(),
layout,
}
}),
ConstructionArgs::Nodes(inputs) => node.resolved.layout_meta.as_ref().and_then(|meta| { ConstructionArgs::Nodes(inputs) => node.resolved.layout_meta.as_ref().and_then(|meta| {
let input_layouts: Vec<Option<&core_types::record::Layout>> = inputs.iter().map(|input| self.nodes[input.0 as usize].1.resolved.layout.as_ref()).collect(); let input_layouts: Vec<Option<&core_types::record::Layout>> =
meta.sources.iter().all(|&source| input_layouts[source as usize].is_some()).then(|| meta.fold(&input_layouts)) inputs.iter().map(|input| self.nodes[input.0 as usize].1.resolved.layout.as_ref().map(|resolved| &resolved.layout)).collect();
meta.sources.iter().all(|&source| input_layouts[source as usize].is_some()).then(|| meta.resolve(&input_layouts))
}), }),
ConstructionArgs::Inline(_) => None, ConstructionArgs::Inline(_) => None,
} }
@@ -476,15 +476,16 @@ impl BorrowTree {
.clone() .clone()
.to_edge() .to_edge()
.map_err(|error| vec![GraphError::new(&proto_node, GraphErrorType::ConstructionFailed(error))])?; .map_err(|error| vec![GraphError::new(&proto_node, GraphErrorType::ConstructionFailed(error))])?;
debug_assert!(proto_node.resolved_layout().map_or(true, |expected| node.layout() == Some(expected)), "layout pass disagrees with construction for {}", proto_node.identifier.as_str());
self.store_node(node, id, path.into()); self.store_node(node, id, path.into());
} }
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ids) => { ConstructionArgs::Nodes(ids) => {
let construction_nodes = self.node_deps(ids); let construction_nodes = self.node_deps(ids);
let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?; let constructor = typing_context.constructor(id).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
let node = constructor(construction_nodes).map_err(|error| vec![GraphError::new(&proto_node, GraphErrorType::ConstructionFailed(format!("{error:?}")))])?; let mut node = constructor(construction_nodes).map_err(|error| vec![GraphError::new(&proto_node, GraphErrorType::ConstructionFailed(format!("{error:?}")))])?;
debug_assert!(proto_node.resolved_layout().map_or(true, |expected| node.layout() == Some(expected)), "layout pass disagrees with construction for {}", proto_node.identifier.as_str()); if let Some(layout) = proto_node.resolved_layout() {
node.set_layout(layout.clone());
}
self.store_node(node, id, path.into()); self.store_node(node, id, path.into());
} }
}; };
@@ -503,7 +504,7 @@ impl BorrowTree {
} }
fn frame_bytes(&self, id: NodeId) -> usize { fn frame_bytes(&self, id: NodeId) -> usize {
self.nodes.get(&id).and_then(|(handle, _)| handle.layout()).map_or(0, |layout| layout.frame_bytes()) self.nodes.get(&id).map_or(0, |(handle, _)| handle.layout().frame_bytes())
} }
} }
@@ -191,9 +191,7 @@ mod node_registry_macros {
} }
let mut inputs = inputs.into_iter(); let mut inputs = inputs.into_iter();
let handle = inputs.next().unwrap(); let handle = inputs.next().unwrap();
let Some(layout) = handle.layout().cloned() else { let layout = handle.layout().clone();
return Err(ConstructionError::MissingLayout);
};
let node = graphene_std::ops::IntoNode::<$to, _, $from>::new(handle.downcast_record::<$from>()?, &layout); let node = graphene_std::ops::IntoNode::<$to, _, $from>::new(handle.downcast_record::<$from>()?, &layout);
Ok(EdgeHandle::new_record::<$to>(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>)) Ok(EdgeHandle::new_record::<$to>(std::sync::Arc::new(node) as std::sync::Arc<core_types::registry::ErasedRecordNode>))
}, },
@@ -265,10 +263,8 @@ mod node_registry_macros {
let mut inputs = inputs.into_iter(); let mut inputs = inputs.into_iter();
let mut claim = || { let mut claim = || {
let handle = inputs.next().unwrap(); let handle = inputs.next().unwrap();
let Some(layout) = handle.layout().cloned() else { let layout = handle.layout().clone();
return Err(ConstructionError::MissingLayout); Ok::<_, ConstructionError>((handle, layout))
};
Ok((handle, layout))
}; };
let (value, value_layout) = claim()?; let (value, value_layout) = claim()?;
let (converter, converter_layout) = claim()?; let (converter, converter_layout) = claim()?;
@@ -305,13 +301,9 @@ mod node_registry_macros {
} }
let mut inputs = inputs.into_iter(); let mut inputs = inputs.into_iter();
let value = inputs.next().unwrap(); let value = inputs.next().unwrap();
let Some(value_layout) = value.layout().cloned() else { let value_layout = value.layout().clone();
return Err(ConstructionError::MissingLayout);
};
let converter = inputs.next().unwrap(); let converter = inputs.next().unwrap();
let Some(converter_layout) = converter.layout().cloned() else { let converter_layout = converter.layout().clone();
return Err(ConstructionError::MissingLayout);
};
let node = graphene_std::ops::ConvertNode::<$to, _, _, $from, $convert>::new( let node = graphene_std::ops::ConvertNode::<$to, _, _, $from, $convert>::new(
value.downcast_record::<$from>()?, value.downcast_record::<$from>()?,
converter.downcast_record::<$convert>()?, converter.downcast_record::<$convert>()?,
@@ -278,6 +278,9 @@ pub trait Node<Input> {
crate::record::empty_layout() crate::record::empty_layout()
} }
/// Installs this node's resolved record layout; a no-op unless it produces records.
fn set_layout(&mut self, _layout: crate::record::RecordLayout) {}
fn eval_batch<'a>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<Self::Output>]>) -> BatchStatus<'a, Self::Output> fn eval_batch<'a>(&'a self, input: &'a Input, range: Range<u64>, scratch: Option<&'a mut [MaybeUninit<Self::Output>]>) -> BatchStatus<'a, Self::Output>
where where
Input: InjectIndex + Copy, Input: InjectIndex + Copy,
+26 -2
View File
@@ -243,6 +243,14 @@ pub fn empty_layout() -> &'static Layout {
} }
/// Declarative record-io metadata for a node type, emitted by the macro into /// Declarative record-io metadata for a node type, emitted by the macro into
/// A record node's output layout with the frame size and carrier copy plan derived from it.
#[derive(Clone, Debug, Default)]
pub struct RecordLayout {
pub layout: Layout,
pub frame_bytes: usize,
pub plan: Vec<(usize, usize, usize)>,
}
/// its registry entry so the compiler can fold each wire's layout without /// its registry entry so the compiler can fold each wire's layout without
/// running the node's constructor. [`fold`](LayoutMeta::fold) reproduces the /// running the node's constructor. [`fold`](LayoutMeta::fold) reproduces the
/// layout the constructor derives at wiring today; the compiler layout pass /// layout the constructor derives at wiring today; the compiler layout pass
@@ -287,8 +295,7 @@ pub enum ElementSpec {
} }
impl LayoutMeta { impl LayoutMeta {
/// The meta of an elementwise carrier flip that retypes input 0's element, /// Keeps input 0's layout but replaces its element.
/// preserving its depth and attributes: what an `Into`/`Convert` coercion derives.
pub fn retype(element: ElementWrite) -> Self { pub fn retype(element: ElementWrite) -> Self {
Self { Self {
sources: vec![0], sources: vec![0],
@@ -318,6 +325,23 @@ impl LayoutMeta {
}; };
base.with_writes(depth, element, &self.writes) base.with_writes(depth, element, &self.writes)
} }
/// [`fold`](LayoutMeta::fold) with the frame size and carrier copy plan derived from it.
pub fn resolve(&self, inputs: &[Option<&Layout>]) -> RecordLayout {
let layout = self.fold(inputs);
let frame_bytes = layout.frame_bytes();
let plan = match self.sources.first() {
// A reducer collapses its carrier's levels, so it writes a fresh record rather than copying fields down.
Some(&source) if self.level_delta >= 0 => {
let from = inputs[source as usize].expect("layout resolve source input has no layout");
let carry_element = matches!(self.element, ElementSpec::Carried);
let removes: Vec<(&str, u8)> = self.removes.clone();
copy_plan(from, &layout, carry_element, &removes)
}
_ => Vec::new(),
};
RecordLayout { layout, frame_bytes, plan }
}
} }
/// A view of one record: a pointer whose layout is proven at wiring. /// A view of one record: a pointer whose layout is proven at wiring.
@@ -113,7 +113,6 @@ pub fn cache_key<C: CacheHash + ?Sized>(ctx: &C) -> u64 {
pub enum ConstructionError { pub enum ConstructionError {
Arity { expected: usize, got: usize }, Arity { expected: usize, got: usize },
Type { expected: Box<Type>, found: Box<Type> }, Type { expected: Box<Type>, found: Box<Type> },
MissingLayout,
} }
pub struct SharedEdge<N: ?Sized> { pub struct SharedEdge<N: ?Sized> {
@@ -193,7 +192,8 @@ pub struct EdgeHandle {
node: Box<DynEdge>, node: Box<DynEdge>,
share: fn(&DynEdge) -> Box<DynEdge>, share: fn(&DynEdge) -> Box<DynEdge>,
serialize: fn(&DynEdge) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>, serialize: fn(&DynEdge) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
layout: fn(&DynEdge) -> Option<&crate::record::Layout>, layout: fn(&DynEdge) -> &crate::record::Layout,
set_layout: fn(&mut DynEdge, crate::record::RecordLayout),
ty: Type, ty: Type,
} }
@@ -228,7 +228,12 @@ impl EdgeHandle {
node: Box::new(SharedEdge::new(node)), node: Box::new(SharedEdge::new(node)),
share: |edge| Box::new(edge.downcast_ref::<SharedEdge<N>>().expect("share hook matches the stored edge type").share()), share: |edge| Box::new(edge.downcast_ref::<SharedEdge<N>>().expect("share hook matches the stored edge type").share()),
serialize: |edge| Node::<ContextImpl>::serialize(edge.downcast_ref::<SharedEdge<N>>().expect("serialize hook matches the stored edge type")), serialize: |edge| Node::<ContextImpl>::serialize(edge.downcast_ref::<SharedEdge<N>>().expect("serialize hook matches the stored edge type")),
layout: |edge| Some(Node::<ContextImpl>::layout(edge.downcast_ref::<SharedEdge<N>>().expect("layout hook matches the stored edge type"))), layout: |edge| Node::<ContextImpl>::layout(edge.downcast_ref::<SharedEdge<N>>().expect("layout hook matches the stored edge type")),
set_layout: |edge, layout| {
let shared = edge.downcast_mut::<SharedEdge<N>>().expect("set_layout hook matches the stored edge type");
let node = std::sync::Arc::get_mut(&mut shared.own).expect("layout is installed before the node is shared");
Node::<ContextImpl>::set_layout(node, layout);
},
ty, ty,
} }
} }
@@ -243,6 +248,7 @@ impl EdgeHandle {
share: self.share, share: self.share,
serialize: self.serialize, serialize: self.serialize,
layout: self.layout, layout: self.layout,
set_layout: self.set_layout,
ty: self.ty.clone(), ty: self.ty.clone(),
} }
} }
@@ -251,10 +257,14 @@ impl EdgeHandle {
(self.serialize)(&*self.node) (self.serialize)(&*self.node)
} }
pub fn layout(&self) -> Option<&crate::record::Layout> { pub fn layout(&self) -> &crate::record::Layout {
(self.layout)(&*self.node) (self.layout)(&*self.node)
} }
pub fn set_layout(&mut self, layout: crate::record::RecordLayout) {
(self.set_layout)(&mut *self.node, layout);
}
pub fn downcast<T: 'static>(self) -> Result<SharedEdge<ErasedNode<T>>, ConstructionError> { pub fn downcast<T: 'static>(self) -> Result<SharedEdge<ErasedNode<T>>, ConstructionError> {
self.downcast_erased(edge_type::<T>()) self.downcast_erased(edge_type::<T>())
} }
+58 -49
View File
@@ -403,19 +403,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}) })
.into_iter() .into_iter()
.flatten(); .flatten();
let flip_prelude = flip // The output layout, frame size, and copy plan are installed by `set_layout`.
.then(|| match carrier_flip {
true => quote! {
let __layout = __in_0.with_writes(__in_0.depth, gcore::record::element_write::<#slot_value_type>(), &[]);
let __plan = gcore::record::copy_plan(__in_0, &__layout, false, &[]);
let __frame_bytes = __layout.frame_bytes();
},
false => quote! {
let __layout = gcore::record::Layout::default().with_writes(0, gcore::record::element_write::<#slot_value_type>(), &[]);
let __frame_bytes = __layout.frame_bytes();
},
})
.into_iter();
let flip_read_bindings = flip let flip_read_bindings = flip
.then(|| { .then(|| {
lazy_read_fields(&struct_regular_fields).into_iter().map(|(index, field)| { lazy_read_fields(&struct_regular_fields).into_iter().map(|(index, field)| {
@@ -441,15 +429,12 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
.flatten(); .flatten();
let flip_output_inits = flip let flip_output_inits = flip
.then(|| { .then(|| {
let plan = carrier_flip.then(|| quote!(__plan,)); let plan = carrier_flip.then(|| quote!(__plan: ::std::vec::Vec::new(),));
match flip_generic_idents.is_empty() { let marker = (!flip_generic_idents.is_empty()).then(|| quote!(__marker: ::core::marker::PhantomData,));
true => quote!(__layout, __frame_bytes, #plan), quote!(__layout: ::core::default::Default::default(), __frame_bytes: 0, #plan #marker)
false => quote!(__layout, __frame_bytes, #plan __marker: ::core::marker::PhantomData,),
}
}) })
.into_iter(); .into_iter();
// The flip prelude's `element_write` instantiates the erased glue at the // `new` carries the bounds the erased glue needs at the output type.
// output type, so `new` carries the bounds the glue needs.
let new_where = flip let new_where = flip
.then(|| { .then(|| {
let existing = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()); let existing = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter());
@@ -463,7 +448,6 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
{ {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new(#(#new_args,)* #(#routing_layout_param)* #(#routing_in_params)* #(#flip_layout_params)*) -> Self { pub fn new(#(#new_args,)* #(#routing_layout_param)* #(#routing_in_params)* #(#flip_layout_params)*) -> Self {
#(#flip_prelude)*
#(#flip_read_bindings)* #(#flip_read_bindings)*
Self { Self {
#(#all_field_inits,)* #(#all_field_inits,)*
@@ -747,8 +731,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
crate::codegen::ir::Element::Concrete(ty) => Some(ty), crate::codegen::ir::Element::Concrete(ty) => Some(ty),
_ => None, _ => None,
}); });
// A creator pushes rank levels: its `IList` return raises the output depth
// above its carrier's, so the layout writes one level deeper.
let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth); let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth);
let level_delta = node.output.shape.depth as i8 - subject_depth as i8; let level_delta = node.output.shape.depth as i8 - subject_depth as i8;
let pushed_levels = level_delta.max(0) as u8; let pushed_levels = level_delta.max(0) as u8;
@@ -1709,14 +1691,60 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
false => Vec::new(), false => Vec::new(),
}; };
let set_layout_body = if flip {
let plan = carrier_flip.then(|| quote!(self.__plan = __resolved.plan;));
Some(quote! {
self.__frame_bytes = __resolved.frame_bytes;
#plan
self.__layout = __resolved.layout;
})
} else if record_io {
let write_installs = write_markers.iter().enumerate().map(|(index, marker)| {
let slot = format_ident!("__write_{index}");
quote! {
self.#slot = __resolved.layout.offset_of(<#marker as #core_types::attribute::Attribute>::NAME, 0).expect("a written attribute is always part of the wired layout");
}
});
let plan = (!skips_carrier).then(|| quote!(self.__plan = __resolved.plan;));
Some(quote! {
#(#write_installs)*
self.__frame_bytes = __resolved.frame_bytes;
#plan
self.__layout = __resolved.layout;
})
} else {
None
};
let set_layout_method = set_layout_body.map(|body| {
quote! {
fn set_layout(&mut self, __resolved: #core_types::record::RecordLayout) {
#body
}
}
});
let record_layout_impl = match record_io || routing_generic.is_some() || flip || opaque { let record_layout_impl = match record_io || routing_generic.is_some() || flip || opaque {
true => quote! { true => quote! {
fn layout(&self) -> &#core_types::record::Layout { fn layout(&self) -> &#core_types::record::Layout {
&self.__layout &self.__layout
} }
#set_layout_method
}, },
false => quote!(), false => quote!(),
}; };
let flip_meta_concrete = flip && !element_write.is_some_and(|ty| crate::codegen::classify::contains_open_generic(parsed, ty));
let flip_layout_meta_fn = flip_meta_concrete.then(|| {
let layout_meta_fn = format_ident!("{}_layout_meta", fn_name);
let element_spec = match 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 = crate::codegen::ir::layout_meta_tokens(&node, element_spec, core_types);
quote! {
#vis fn #layout_meta_fn() -> #core_types::record::LayoutMeta {
#layout_meta
}
}
});
let entries = entries_tokens(parsed, &struct_name, &data_field_generic_idents, &regular_fields); let entries = entries_tokens(parsed, &struct_name, &data_field_generic_idents, &regular_fields);
let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes); let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes);
@@ -1769,13 +1797,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let slot = format_ident!("__in_{index}"); let slot = format_ident!("__in_{index}");
quote!(#slot: &#core_types::record::Layout,) quote!(#slot: &#core_types::record::Layout,)
}); });
let layout_binding = match skips_carrier {
true => quote!(let __layout = self::#layout_fn();),
false => quote!(let __layout = self::#layout_fn(__carrier_layout);),
};
let carry_element = element_write.is_none();
let plan_binding =
(!skips_carrier).then(|| quote!(let __plan = #core_types::record::copy_plan(__carrier_layout, &__layout, #carry_element, &[#(#remove_pairs),*]);));
let read_inits = flat_reads.iter().enumerate().map(|(slot, (owner, read))| { let read_inits = flat_reads.iter().enumerate().map(|(slot, (owner, read))| {
let marker = &read.marker; let marker = &read.marker;
let slot = format_ident!("__read_{slot}"); let slot = format_ident!("__read_{slot}");
@@ -1785,14 +1806,6 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
}; };
quote!(let #slot = #source.offset_of(<#marker as #core_types::attribute::Attribute>::NAME, 0);) quote!(let #slot = #source.offset_of(<#marker as #core_types::attribute::Attribute>::NAME, 0);)
}); });
let write_inits = write_markers.iter().enumerate().map(|(index, marker)| {
let slot = format_ident!("__write_{index}");
quote! {
let #slot = __layout
.offset_of(<#marker as #core_types::attribute::Attribute>::NAME, 0)
.expect("a written attribute is always part of the wired layout");
}
});
let data_inits = data_names.iter().map(|name| quote!(#name: ::core::default::Default::default(),)); let data_inits = data_names.iter().map(|name| quote!(#name: ::core::default::Default::default(),));
let edge_inits = regular_fields.iter().map(|field| { let edge_inits = regular_fields.iter().map(|field| {
let name = &field.pat_ident.ident; let name = &field.pat_ident.ident;
@@ -1803,9 +1816,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
let slot = format_ident!("__in_{index}"); let slot = format_ident!("__in_{index}");
quote!(#slot: #slot.clone(),) quote!(#slot: #slot.clone(),)
}); });
let plan_init = (!skips_carrier).then(|| quote!(__plan,)).into_iter(); let plan_default = (!skips_carrier).then(|| quote!(__plan: ::std::vec::Vec::new(),)).into_iter();
let read_names = (0..flat_reads.len()).map(|index| format_ident!("__read_{index}")).map(|slot| quote!(#slot,)); let read_names = (0..flat_reads.len()).map(|index| format_ident!("__read_{index}")).map(|slot| quote!(#slot,));
let write_names = (0..write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot,)); let write_defaults = (0..write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot: 0,));
quote! { quote! {
#layout_def #layout_def
#layout_meta_def #layout_meta_def
@@ -1814,21 +1827,17 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
impl<#(#data_field_generic_idents,)* #(#node_generics,)*> #mod_name::#struct_name<#(#struct_type_params,)*> { impl<#(#data_field_generic_idents,)* #(#node_generics,)*> #mod_name::#struct_name<#(#struct_type_params,)*> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#vis fn new(#(#edge_args,)* #(#carrier_layout_param)* #(#input_layout_params)*) -> Self { #vis fn new(#(#edge_args,)* #(#carrier_layout_param)* #(#input_layout_params)*) -> Self {
#layout_binding
#plan_binding
#(#read_inits)* #(#read_inits)*
#(#write_inits)*
let __frame_bytes = __layout.frame_bytes();
Self { Self {
#(#data_inits)* #(#data_inits)*
#(#edge_inits)* #(#edge_inits)*
#(#carrier_init)* #(#carrier_init)*
#(#input_layout_inits)* #(#input_layout_inits)*
__layout, __layout: ::core::default::Default::default(),
#(#plan_init)* #(#plan_default)*
__frame_bytes, __frame_bytes: 0,
#(#read_names)* #(#read_names)*
#(#write_names)* #(#write_defaults)*
} }
} }
} }
@@ -1968,7 +1977,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
Ok(NodePlan { Ok(NodePlan {
kernel, kernel,
lazy_read_fns: quote!(#(#lazy_read_fns)*), lazy_read_fns: quote!(#(#lazy_read_fns)*),
record_ctor: quote!(#record_wiring), record_ctor: quote!(#record_wiring #flip_layout_meta_fn),
node_impl: top_level, node_impl: top_level,
entries, entries,
..Default::default() ..Default::default()
+3 -9
View File
@@ -132,9 +132,7 @@ fn flip_entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, regular_field
let layout = format_ident!("__layout_{index}"); let layout = format_ident!("__layout_{index}");
quote! { quote! {
let #handle = inputs.next().unwrap(); let #handle = inputs.next().unwrap();
let Some(#layout) = #handle.layout().cloned() else { let #layout = #handle.layout().clone();
return Err(gcore::registry::ConstructionError::MissingLayout);
};
let #name = #handle.downcast_record::<#ty>()?; let #name = #handle.downcast_record::<#ty>()?;
} }
}); });
@@ -261,16 +259,12 @@ fn single_row_entries(parsed: &ParsedNodeFn, struct_name: &Ident, regular_fields
SlotKind::BaseGeneric(_) | SlotKind::BaseConcrete(_) => 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 #layout = #handle.layout().clone();
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! { SlotKind::Value(value_ty) => quote! {
let #handle = inputs.next().unwrap(); let #handle = inputs.next().unwrap();
let Some(#layout) = #handle.layout().cloned() else { let #layout = #handle.layout().clone();
return Err(gcore::registry::ConstructionError::MissingLayout);
};
let #name = #handle.downcast_record::<#value_ty>()?; let #name = #handle.downcast_record::<#value_ty>()?;
}, },
SlotKind::Plain(value_ty) | SlotKind::Lazy(value_ty) => quote!(let #name = inputs.next().unwrap().downcast::<#value_ty>()?;), SlotKind::Plain(value_ty) | SlotKind::Lazy(value_ty) => quote!(let #name = inputs.next().unwrap().downcast::<#value_ty>()?;),
+173 -77
View File
@@ -232,6 +232,21 @@ mod tests {
stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12)); stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12));
} }
fn install<N: Node<ContextImpl<'static>>>(mut node: N, meta: core_types::record::LayoutMeta, inputs: &[Option<&Layout>]) -> N {
<N as Node<ContextImpl<'static>>>::set_layout(&mut node, meta.resolve(inputs));
node
}
fn install_flip<N: Node<ContextImpl<'static>>>(mut node: N, layout: &Layout) -> N {
let bundle = core_types::record::RecordLayout {
frame_bytes: layout.frame_bytes(),
plan: Vec::new(),
layout: layout.clone(),
};
<N as Node<ContextImpl<'static>>>::set_layout(&mut node, bundle);
node
}
fn lifted_value<T: Clone + Send + Sync + 'static>(value: T) -> (core_types::record::RecordLift<T, ValueNode<T>>, Layout) { fn lifted_value<T: Clone + Send + Sync + 'static>(value: T) -> (core_types::record::RecordLift<T, ValueNode<T>>, Layout) {
let lift = core_types::record::RecordLift::<T, _>::new(ValueNode(value)); let lift = core_types::record::RecordLift::<T, _>::new(ValueNode(value));
let layout = Node::<ContextImpl>::layout(&lift).clone(); let layout = Node::<ContextImpl>::layout(&lift).clone();
@@ -268,7 +283,7 @@ mod tests {
let leveled = repeat_opacity_layout(&base); let leveled = repeat_opacity_layout(&base);
reserve_for(&[&base, &leveled]); reserve_for(&[&base, &leveled]);
let node = RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base); let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
assert_eq!(node.layout(), &leveled); assert_eq!(node.layout(), &leveled);
let GPoll::Final(value) = node.eval(&indexed) else { let GPoll::Final(value) = node.eval(&indexed) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -288,7 +303,7 @@ mod tests {
let base = f64_layout(&[]); let base = f64_layout(&[]);
reserve_for(&[&base]); reserve_for(&[&base]);
let node = RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base); let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
// The pushed level (0, the only level) reports the copy count. // The pushed level (0, the only level) reports the copy count.
assert_eq!(node.extent_at(&ctx, 0), core_types::gpoll::GPoll::Final(core_types::gpoll::Extent::Exactly(3))); assert_eq!(node.extent_at(&ctx, 0), core_types::gpoll::GPoll::Final(core_types::gpoll::Extent::Exactly(3)));
} }
@@ -305,8 +320,8 @@ mod tests {
let out = f64_layout(&[]); let out = f64_layout(&[]);
reserve_for(&[&base, &leveled, &out]); reserve_for(&[&base, &leveled, &out]);
let repeat = RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base); let repeat = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
let node = SumNode::new(repeat, &leveled); let node = install_flip(SumNode::new(repeat, &leveled), &out);
assert_eq!(node.layout().depth, 0, "the reducer collapsed the rank level"); assert_eq!(node.layout().depth, 0, "the reducer collapsed the rank level");
let GPoll::Final(value) = node.eval(&ctx) else { let GPoll::Final(value) = node.eval(&ctx) else {
@@ -342,7 +357,15 @@ mod tests {
let stacked = multiply_opacity_layout(&modified); let stacked = multiply_opacity_layout(&modified);
reserve_for(&[&source_layout, &modified, &stacked]); 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); let chain = install(
MultiplyOpacityNode::new(
install(MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), multiply_opacity_layout_meta(), &[Some(&source_layout)]),
ValueNode(0.5),
&modified,
),
multiply_opacity_layout_meta(),
&[Some(&modified)],
);
assert_eq!(chain.layout(), &stacked); assert_eq!(chain.layout(), &stacked);
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -363,7 +386,7 @@ mod tests {
let measured = measure_layout(&source_layout); let measured = measure_layout(&source_layout);
reserve_for(&[&source_layout, &measured]); reserve_for(&[&source_layout, &measured]);
let chain = MeasureNode::new(bare_source(&source_layout, -2.), &source_layout); let chain = install(MeasureNode::new(bare_source(&source_layout, -2.), &source_layout), measure_layout_meta(), &[Some(&source_layout)]);
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
@@ -384,7 +407,14 @@ mod tests {
let measured = measure_layout(&modified); let measured = measure_layout(&modified);
reserve_for(&[&source_layout, &modified, &measured]); reserve_for(&[&source_layout, &modified, &measured]);
let chain = MeasureNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, -2.), ValueNode(0.5), &source_layout), &modified); let chain = install(
MeasureNode::new(
install(MultiplyOpacityNode::new(bare_source(&source_layout, -2.), ValueNode(0.5), &source_layout), multiply_opacity_layout_meta(), &[Some(&source_layout)]),
&modified,
),
measure_layout_meta(),
&[Some(&modified)],
);
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
@@ -405,13 +435,20 @@ mod tests {
let shaded = shade_layout(&modified); let shaded = shade_layout(&modified);
reserve_for(&[&source_layout, &modified, &shaded]); reserve_for(&[&source_layout, &modified, &shaded]);
let bare = ShadeNode::new(bare_source(&source_layout, 4.), &source_layout); let bare = install(ShadeNode::new(bare_source(&source_layout, 4.), &source_layout), shade_layout_meta(), &[Some(&source_layout)]);
let GPoll::Final(value) = bare.eval(&ctx) else { let GPoll::Final(value) = bare.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
assert_eq!(unsafe { source_layout.rec(&value).element::<f64>() }, 4.); assert_eq!(unsafe { source_layout.rec(&value).element::<f64>() }, 4.);
let chain = ShadeNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), &modified); let chain = install(
ShadeNode::new(
install(MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), multiply_opacity_layout_meta(), &[Some(&source_layout)]),
&modified,
),
shade_layout_meta(),
&[Some(&modified)],
);
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
@@ -433,7 +470,7 @@ mod tests {
let u32_faded = fade_layout(&u32_source); let u32_faded = fade_layout(&u32_source);
reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]); reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]);
let wide = FadeNode::new(bare_source(&f64_source, 8.), ValueNode(0.5), &f64_source); let wide = install(FadeNode::new(bare_source(&f64_source, 8.), ValueNode(0.5), &f64_source), fade_layout_meta(), &[Some(&f64_source)]);
let GPoll::Final(value) = wide.eval(&ctx) else { let GPoll::Final(value) = wide.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
@@ -441,15 +478,19 @@ mod tests {
assert_eq!(unsafe { rec.element::<f64>() }, 8.); assert_eq!(unsafe { rec.element::<f64>() }, 8.);
assert_eq!(unsafe { rec.read::<f64>(f64_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); assert_eq!(unsafe { rec.read::<f64>(f64_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
let narrow = FadeNode::new( let narrow = install(
RecordSourceNode { FadeNode::new(
layout: u32_source.clone(), RecordSourceNode {
element: 7u32, layout: u32_source.clone(),
fields: vec![], element: 7u32,
partial: false, fields: vec![],
}, partial: false,
ValueNode(0.25), },
&u32_source, ValueNode(0.25),
&u32_source,
),
fade_layout_meta(),
&[Some(&u32_source)],
); );
let GPoll::Final(value) = narrow.eval(&ctx) else { let GPoll::Final(value) = narrow.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -469,7 +510,7 @@ mod tests {
let layout = source_opacity_layout(); let layout = source_opacity_layout();
reserve_for(&[&layout]); reserve_for(&[&layout]);
let node = SourceOpacityNode::new(ValueNode(3.), ValueNode(0.25)); let node = install(SourceOpacityNode::new(ValueNode(3.), ValueNode(0.25)), source_opacity_layout_meta(), &[]);
assert_eq!(Node::<ContextImpl>::layout(&node), &layout); assert_eq!(Node::<ContextImpl>::layout(&node), &layout);
let GPoll::Final(value) = node.eval(&ctx) else { let GPoll::Final(value) = node.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -490,15 +531,19 @@ mod tests {
let modified = multiply_opacity_layout(&source_layout); let modified = multiply_opacity_layout(&source_layout);
reserve_for(&[&source_layout, &modified]); reserve_for(&[&source_layout, &modified]);
let chain = MultiplyOpacityNode::new( let chain = install(
RecordSourceNode { MultiplyOpacityNode::new(
layout: source_layout.clone(), RecordSourceNode {
element: 1., layout: source_layout.clone(),
fields: vec![], element: 1.,
partial: true, fields: vec![],
}, partial: true,
ValueNode(0.5), },
&source_layout, ValueNode(0.5),
&source_layout,
),
multiply_opacity_layout_meta(),
&[Some(&source_layout)],
); );
let GPoll::Partial(value) = chain.eval(&ctx) else { let GPoll::Partial(value) = chain.eval(&ctx) else {
panic!("expected a partial record"); panic!("expected a partial record");
@@ -517,13 +562,13 @@ mod tests {
let modified = checked_multiply_opacity_layout(&source_layout); let modified = checked_multiply_opacity_layout(&source_layout);
reserve_for(&[&source_layout, &modified]); reserve_for(&[&source_layout, &modified]);
let ok = CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(0.5), &source_layout); let ok = install(CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(0.5), &source_layout), checked_multiply_opacity_layout_meta(), &[Some(&source_layout)]);
let GPoll::Final(value) = ok.eval(&ctx) else { let GPoll::Final(value) = ok.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
assert_eq!(unsafe { modified.rec(&value).read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); assert_eq!(unsafe { modified.rec(&value).read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
let failing = CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout); let failing = install(CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout), checked_multiply_opacity_layout_meta(), &[Some(&source_layout)]);
let GPoll::Error(error) = failing.eval(&ctx) else { let GPoll::Error(error) = failing.eval(&ctx) else {
panic!("expected an error"); panic!("expected an error");
}; };
@@ -542,7 +587,15 @@ mod tests {
let scaled = scale_layout(&modified); let scaled = scale_layout(&modified);
reserve_for(&[&source_layout, &modified, &scaled]); reserve_for(&[&source_layout, &modified, &scaled]);
let chain = ScaleNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), ValueNode(3.), &modified); let chain = install(
ScaleNode::new(
install(MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout), multiply_opacity_layout_meta(), &[Some(&source_layout)]),
ValueNode(3.),
&modified,
),
scale_layout_meta(),
&[Some(&modified)],
);
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
@@ -563,11 +616,15 @@ mod tests {
let transferred = transfer_opacity_layout(&carrier_layout); let transferred = transfer_opacity_layout(&carrier_layout);
reserve_for(&[&carrier_layout, &secondary_layout, &transferred]); reserve_for(&[&carrier_layout, &secondary_layout, &transferred]);
let chain = TransferOpacityNode::new( let chain = install(
f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.5)]), TransferOpacityNode::new(
f64_record_source(&secondary_layout, 3., vec![(secondary_layout.offset_of("opacity", 0).unwrap(), 0.25)]), f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.5)]),
&carrier_layout, f64_record_source(&secondary_layout, 3., vec![(secondary_layout.offset_of("opacity", 0).unwrap(), 0.25)]),
&secondary_layout, &carrier_layout,
&secondary_layout,
),
transfer_opacity_layout_meta(),
&[Some(&carrier_layout), None],
); );
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -577,11 +634,15 @@ mod tests {
assert_eq!(unsafe { rec.read::<f64>(transferred.offset_of(Opacity::NAME, 0).unwrap()) }, 0.125); assert_eq!(unsafe { rec.read::<f64>(transferred.offset_of(Opacity::NAME, 0).unwrap()) }, 0.125);
let bare_secondary = f64_layout(&[]); let bare_secondary = f64_layout(&[]);
let defaulted = TransferOpacityNode::new( let defaulted = install(
f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.5)]), TransferOpacityNode::new(
bare_source(&bare_secondary, 3.), f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.5)]),
&carrier_layout, bare_source(&bare_secondary, 3.),
&bare_secondary, &carrier_layout,
&bare_secondary,
),
transfer_opacity_layout_meta(),
&[Some(&carrier_layout), None],
); );
let GPoll::Final(value) = defaulted.eval(&ctx) else { let GPoll::Final(value) = defaulted.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -602,11 +663,15 @@ mod tests {
let factor_layout = Node::<ContextImpl>::layout(&factor).clone(); let factor_layout = Node::<ContextImpl>::layout(&factor).clone();
reserve_for(&[&source_layout]); reserve_for(&[&source_layout]);
let node = BoostNode::new( let node = install(
f64_record_source(&source_layout, 2., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]), BoostNode::new(
factor, f64_record_source(&source_layout, 2., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]),
&source_layout, factor,
&factor_layout, &source_layout,
&factor_layout,
),
boost_layout_meta(),
&[Some(&source_layout)],
); );
let out_layout = Node::<ContextImpl>::layout(&node).clone(); let out_layout = Node::<ContextImpl>::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 opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through to the output");
@@ -629,11 +694,15 @@ mod tests {
let (factor, factor_layout) = lifted_value(3.); let (factor, factor_layout) = lifted_value(3.);
reserve_for(&[&source_layout]); reserve_for(&[&source_layout]);
let node = BoostPollNode::new( let node = install(
f64_record_source(&source_layout, 2., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]), BoostPollNode::new(
factor, f64_record_source(&source_layout, 2., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]),
&source_layout, factor,
&factor_layout, &source_layout,
&factor_layout,
),
boost_poll_layout_meta(),
&[Some(&source_layout)],
); );
let out_layout = Node::<ContextImpl>::layout(&node).clone(); let out_layout = Node::<ContextImpl>::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 opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through the poll kernel");
@@ -657,11 +726,15 @@ mod tests {
assert!(by_layout.frame_bytes() != 0, "the borrow must point into a spilled frame to exercise the park"); assert!(by_layout.frame_bytes() != 0, "the borrow must point into a spilled frame to exercise the park");
reserve_for(&[&carrier_layout, &by_layout]); reserve_for(&[&carrier_layout, &by_layout]);
let node = OffsetNode::new( let node = install(
f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.25)]), OffsetNode::new(
f64_record_source(&by_layout, 40., vec![]), f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.25)]),
&carrier_layout, f64_record_source(&by_layout, 40., vec![]),
&by_layout, &carrier_layout,
&by_layout,
),
offset_layout_meta(),
&[Some(&carrier_layout)],
); );
let out_layout = Node::<ContextImpl>::layout(&node).clone(); let out_layout = Node::<ContextImpl>::layout(&node).clone();
let GPoll::Final(value) = node.eval(&ctx) else { let GPoll::Final(value) = node.eval(&ctx) else {
@@ -694,13 +767,17 @@ mod tests {
let (source_id, source_id_layout) = lifted_value(7 as SourceId); let (source_id, source_id_layout) = lifted_value(7 as SourceId);
reserve_for(&[&source_layout]); reserve_for(&[&source_layout]);
let node = DoubleAsyncNode::new( let node = install(
f64_record_source(&source_layout, 3., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]), DoubleAsyncNode::new(
runtime, f64_record_source(&source_layout, 3., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]),
source_id, runtime,
&source_layout, source_id,
&runtime_layout, &source_layout,
&source_id_layout, &runtime_layout,
&source_id_layout,
),
double_async_layout_meta(),
&[Some(&source_layout)],
); );
let out_layout = Node::<ContextImpl>::layout(&node).clone(); let out_layout = Node::<ContextImpl>::layout(&node).clone();
let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the carrier's fields pass through the async source"); let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the carrier's fields pass through the async source");
@@ -774,9 +851,20 @@ mod tests {
let shaded = shade_layout(&stripped); let shaded = shade_layout(&stripped);
reserve_for(&[&source_layout, &modified, &stripped, &shaded]); reserve_for(&[&source_layout, &modified, &stripped, &shaded]);
let chain = ShadeNode::new( let chain = install(
StripOpacityNode::new(MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), &modified), ShadeNode::new(
&stripped, install(
StripOpacityNode::new(
install(MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), multiply_opacity_layout_meta(), &[Some(&source_layout)]),
&modified,
),
strip_opacity_layout_meta(),
&[Some(&modified)],
),
&stripped,
),
shade_layout_meta(),
&[Some(&stripped)],
); );
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -796,13 +884,17 @@ mod tests {
assert!(relengthed.offset_of(Opacity::NAME, 0).is_none()); assert!(relengthed.offset_of(Opacity::NAME, 0).is_none());
reserve_for(&[&source_layout, &relengthed]); reserve_for(&[&source_layout, &relengthed]);
let chain = RelengthNode::new( let chain = install(
f64_record_source( RelengthNode::new(
f64_record_source(
&source_layout,
3.,
vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25), (source_layout.offset_of("length", 0).unwrap(), 9.)],
),
&source_layout, &source_layout,
3.,
vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25), (source_layout.offset_of("length", 0).unwrap(), 9.)],
), ),
&source_layout, relength_layout_meta(),
&[Some(&source_layout)],
); );
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -824,10 +916,14 @@ mod tests {
let relabeled = label_layout(&labeled); let relabeled = label_layout(&labeled);
reserve_for(&[&source_layout, &labeled, &relabeled]); reserve_for(&[&source_layout, &labeled, &relabeled]);
let chain = LabelNode::new( let chain = install(
LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout), LabelNode::new(
ValueNode(String::from("b")), install(LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout), label_layout_meta(), &[Some(&source_layout)]),
&labeled, ValueNode(String::from("b")),
&labeled,
),
label_layout_meta(),
&[Some(&labeled)],
); );
let GPoll::Final(value) = chain.eval(&ctx) else { let GPoll::Final(value) = chain.eval(&ctx) else {
panic!("expected a final record"); panic!("expected a final record");
@@ -1187,7 +1283,7 @@ mod tests {
let labeled = label_layout(&source_layout); let labeled = label_layout(&source_layout);
reserve_for(&[&labeled, &labeled]); reserve_for(&[&labeled, &labeled]);
let chain = LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout); let chain = install(LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout), label_layout_meta(), &[Some(&source_layout)]);
let memo = crate::memo::MemoizeNode::new(chain, &labeled); let memo = crate::memo::MemoizeNode::new(chain, &labeled);
let first_arena = Arena::new(1024).unwrap(); let first_arena = Arena::new(1024).unwrap();