mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 01:38:12 +08:00
Materialize a reducer's ranked input into a List and fold it
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use crate::context::InjectIndex;
|
use crate::context::InjectIndex;
|
||||||
use crate::gpoll::{Extent, Finality, GPoll, GraphError, Interrupt, Level};
|
use crate::gpoll::{Extent, Finality, GPoll, GraphError, Interrupt, Level};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
@@ -172,6 +173,66 @@ impl<'e> RecordLane<'_, crate::record::RecordValue<'e>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A materialized nesting level handed to a folding kernel: a thin element-typed
|
||||||
|
/// view over the [`RecordBatch`] the level was collected into. `'a` is the batch
|
||||||
|
/// view, `'e` the record payloads. The eventual `List` once `IList` is renamed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct List<'a, 'e, T> {
|
||||||
|
batch: RecordBatch<'a, crate::record::RecordValue<'e>>,
|
||||||
|
_element: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'e, T: Copy> List<'a, 'e, T> {
|
||||||
|
/// # Safety
|
||||||
|
/// `T` must be the batch's record element type, proven at the consumer's wiring.
|
||||||
|
pub unsafe fn new(batch: RecordBatch<'a, crate::record::RecordValue<'e>>) -> Self {
|
||||||
|
Self { batch, _element: PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.batch.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.batch.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> T {
|
||||||
|
// SAFETY: `List::new` established that `T` is the batch's element type.
|
||||||
|
self.batch.get(index, |lane| unsafe { lane.element::<T>() })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
|
||||||
|
(0..self.len()).map(move |index| self.get(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'e, T: Copy> IntoIterator for List<'a, 'e, T> {
|
||||||
|
type Item = T;
|
||||||
|
type IntoIter = ListIter<'a, 'e, T>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> ListIter<'a, 'e, T> {
|
||||||
|
ListIter { list: self, position: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListIter<'a, 'e, T> {
|
||||||
|
list: List<'a, 'e, T>,
|
||||||
|
position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy> Iterator for ListIter<'_, '_, T> {
|
||||||
|
type Item = T;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<T> {
|
||||||
|
(self.position < self.list.len()).then(|| {
|
||||||
|
let value = self.list.get(self.position);
|
||||||
|
self.position += 1;
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Node<Input> {
|
pub trait Node<Input> {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
|
|||||||
@@ -353,6 +353,13 @@ impl std::fmt::Debug for RecordValue<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: `element_write` requires the element `Send + Sync` and attribute payloads
|
||||||
|
// are `Copy` or arena-backed, so the record bytes behind the pointer are thread-safe;
|
||||||
|
// `'e` ties the pointer's validity to the shared arena and record-stack discipline.
|
||||||
|
unsafe impl Send for RecordValue<'_> {}
|
||||||
|
// SAFETY: as `Send`.
|
||||||
|
unsafe impl Sync for RecordValue<'_> {}
|
||||||
|
|
||||||
impl<'e> RecordValue<'e> {
|
impl<'e> RecordValue<'e> {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn zeroed() -> Self {
|
pub fn zeroed() -> Self {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let model = analyze(parsed);
|
let model = analyze(parsed);
|
||||||
let node = crate::codegen::ir::build(parsed);
|
let node = crate::codegen::ir::build(parsed);
|
||||||
let kind = model.as_ref().map(|_| crate::codegen::ir::node_kind(&node));
|
let kind = model.as_ref().map(|_| crate::codegen::ir::node_kind(&node));
|
||||||
let carrier_present = node.inputs.first().is_some_and(|input| input.subject);
|
let carrier_present = matches!(node.inputs.first(), Some(input) if input.subject && crate::codegen::ir::materialized_levels(&node, 0) == 0);
|
||||||
let record_io = matches!(kind, Some(crate::codegen::ir::NodeKind::RecordIo));
|
let record_io = matches!(kind, Some(crate::codegen::ir::NodeKind::RecordIo));
|
||||||
let flip = matches!(kind, Some(crate::codegen::ir::NodeKind::Flip));
|
let flip = matches!(kind, Some(crate::codegen::ir::NodeKind::Flip));
|
||||||
let carrier_flip = flip && carrier_present;
|
let carrier_flip = flip && carrier_present;
|
||||||
@@ -722,7 +722,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let async_source = async_fn || future_kernel;
|
let async_source = async_fn || future_kernel;
|
||||||
let node = crate::codegen::ir::build(parsed);
|
let node = crate::codegen::ir::build(parsed);
|
||||||
let kind = crate::codegen::ir::node_kind(&node);
|
let kind = crate::codegen::ir::node_kind(&node);
|
||||||
let carrier_present = node.inputs.first().is_some_and(|input| input.subject);
|
let carrier_present = matches!(node.inputs.first(), Some(input) if input.subject && crate::codegen::ir::materialized_levels(&node, 0) == 0);
|
||||||
let flip = matches!(kind, crate::codegen::ir::NodeKind::Flip);
|
let flip = matches!(kind, crate::codegen::ir::NodeKind::Flip);
|
||||||
let carrier_flip = flip && carrier_present;
|
let carrier_flip = flip && carrier_present;
|
||||||
let opaque = matches!(kind, crate::codegen::ir::NodeKind::Opaque);
|
let opaque = matches!(kind, crate::codegen::ir::NodeKind::Opaque);
|
||||||
@@ -951,6 +951,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
.map(|(index, field)| {
|
.map(|(index, field)| {
|
||||||
let pat = &field.pat_ident;
|
let pat = &field.pat_ident;
|
||||||
match &field.ty {
|
match &field.ty {
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if ir::materialized_levels(&node, index) > 0 => {
|
||||||
|
quote!(#pat: #core_types::node::List<'_, '_, #ty>)
|
||||||
|
}
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => quote!(#pat: &#ty),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if !field.attribute_reads.is_empty() => read_tuple_param(field, quote!(#pat), quote!(#ty)),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if !field.attribute_reads.is_empty() => read_tuple_param(field, quote!(#pat), quote!(#ty)),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty),
|
||||||
@@ -1088,6 +1091,28 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// A carrier primary evaluates beyond the node's own frame (in the
|
// A carrier primary evaluates beyond the node's own frame (in the
|
||||||
// record/flip tail), so it does not bind here.
|
// record/flip tail), so it does not bind here.
|
||||||
ValueBinding::Carrier => quote!(),
|
ValueBinding::Carrier => quote!(),
|
||||||
|
ValueBinding::Materialized => {
|
||||||
|
let levels = ir::materialized_levels(&node, index);
|
||||||
|
quote! {
|
||||||
|
let __arena = #core_types::context::ExtractArena::arena(__input);
|
||||||
|
let __count = match #core_types::node::Node::extent(&self.#name, __input, #core_types::gpoll::Level::Below(#levels)) {
|
||||||
|
#core_types::gpoll::GPoll::Final(#core_types::gpoll::Extent::Exactly(__count)) => __count,
|
||||||
|
#core_types::gpoll::GPoll::Pending => return #core_types::gpoll::GPoll::Pending,
|
||||||
|
_ => return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#core_types::gpoll::GraphError::new("reduce over a non-exact extent"))),
|
||||||
|
};
|
||||||
|
let __scratch = match __arena.alloc_scratch::<#core_types::record::RecordValue<'__record>>(__count) {
|
||||||
|
Some(__scratch) => __scratch,
|
||||||
|
None => return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#core_types::gpoll::GraphError::new("reduce scratch allocation failed"))),
|
||||||
|
};
|
||||||
|
let __batch = match #core_types::node::Node::eval_batch(&self.#name, __input, 0..__count as u64, Some(__scratch)) {
|
||||||
|
#core_types::node::BatchStatus::Lent(__batch, _) | #core_types::node::BatchStatus::Filled(__batch, _) => __batch,
|
||||||
|
#core_types::node::BatchStatus::Pending => return #core_types::gpoll::GPoll::Pending,
|
||||||
|
#core_types::node::BatchStatus::Error(__error) => return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(__error)),
|
||||||
|
_ => return #core_types::gpoll::GPoll::Error(::std::boxed::Box::new(#core_types::gpoll::GraphError::new("reduce batch failed"))),
|
||||||
|
};
|
||||||
|
let #name = unsafe { #core_types::node::List::<#ty>::new(__batch) };
|
||||||
|
}
|
||||||
|
}
|
||||||
// A reading secondary input claims a record edge: the element and
|
// A reading secondary input claims a record edge: the element and
|
||||||
// the declared reads copy out right after its eval, before any
|
// the declared reads copy out right after its eval, before any
|
||||||
// later sibling eval can reuse the record stack.
|
// later sibling eval can reuse the record stack.
|
||||||
@@ -1668,7 +1693,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
true => {
|
true => {
|
||||||
let mut bounds: Vec<TokenStream2> = regular_fields
|
let mut bounds: Vec<TokenStream2> = regular_fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|field| match &field.ty {
|
.enumerate()
|
||||||
|
.filter(|(index, _)| ir::materialized_levels(&node, *index) == 0)
|
||||||
|
.filter_map(|(_, field)| match &field.ty {
|
||||||
// The conditional arena-park moves a lend element once.
|
// The conditional arena-park moves a lend element once.
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => Some(quote!(#ty: ::core::marker::Send + ::core::marker::Sync + 'static)),
|
ParsedFieldType::Regular(RegularParsedField { ty, lend: Some(_), .. }) => Some(quote!(#ty: ::core::marker::Send + ::core::marker::Sync + 'static)),
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)),
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: ::core::clone::Clone)),
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ pub(crate) fn analyze(parsed: &ParsedNodeFn) -> Option<Dialect> {
|
|||||||
} else if has_record_io(parsed) {
|
} else if has_record_io(parsed) {
|
||||||
return None;
|
return None;
|
||||||
} else {
|
} else {
|
||||||
routing_io(parsed).is_some() || record_flip(parsed) || record_opaque(parsed)
|
routing_io(parsed).is_some() || record_flip(parsed) || record_opaque(parsed) || has_materialized_input(parsed)
|
||||||
};
|
};
|
||||||
supported.then(|| dialect(parsed))
|
supported.then(|| dialect(parsed))
|
||||||
}
|
}
|
||||||
@@ -367,6 +367,13 @@ pub(crate) fn flip_carrier(parsed: &ParsedNodeFn) -> bool {
|
|||||||
/// fully-concrete value-input nodes in this cut; batch, shader, async, lend,
|
/// fully-concrete value-input nodes in this cut; batch, shader, async, lend,
|
||||||
/// lazy, and generic nodes keep the plain lowering until their record forms
|
/// lazy, and generic nodes keep the plain lowering until their record forms
|
||||||
/// land.
|
/// land.
|
||||||
|
pub(crate) fn has_materialized_input(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
parsed
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.any(|field| matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { list_levels, .. }) if *list_levels > 0))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
|
pub(crate) fn record_flip(parsed: &ParsedNodeFn) -> bool {
|
||||||
if record_shape(parsed).is_some() || has_record_io(parsed) || routing_io(parsed).is_some() {
|
if record_shape(parsed).is_some() || has_record_io(parsed) || routing_io(parsed).is_some() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -59,10 +59,14 @@ fn inputs(parsed: &ParsedNodeFn, fields: &[&ParsedField], generics: &[Ident]) ->
|
|||||||
ParsedFieldType::Node(_) => Evaluation::Lazy,
|
ParsedFieldType::Node(_) => Evaluation::Lazy,
|
||||||
ParsedFieldType::Regular(_) => Evaluation::Eager,
|
ParsedFieldType::Regular(_) => Evaluation::Eager,
|
||||||
};
|
};
|
||||||
|
let (element, depth) = match &field.ty {
|
||||||
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => strip_ilist(output_type),
|
||||||
|
ParsedFieldType::Regular(RegularParsedField { ty, list_levels, .. }) => (ty.clone(), *list_levels),
|
||||||
|
};
|
||||||
Input {
|
Input {
|
||||||
ident: field.pat_ident.ident.clone(),
|
ident: field.pat_ident.ident.clone(),
|
||||||
evaluation,
|
evaluation,
|
||||||
shape: item_shape(field_element_type(field), &field.attribute_reads, generics),
|
shape: item_shape(&element, depth, &field.attribute_reads, generics),
|
||||||
subject: subject(index, field, carrier_subject, routing.as_ref()),
|
subject: subject(index, field, carrier_subject, routing.as_ref()),
|
||||||
lend: matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. })),
|
lend: matches!(&field.ty, ParsedFieldType::Regular(RegularParsedField { lend: Some(_), .. })),
|
||||||
}
|
}
|
||||||
@@ -135,10 +139,9 @@ fn field_element_type(field: &ParsedField) -> &Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_shape(element: &Type, reads: &[AttributeRead], generics: &[Ident]) -> ItemShape {
|
fn item_shape(element: &Type, depth: u8, reads: &[AttributeRead], generics: &[Ident]) -> ItemShape {
|
||||||
let (element, depth) = strip_ilist(element);
|
|
||||||
ItemShape {
|
ItemShape {
|
||||||
element: element_of(&element, generics),
|
element: element_of(element, generics),
|
||||||
depth,
|
depth,
|
||||||
attrs: reads.iter().map(|read| LevelAttr { marker: read.marker.clone(), level: 0 }).collect(),
|
attrs: reads.iter().map(|read| LevelAttr { marker: read.marker.clone(), level: 0 }).collect(),
|
||||||
}
|
}
|
||||||
@@ -226,6 +229,7 @@ fn level_delta(node: &Node) -> i8 {
|
|||||||
/// How an eager value input binds in eval.
|
/// How an eager value input binds in eval.
|
||||||
pub(crate) enum ValueBinding {
|
pub(crate) enum ValueBinding {
|
||||||
Carrier,
|
Carrier,
|
||||||
|
Materialized,
|
||||||
Lend,
|
Lend,
|
||||||
ReadingSecondary,
|
ReadingSecondary,
|
||||||
RecordElement,
|
RecordElement,
|
||||||
@@ -282,10 +286,18 @@ fn has_attr_io(node: &Node) -> bool {
|
|||||||
node.inputs.iter().any(|input| matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()) || !node.output.shape.attrs.is_empty() || !node.output.removes.is_empty()
|
node.inputs.iter().any(|input| matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()) || !node.output.shape.attrs.is_empty() || !node.output.removes.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Levels of `input[index]` the output does not carry; `> 0` folds the input
|
||||||
|
/// into a `List` before the kernel.
|
||||||
|
pub(crate) fn materialized_levels(node: &Node, index: usize) -> u8 {
|
||||||
|
node.inputs[index].shape.depth.saturating_sub(node.output.shape.depth)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn value_binding(node: &Node, index: usize) -> ValueBinding {
|
pub(crate) fn value_binding(node: &Node, index: usize) -> ValueBinding {
|
||||||
let input = &node.inputs[index];
|
let input = &node.inputs[index];
|
||||||
let kind = node_kind(node);
|
let kind = node_kind(node);
|
||||||
if matches!(kind, NodeKind::RecordIo | NodeKind::Flip) && index == 0 && input.subject {
|
if materialized_levels(node, index) > 0 {
|
||||||
|
ValueBinding::Materialized
|
||||||
|
} else if matches!(kind, NodeKind::RecordIo | NodeKind::Flip) && index == 0 && input.subject {
|
||||||
ValueBinding::Carrier
|
ValueBinding::Carrier
|
||||||
} else if matches!(kind, NodeKind::Flip) && input.lend {
|
} else if matches!(kind, NodeKind::Flip) && input.lend {
|
||||||
ValueBinding::Lend
|
ValueBinding::Lend
|
||||||
@@ -620,6 +632,7 @@ mod tests {
|
|||||||
match &field.ty {
|
match &field.ty {
|
||||||
ParsedFieldType::Regular(_) => match value_binding(node, index) {
|
ParsedFieldType::Regular(_) => match value_binding(node, index) {
|
||||||
ValueBinding::Carrier => "carrier",
|
ValueBinding::Carrier => "carrier",
|
||||||
|
ValueBinding::Materialized => "materialized",
|
||||||
ValueBinding::Lend => "lend",
|
ValueBinding::Lend => "lend",
|
||||||
ValueBinding::ReadingSecondary => "reading",
|
ValueBinding::ReadingSecondary => "reading",
|
||||||
ValueBinding::RecordElement => "record",
|
ValueBinding::RecordElement => "record",
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ pub(crate) fn generate_node_input_references(
|
|||||||
|
|
||||||
for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {
|
for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {
|
||||||
let mut ty = match &parsed_input.ty {
|
let mut ty = match &parsed_input.ty {
|
||||||
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty,
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) => ty.clone(),
|
||||||
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type,
|
ParsedFieldType::Node(NodeParsedField { output_type, .. }) => output_type.clone(),
|
||||||
}
|
};
|
||||||
.clone();
|
|
||||||
|
|
||||||
// We only want the necessary generics.
|
// We only want the necessary generics.
|
||||||
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);
|
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);
|
||||||
|
|||||||
@@ -313,6 +313,8 @@ impl Parse for NumberRange {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RegularParsedField {
|
pub struct RegularParsedField {
|
||||||
pub ty: Type,
|
pub ty: Type,
|
||||||
|
/// `IList` nesting stripped from `ty` at parse; `ty` holds the element row.
|
||||||
|
pub list_levels: u8,
|
||||||
/// The original reference tokens when the parameter was written `&T`; `ty` holds the peeled inner type.
|
/// The original reference tokens when the parameter was written `&T`; `ty` holds the peeled inner type.
|
||||||
pub lend: Option<syn::TypeReference>,
|
pub lend: Option<syn::TypeReference>,
|
||||||
pub exposed: bool,
|
pub exposed: bool,
|
||||||
@@ -911,6 +913,7 @@ fn parse_node_implementations<T: Parse>(attr: &Attribute, name: &Ident) -> syn::
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Result<ParsedField> {
|
fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Result<ParsedField> {
|
||||||
|
let (ty, list_levels) = crate::codegen::ir::strip_ilist(&ty);
|
||||||
let ident = &pat_ident.ident;
|
let ident = &pat_ident.ident;
|
||||||
|
|
||||||
// Checks for the #[data] attribute, indicating that this is a data field rather than an input parameter to the node.
|
// Checks for the #[data] attribute, indicating that this is a data field rather than an input parameter to the node.
|
||||||
@@ -1122,6 +1125,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul
|
|||||||
number_hard_max,
|
number_hard_max,
|
||||||
number_mode_range,
|
number_mode_range,
|
||||||
ty,
|
ty,
|
||||||
|
list_levels,
|
||||||
lend,
|
lend,
|
||||||
value_source,
|
value_source,
|
||||||
implementations,
|
implementations,
|
||||||
@@ -1236,6 +1240,7 @@ impl ParsedNodeFn {
|
|||||||
widget_override: ParsedWidgetOverride::Hidden,
|
widget_override: ParsedWidgetOverride::Hidden,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
ty,
|
ty,
|
||||||
|
list_levels: 0,
|
||||||
lend: None,
|
lend: None,
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source,
|
value_source,
|
||||||
@@ -1410,6 +1415,7 @@ mod tests {
|
|||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
|
list_levels: 0,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ParsedValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
@@ -1504,6 +1510,7 @@ mod tests {
|
|||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
|
list_levels: 0,
|
||||||
ty: parse_quote!(DVec2),
|
ty: parse_quote!(DVec2),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ParsedValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
@@ -1579,6 +1586,7 @@ mod tests {
|
|||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
|
list_levels: 0,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ParsedValueSource::Default(quote!(50.)),
|
value_source: ParsedValueSource::Default(quote!(50.)),
|
||||||
@@ -1652,6 +1660,7 @@ mod tests {
|
|||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
|
list_levels: 0,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ParsedValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
@@ -1737,6 +1746,7 @@ mod tests {
|
|||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
|
list_levels: 0,
|
||||||
ty: parse_quote!(f64),
|
ty: parse_quote!(f64),
|
||||||
exposed: false,
|
exposed: false,
|
||||||
value_source: ParsedValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
@@ -1825,6 +1835,7 @@ mod tests {
|
|||||||
widget_override: ParsedWidgetOverride::None,
|
widget_override: ParsedWidgetOverride::None,
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
lend: None,
|
lend: None,
|
||||||
|
list_levels: 0,
|
||||||
ty: parse_quote!(String),
|
ty: parse_quote!(String),
|
||||||
exposed: true,
|
exposed: true,
|
||||||
value_source: ParsedValueSource::None,
|
value_source: ParsedValueSource::None,
|
||||||
|
|||||||
@@ -232,6 +232,7 @@ impl PerPixelAdjustCodegen<'_> {
|
|||||||
widget_override: Default::default(),
|
widget_override: Default::default(),
|
||||||
ty: ParsedFieldType::Regular(RegularParsedField {
|
ty: ParsedFieldType::Regular(RegularParsedField {
|
||||||
ty: parse_quote!(#wgpu_executor::WgpuExecutorHandle),
|
ty: parse_quote!(#wgpu_executor::WgpuExecutorHandle),
|
||||||
|
list_levels: 0,
|
||||||
lend: None,
|
lend: None,
|
||||||
exposed: true,
|
exposed: true,
|
||||||
value_source: ParsedValueSource::Scope(Box::new(parse_quote!("graphene_std::platform_application_io::WgpuExecutorNode"))),
|
value_source: ParsedValueSource::Scope(Box::new(parse_quote!("graphene_std::platform_application_io::WgpuExecutorNode"))),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! wiring is by hand until the compiler pass constructs layouts.
|
//! wiring is by hand until the compiler pass constructs layouts.
|
||||||
|
|
||||||
use core_types::attribute::{Attr, Opacity, RemoveAttr};
|
use core_types::attribute::{Attr, Opacity, RemoveAttr};
|
||||||
use core_types::context::{ExtractArena, ExtractIndex};
|
use core_types::context::{ExtractArena, ExtractIndex, InjectIndex};
|
||||||
use core_types::gpoll::{ErrorKind, GraphError, Interrupt};
|
use core_types::gpoll::{ErrorKind, GraphError, Interrupt};
|
||||||
use core_types::{Context, Ctx};
|
use core_types::{Context, Ctx};
|
||||||
|
|
||||||
@@ -57,6 +57,11 @@ fn repeat_opacity(ctx: impl Ctx + ExtractIndex, element: f64, count: u32) -> ILi
|
|||||||
emit(element, Attr(ctx.innermost_index() as f64))
|
emit(element, Attr(ctx.innermost_index() as f64))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Test"))]
|
||||||
|
fn sum(_: impl Ctx + InjectIndex + Copy, items: IList<f64>) -> f64 {
|
||||||
|
items.into_iter().sum()
|
||||||
|
}
|
||||||
|
|
||||||
/// The pushed level's extent is the copy count; other levels forward to the carrier.
|
/// The pushed level's extent is the copy count; other levels forward to the carrier.
|
||||||
fn repeat_opacity_extent<C, In0, In1>(node: &RepeatOpacityNode<In0, In1>, ctx: &C, level: u8) -> core_types::gpoll::GPoll<core_types::gpoll::Extent>
|
fn repeat_opacity_extent<C, In0, In1>(node: &RepeatOpacityNode<In0, In1>, ctx: &C, level: u8) -> core_types::gpoll::GPoll<core_types::gpoll::Extent>
|
||||||
where
|
where
|
||||||
@@ -288,6 +293,29 @@ mod tests {
|
|||||||
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reducer_folds_a_repeated_level() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let base = f64_layout(&[]);
|
||||||
|
let leveled = repeat_opacity_layout(&base);
|
||||||
|
let out = f64_layout(&[]);
|
||||||
|
reserve_for(&[&base, &leveled, &out]);
|
||||||
|
|
||||||
|
let repeat = RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base);
|
||||||
|
let node = SumNode::new(repeat, &leveled);
|
||||||
|
assert_eq!(node.layout().depth, 0, "the reducer collapsed the rank level");
|
||||||
|
|
||||||
|
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
// sum(repeat(3, 7)) folds three copies of the element back to a scalar.
|
||||||
|
assert_eq!(unsafe { out.rec(&value).element::<f64>() }, 21.);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn layout_meta_folds_to_construction() {
|
fn layout_meta_folds_to_construction() {
|
||||||
let base = f64_layout(&[]);
|
let base = f64_layout(&[]);
|
||||||
|
|||||||
Reference in New Issue
Block a user