mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-03 07:48:12 +08:00
Gather a chosen subject lane through a Lane output and convert pack_strips
This commit is contained in:
@@ -3,7 +3,7 @@ use crate::gpoll::{Extent, Finality, GPoll, GraphError, Interrupt, Level};
|
|||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::Range;
|
use std::ops::{Deref, Range};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BatchStatus<'a> {
|
pub enum BatchStatus<'a> {
|
||||||
@@ -185,6 +185,31 @@ impl<'a> RecordLane<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One lane of a materialized level, element-typed. In a kernel's element
|
||||||
|
/// position the output frame is copied from this lane.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Lane<'a, T> {
|
||||||
|
lane: RecordLane<'a>,
|
||||||
|
_element: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A view regardless of `T`: copying a lane copies no record.
|
||||||
|
impl<T> Clone for Lane<'_, T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Copy for Lane<'_, T> {}
|
||||||
|
|
||||||
|
impl<'a, T> Deref for Lane<'a, T> {
|
||||||
|
type Target = RecordLane<'a>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &RecordLane<'a> {
|
||||||
|
&self.lane
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A materialized nesting level handed to a folding kernel: a thin element-typed
|
/// A materialized nesting level handed to a folding kernel: a thin element-typed
|
||||||
/// view over the [`RecordBatch`] the level was collected into. The eventual
|
/// view over the [`RecordBatch`] the level was collected into. The eventual
|
||||||
/// `List` once `IList` is renamed.
|
/// `List` once `IList` is renamed.
|
||||||
@@ -238,8 +263,11 @@ impl<'a, T> List<'a, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Lane `index`'s record, for attribute reads beside the element.
|
/// Lane `index`'s record, for attribute reads beside the element.
|
||||||
pub fn lane(&self, index: usize) -> RecordLane<'a> {
|
pub fn lane(&self, index: usize) -> Lane<'a, T> {
|
||||||
self.batch.get(index)
|
Lane {
|
||||||
|
lane: self.batch.get(index),
|
||||||
|
_element: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = T> + '_
|
pub fn iter(&self) -> impl Iterator<Item = T> + '_
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// A `_: ()` primary keeps its slot: dropping it would shift every
|
// A `_: ()` primary keeps its slot: dropping it would shift every
|
||||||
// per-index classification against the IR and the document's arity.
|
// per-index classification against the IR and the document's arity.
|
||||||
let record_skips_carrier = record_io && !carrier_present;
|
let record_skips_carrier = record_io && !carrier_present;
|
||||||
|
// A gather carrier copies the returned lane's frame, so it needs the plan
|
||||||
|
// without a carrier layout of its own.
|
||||||
|
let gather_carrier = record_io && node.output.gathers;
|
||||||
let struct_regular_fields: Vec<_> = regular_fields.to_vec();
|
let struct_regular_fields: Vec<_> = regular_fields.to_vec();
|
||||||
let struct_regular_field_names: Vec<_> = struct_regular_fields.iter().map(|f| &f.pat_ident.ident).collect();
|
let struct_regular_field_names: Vec<_> = struct_regular_fields.iter().map(|f| &f.pat_ident.ident).collect();
|
||||||
|
|
||||||
@@ -193,6 +196,8 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout)];
|
let mut state = vec![quote!(pub(super) __layout: gcore::record::Layout)];
|
||||||
if !record_skips_carrier {
|
if !record_skips_carrier {
|
||||||
state.push(quote!(pub(super) __carrier: gcore::record::Layout));
|
state.push(quote!(pub(super) __carrier: gcore::record::Layout));
|
||||||
|
}
|
||||||
|
if !record_skips_carrier || gather_carrier {
|
||||||
state.push(quote!(pub(super) __plan: ::std::vec::Vec<(usize, usize, usize)>));
|
state.push(quote!(pub(super) __plan: ::std::vec::Vec<(usize, usize, usize)>));
|
||||||
}
|
}
|
||||||
state.push(quote!(pub(super) __frame_bytes: usize));
|
state.push(quote!(pub(super) __frame_bytes: usize));
|
||||||
@@ -752,16 +757,22 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
(crate::codegen::ir::NodeKind::Routing, crate::codegen::ir::Element::Generic(ident)) => Some(ident.clone()),
|
(crate::codegen::ir::NodeKind::Routing, crate::codegen::ir::Element::Generic(ident)) => Some(ident.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
let skips_carrier = record_io && !carrier_present;
|
||||||
|
// A gather carrier copies the returned lane's frame, so it needs the plan
|
||||||
|
// without a carrier layout of its own.
|
||||||
|
let gather_carrier = record_io && node.output.gathers;
|
||||||
|
// A gathered element is carried by the copy plan, not as a lazy token, so
|
||||||
|
// its generic stays a struct parameter.
|
||||||
let record_token = match (kind, &node.output.shape.element) {
|
let record_token = match (kind, &node.output.shape.element) {
|
||||||
(crate::codegen::ir::NodeKind::RecordIo, crate::codegen::ir::Element::Generic(ident)) => Some(ident.clone()),
|
(crate::codegen::ir::NodeKind::RecordIo, crate::codegen::ir::Element::Generic(ident)) if !gather_carrier => Some(ident.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let skips_carrier = record_io && !carrier_present;
|
|
||||||
// The record-io write set, resolved from the output item and carrier input.
|
// The record-io write set, resolved from the output item and carrier input.
|
||||||
let write_markers: Vec<&Type> = node.output.shape.attrs.iter().map(|attr| &attr.marker).collect();
|
let write_markers: Vec<&Type> = node.output.shape.attrs.iter().map(|attr| &attr.marker).collect();
|
||||||
let removes: Vec<&Type> = node.output.removes.iter().map(|attr| &attr.marker).collect();
|
let removes: Vec<&Type> = node.output.removes.iter().map(|attr| &attr.marker).collect();
|
||||||
|
// A gathered element rides the copy plan, never a write.
|
||||||
let element_write: Option<&Type> = match &node.output.shape.element {
|
let element_write: Option<&Type> = match &node.output.shape.element {
|
||||||
crate::codegen::ir::Element::Concrete(ty) => Some(ty),
|
crate::codegen::ir::Element::Concrete(ty) if !gather_carrier => Some(ty),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let carrier_read_ty: Option<&Type> = node.inputs.first().filter(|input| input.subject).and_then(|input| match &input.shape.element {
|
let carrier_read_ty: Option<&Type> = node.inputs.first().filter(|input| input.subject).and_then(|input| match &input.shape.element {
|
||||||
@@ -998,7 +1009,12 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
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 => {
|
ParsedFieldType::Regular(RegularParsedField { ty, .. }) if ir::materialized_levels(&node, index) > 0 => {
|
||||||
quote!(#pat: #core_types::node::List<'_, #ty>)
|
// The gathered lane borrows this list, so both take the kernel's
|
||||||
|
// own subject lifetime.
|
||||||
|
match ir::gathered_subject(&node) == Some(index) {
|
||||||
|
true => quote!(#pat: #core_types::node::List<'__lane, #ty>),
|
||||||
|
false => 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)),
|
||||||
@@ -1578,8 +1594,13 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
// A bare `Attr<M>` in the return type cannot elide its lifetime, so the
|
// A bare `Attr<M>` in the return type cannot elide its lifetime, so the
|
||||||
// kernel gets a fresh one; reference-valued writes name their real
|
// kernel gets a fresh one; reference-valued writes name their real
|
||||||
// lifetime explicitly and pass through untouched.
|
// lifetime explicitly and pass through untouched.
|
||||||
let kernel_output = record_io.then(|| inject_attr_lifetimes(&parsed.output_type)).flatten();
|
let attr_injected = record_io.then(|| inject_attr_lifetimes(&parsed.output_type)).flatten();
|
||||||
let attr_lifetime = kernel_output.is_some().then(|| quote!('__attr,));
|
let attr_lifetime = attr_injected.is_some().then(|| quote!('__attr,));
|
||||||
|
let lane_injected = gather_carrier
|
||||||
|
.then(|| crate::codegen::classify::inject_lane_lifetime(attr_injected.as_ref().unwrap_or(&parsed.output_type)))
|
||||||
|
.flatten();
|
||||||
|
let lane_lifetime = lane_injected.is_some().then(|| quote!('__lane,));
|
||||||
|
let kernel_output = lane_injected.or(attr_injected);
|
||||||
let kernel_output = match derive_routing {
|
let kernel_output = match derive_routing {
|
||||||
true => {
|
true => {
|
||||||
let generic = routing_generic.as_ref().expect("derive routing implies routing");
|
let generic = routing_generic.as_ref().expect("derive routing implies routing");
|
||||||
@@ -1591,7 +1612,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let kernel = match async_fn {
|
let kernel = match async_fn {
|
||||||
false => quote! {
|
false => quote! {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#vis fn #fn_name<#attr_lifetime #(#generics,)*>(#ctx_pat: &#ctx_ident #(, #data_params)* #(, #kernel_params)*) -> #kernel_output #fn_where #body
|
#vis fn #fn_name<#attr_lifetime #lane_lifetime #(#generics,)*>(#ctx_pat: &#ctx_ident #(, #data_params)* #(, #kernel_params)*) -> #kernel_output #fn_where #body
|
||||||
},
|
},
|
||||||
true => {
|
true => {
|
||||||
let kernel_generics = parsed.fn_generics.iter().filter(|param| match param {
|
let kernel_generics = parsed.fn_generics.iter().filter(|param| match param {
|
||||||
@@ -1784,6 +1805,15 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
// A gathered lane owns its record, so the plan reads straight off it.
|
||||||
|
let gather_carry = gather_carrier
|
||||||
|
.then(|| {
|
||||||
|
quote! {
|
||||||
|
let __src_rec = __element.rec();
|
||||||
|
unsafe { #core_types::record::apply_plan(__src_rec, __dst, &self.__plan) };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
let carrier_read_bindings: Vec<TokenStream2> = match skips_carrier || lazy_carrier {
|
let carrier_read_bindings: Vec<TokenStream2> = match skips_carrier || lazy_carrier {
|
||||||
true => Vec::new(),
|
true => Vec::new(),
|
||||||
false => reads_of(0).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(__src_rec))).collect(),
|
false => reads_of(0).into_iter().map(|(slot, read)| read_binding(slot, read, quote!(__src_rec))).collect(),
|
||||||
@@ -1798,9 +1828,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
_ => quote!(#record_kernel_call),
|
_ => quote!(#record_kernel_call),
|
||||||
};
|
};
|
||||||
let attr_binders: Vec<Ident> = (0..write_markers.len()).map(|index| format_ident!("__attr_{index}")).collect();
|
let attr_binders: Vec<Ident> = (0..write_markers.len()).map(|index| format_ident!("__attr_{index}")).collect();
|
||||||
let element_binder = match (element_write, lazy_carrier) {
|
let element_binder = match (element_write.is_some(), lazy_carrier || gather_carrier) {
|
||||||
(Some(_), _) | (None, true) => quote!(__element),
|
(true, _) | (_, true) => quote!(__element),
|
||||||
(None, false) => quote!(_),
|
(false, false) => quote!(_),
|
||||||
};
|
};
|
||||||
// Slot binders in the return tuple's own order: an `Attr` binds the
|
// Slot binders in the return tuple's own order: an `Attr` binds the
|
||||||
// next write binder, a `RemoveAttr` binds nothing.
|
// next write binder, a `RemoveAttr` binds nothing.
|
||||||
@@ -1854,6 +1884,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
let __kernel_value = #kernel_value;
|
let __kernel_value = #kernel_value;
|
||||||
#destructure
|
#destructure
|
||||||
#lazy_carry
|
#lazy_carry
|
||||||
|
#gather_carry
|
||||||
#element_store
|
#element_store
|
||||||
#(#attr_stores)*
|
#(#attr_stores)*
|
||||||
if self.__frame_bytes != 0 {
|
if self.__frame_bytes != 0 {
|
||||||
@@ -2226,7 +2257,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
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");
|
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;));
|
let plan = (!skips_carrier || gather_carrier).then(|| quote!(self.__plan = __resolved.plan;));
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#(#write_installs)*
|
#(#write_installs)*
|
||||||
self.__frame_bytes = __resolved.frame_bytes;
|
self.__frame_bytes = __resolved.frame_bytes;
|
||||||
@@ -2292,7 +2323,9 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
Some(ty) => quote!({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() }),
|
Some(ty) => quote!({ use #core_types::record::{ElementWritePickHashed as _, ElementWritePickPlain as _}; (&#core_types::record::ElementWritePick::<#ty>(::core::marker::PhantomData)).element_write() }),
|
||||||
None => quote!(__carrier.element),
|
None => quote!(__carrier.element),
|
||||||
};
|
};
|
||||||
let layout_def = match skips_carrier {
|
// A gather carrier's base is the gathered subject's layout, so its free
|
||||||
|
// layout fn takes that layout even though the subject materializes.
|
||||||
|
let layout_def = match skips_carrier && !gather_carrier {
|
||||||
true => quote! {
|
true => quote! {
|
||||||
#vis fn #layout_fn() -> #core_types::record::Layout {
|
#vis fn #layout_fn() -> #core_types::record::Layout {
|
||||||
#core_types::record::Layout::default().with_writes(0, #element, &[#(#write_descs),*])
|
#core_types::record::Layout::default().with_writes(0, #element, &[#(#write_descs),*])
|
||||||
@@ -2352,19 +2385,39 @@ 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_default = (!skips_carrier).then(|| quote!(__plan: ::std::vec::Vec::new(),)).into_iter();
|
let plan_default = (!skips_carrier || gather_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_defaults = (0..write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot: 0,));
|
let write_defaults = (0..write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot: 0,));
|
||||||
let mat_cache_defaults = materialized_indices(®ular_fields, &node).into_iter().map(|index| {
|
let mat_cache_defaults = materialized_indices(®ular_fields, &node).into_iter().map(|index| {
|
||||||
let slot = format_ident!("__mat_cache_{index}");
|
let slot = format_ident!("__mat_cache_{index}");
|
||||||
quote!(#slot: ::core::default::Default::default(),)
|
quote!(#slot: ::core::default::Default::default(),)
|
||||||
});
|
});
|
||||||
|
// A ranked input's element generic rides the struct as a phantom
|
||||||
|
// parameter, so the constructor declares and initializes it too.
|
||||||
|
let carried_type_params: Vec<&Ident> = struct_type_params
|
||||||
|
.iter()
|
||||||
|
.filter(|ident| !data_field_generic_idents.contains(ident) && !node_generics.contains(ident))
|
||||||
|
.collect();
|
||||||
|
let carried_generic_params: Vec<TokenStream2> = carried_type_params
|
||||||
|
.iter()
|
||||||
|
.map(|ident| {
|
||||||
|
parsed
|
||||||
|
.fn_generics
|
||||||
|
.iter()
|
||||||
|
.find_map(|param| match param {
|
||||||
|
GenericParam::Type(type_param) if &&type_param.ident == ident => Some(quote!(#type_param)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| quote!(#ident))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let marker_init = (!carried_type_params.is_empty()).then(|| quote!(__marker: ::core::marker::PhantomData,)).into_iter();
|
||||||
quote! {
|
quote! {
|
||||||
#layout_def
|
#layout_def
|
||||||
#layout_meta_def
|
#layout_meta_def
|
||||||
|
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl<#(#data_field_generic_idents,)* #(#node_generics,)*> #mod_name::#struct_name<#(#struct_type_params,)*> {
|
impl<#(#data_field_generic_idents,)* #(#node_generics,)* #(#carried_generic_params,)*> #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 {
|
||||||
#(#read_inits)*
|
#(#read_inits)*
|
||||||
@@ -2375,6 +2428,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
#(#input_layout_inits)*
|
#(#input_layout_inits)*
|
||||||
__layout: ::core::default::Default::default(),
|
__layout: ::core::default::Default::default(),
|
||||||
#(#plan_default)*
|
#(#plan_default)*
|
||||||
|
#(#marker_init)*
|
||||||
__frame_bytes: 0,
|
__frame_bytes: 0,
|
||||||
#(#read_names)*
|
#(#read_names)*
|
||||||
#(#write_defaults)*
|
#(#write_defaults)*
|
||||||
|
|||||||
@@ -230,6 +230,33 @@ pub(crate) fn inject_attr_lifetimes(output: &Type) -> Option<Type> {
|
|||||||
injector.changed.then_some(ty)
|
injector.changed.then_some(ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Binds a `Lane` output to the kernel's subject lifetime, replacing whatever
|
||||||
|
/// the author spelled: the lane borrows the materialized subject, not the arena.
|
||||||
|
pub(crate) fn inject_lane_lifetime(output: &Type) -> Option<Type> {
|
||||||
|
struct Injector {
|
||||||
|
changed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisitMut for Injector {
|
||||||
|
fn visit_path_segment_mut(&mut self, segment: &mut syn::PathSegment) {
|
||||||
|
if segment.ident == "Lane"
|
||||||
|
&& let PathArguments::AngleBracketed(args) = &mut segment.arguments
|
||||||
|
{
|
||||||
|
let kept: Vec<GenericArgument> = args.args.iter().filter(|arg| !matches!(arg, GenericArgument::Lifetime(_))).cloned().collect();
|
||||||
|
args.args = kept.into_iter().collect();
|
||||||
|
args.args.insert(0, GenericArgument::Lifetime(Lifetime::new("'__lane", proc_macro2::Span::call_site())));
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
syn::visit_mut::visit_path_segment_mut(self, segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ty = output.clone();
|
||||||
|
let mut injector = Injector { changed: false };
|
||||||
|
injector.visit_type_mut(&mut ty);
|
||||||
|
injector.changed.then_some(ty)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn contains_open_generic(parsed: &ParsedNodeFn, ty: &Type) -> bool {
|
pub(crate) fn contains_open_generic(parsed: &ParsedNodeFn, ty: &Type) -> bool {
|
||||||
let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone());
|
let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone());
|
||||||
parsed
|
parsed
|
||||||
@@ -306,12 +333,14 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
|
|||||||
let ParsedFieldType::Regular(RegularParsedField { ty, lend: None, implementations, .. }) = &carrier_field.ty else {
|
let ParsedFieldType::Regular(RegularParsedField { ty, lend: None, implementations, .. }) = &carrier_field.ty else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
// A gathered subject is never read as an element, so its generic stays open.
|
||||||
|
let gathers = crate::codegen::ir::gathers_lane(parsed);
|
||||||
let token = match ty {
|
let token = match ty {
|
||||||
Type::Tuple(tuple) if tuple.elems.is_empty() => None,
|
Type::Tuple(tuple) if tuple.elems.is_empty() => None,
|
||||||
ty => match implementations.is_empty().then(|| unbounded_generic(parsed, ty)).flatten() {
|
ty => match implementations.is_empty().then(|| unbounded_generic(parsed, ty)).flatten() {
|
||||||
Some(token) => Some(token),
|
Some(token) => Some(token),
|
||||||
None => {
|
None => {
|
||||||
if contains_open_generic(parsed, ty) {
|
if !gathers && contains_open_generic(parsed, ty) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@@ -334,7 +363,7 @@ pub(crate) fn record_shape(parsed: &ParsedNodeFn) -> Option<RecordShape> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if contains_open_generic(parsed, &element) {
|
if !gathers && contains_open_generic(parsed, &element) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ fn output(parsed: &ParsedNodeFn, generics: &[Ident]) -> Output {
|
|||||||
Some(RecordWrites { element, markers, removes }) => (element, markers, removes),
|
Some(RecordWrites { element, markers, removes }) => (element, markers, removes),
|
||||||
None => (row, Vec::new(), Vec::new()),
|
None => (row, Vec::new(), Vec::new()),
|
||||||
};
|
};
|
||||||
|
let (element, gathers) = match lane_inner(&element) {
|
||||||
|
Some(inner) => (inner, true),
|
||||||
|
None => (element, false),
|
||||||
|
};
|
||||||
Output {
|
Output {
|
||||||
shape: ItemShape {
|
shape: ItemShape {
|
||||||
element: element_of(&element, generics),
|
element: element_of(&element, generics),
|
||||||
@@ -97,6 +101,7 @@ fn output(parsed: &ParsedNodeFn, generics: &[Ident]) -> Output {
|
|||||||
attrs: writes.into_iter().map(|marker| LevelAttr { marker, level: 0 }).collect(),
|
attrs: writes.into_iter().map(|marker| LevelAttr { marker, level: 0 }).collect(),
|
||||||
},
|
},
|
||||||
removes: removes.into_iter().map(|marker| LevelAttr { marker, level: 0 }).collect(),
|
removes: removes.into_iter().map(|marker| LevelAttr { marker, level: 0 }).collect(),
|
||||||
|
gathers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,6 +215,31 @@ fn replace_first_type_arg(ty: &Type, replacement: Type) -> Type {
|
|||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the output's element position spells `Lane<T>`.
|
||||||
|
pub(crate) fn gathers_lane(parsed: &ParsedNodeFn) -> bool {
|
||||||
|
gathered_element(parsed).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gathered_element(parsed: &ParsedNodeFn) -> Option<Type> {
|
||||||
|
let row = slot_value_type(&parsed.output_type);
|
||||||
|
let element = record_writes(&row).map_or(row, |writes| writes.element);
|
||||||
|
lane_inner(&element).is_some().then_some(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The element type inside a `Lane<T>` position, lifetime argument skipped.
|
||||||
|
fn lane_inner(ty: &Type) -> Option<Type> {
|
||||||
|
let Type::Path(path) = ty else { return None };
|
||||||
|
let segment = path.path.segments.last()?;
|
||||||
|
if segment.ident != "Lane" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let PathArguments::AngleBracketed(args) = &segment.arguments else { return None };
|
||||||
|
args.args.iter().find_map(|arg| match arg {
|
||||||
|
GenericArgument::Type(inner) => Some(inner.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn ilist_inner(ty: &Type) -> Option<Type> {
|
fn ilist_inner(ty: &Type) -> Option<Type> {
|
||||||
let Type::Path(path) = ty else { return None };
|
let Type::Path(path) = ty else { return None };
|
||||||
let segment = path.path.segments.last()?;
|
let segment = path.path.segments.last()?;
|
||||||
@@ -226,13 +256,7 @@ fn ilist_inner(ty: &Type) -> Option<Type> {
|
|||||||
/// Emits the `LayoutMeta` literal from the IR. `element_spec` is supplied by the
|
/// Emits the `LayoutMeta` literal from the IR. `element_spec` is supplied by the
|
||||||
/// caller since it is the one row-dependent facet; the rest folds from the node.
|
/// caller since it is the one row-dependent facet; the rest folds from the node.
|
||||||
pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_types: &TokenStream2) -> TokenStream2 {
|
pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_types: &TokenStream2) -> TokenStream2 {
|
||||||
// A materialized subject is folded, not carried: it contributes no layout.
|
let sources = layout_sources(node).into_iter().map(|index| index as u8);
|
||||||
let sources = node
|
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(index, input)| input.subject && materialized_levels(node, *index) == 0)
|
|
||||||
.map(|(index, _)| index as u8);
|
|
||||||
let reads = node.inputs.iter().enumerate().filter_map(|(index, input)| {
|
let reads = node.inputs.iter().enumerate().filter_map(|(index, input)| {
|
||||||
(matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()).then(|| {
|
(matches!(input.evaluation, Evaluation::Eager) && !input.shape.attrs.is_empty()).then(|| {
|
||||||
let descs = field_writes(&input.shape.attrs, core_types);
|
let descs = field_writes(&input.shape.attrs, core_types);
|
||||||
@@ -264,6 +288,29 @@ pub(crate) fn layout_meta_tokens(node: &Node, element_spec: TokenStream2, core_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The subjects whose layouts union into the output's base: un-materialized
|
||||||
|
/// ones, plus a gathered subject.
|
||||||
|
pub(crate) fn layout_sources(node: &Node) -> Vec<usize> {
|
||||||
|
node.inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(index, input)| input.subject && (materialized_levels(node, *index) == 0 || gathered_subject(node) == Some(*index)))
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The materialized subject a gather-carrier copies its output frames from.
|
||||||
|
pub(crate) fn gathered_subject(node: &Node) -> Option<usize> {
|
||||||
|
if !node.output.gathers {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
node.inputs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(index, input)| input.subject && materialized_levels(node, *index) > 0)
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
}
|
||||||
|
|
||||||
/// The single carried subject a level-preserving node forwards its extents
|
/// The single carried subject a level-preserving node forwards its extents
|
||||||
/// to: exactly one un-materialized subject, no level shift, and no fold.
|
/// to: exactly one un-materialized subject, no level shift, and no fold.
|
||||||
pub(crate) fn forwarded_subject(node: &Node) -> Option<usize> {
|
pub(crate) fn forwarded_subject(node: &Node) -> Option<usize> {
|
||||||
@@ -282,8 +329,12 @@ pub(crate) fn forwarded_subject(node: &Node) -> Option<usize> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The materialized subject a node folds, as `(input, levels)`.
|
/// The materialized subject a node folds, as `(input, levels)`. A gathered
|
||||||
|
/// subject is count-preserving, not folded.
|
||||||
pub(crate) fn folded_subject(node: &Node) -> Option<(u8, u8)> {
|
pub(crate) fn folded_subject(node: &Node) -> Option<(u8, u8)> {
|
||||||
|
if node.output.gathers {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
node.inputs
|
node.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -303,14 +354,9 @@ fn field_writes(attrs: &[LevelAttr], core_types: &TokenStream2) -> Vec<TokenStre
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn level_delta(node: &Node) -> i8 {
|
fn level_delta(node: &Node) -> i8 {
|
||||||
// A materialized subject contributes no base layout, so the delta is
|
// A folded subject contributes no base layout, so the delta is relative to
|
||||||
// relative to the fresh (empty) base.
|
// the fresh (empty) base.
|
||||||
let base_depth = node
|
let base_depth = layout_sources(node).first().map_or(0, |&index| node.inputs[index].shape.depth as i8);
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(index, input)| input.subject && materialized_levels(node, *index) == 0)
|
|
||||||
.map_or(0, |(_, input)| input.shape.depth as i8);
|
|
||||||
node.output.shape.depth as i8 - base_depth
|
node.output.shape.depth as i8 - base_depth
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +435,11 @@ fn is_routing(node: &Node) -> bool {
|
|||||||
|
|
||||||
fn has_attr_io(node: &Node) -> bool {
|
fn has_attr_io(node: &Node) -> bool {
|
||||||
// Reads on lazy inputs ride the flip; only eager reads make a record-io node.
|
// Reads on lazy inputs ride the flip; only eager reads make a record-io node.
|
||||||
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()
|
// A gathered output takes the record tail regardless of its write set.
|
||||||
|
node.output.gathers
|
||||||
|
|| 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
|
/// Levels of `input[index]` the output does not carry; `> 0` folds the input
|
||||||
@@ -484,6 +534,9 @@ pub(crate) enum Evaluation {
|
|||||||
pub(crate) struct Output {
|
pub(crate) struct Output {
|
||||||
pub(crate) shape: ItemShape,
|
pub(crate) shape: ItemShape,
|
||||||
pub(crate) removes: Vec<LevelAttr>,
|
pub(crate) removes: Vec<LevelAttr>,
|
||||||
|
/// The element position spells `Lane<T>`, so the output frame is a copy of a
|
||||||
|
/// chosen subject lane.
|
||||||
|
pub(crate) gathers: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An item's ranked layout; `attrs` are reads on an input, writes on the output.
|
/// An item's ranked layout; `attrs` are reads on an input, writes on the output.
|
||||||
@@ -545,13 +598,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth as i8);
|
let subject_depth = node.inputs.iter().find(|input| input.subject).map_or(0, |input| input.shape.depth as i8);
|
||||||
Facts {
|
Facts {
|
||||||
sources: node
|
sources: layout_sources(node),
|
||||||
.inputs
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(index, input)| input.subject && materialized_levels(node, *index) == 0)
|
|
||||||
.map(|(index, _)| index)
|
|
||||||
.collect(),
|
|
||||||
carried,
|
carried,
|
||||||
writes: markers(node.output.shape.attrs.iter().map(|attr| &attr.marker)),
|
writes: markers(node.output.shape.attrs.iter().map(|attr| &attr.marker)),
|
||||||
removes: markers(node.output.removes.iter().map(|attr| &attr.marker)),
|
removes: markers(node.output.removes.iter().map(|attr| &attr.marker)),
|
||||||
|
|||||||
@@ -110,10 +110,15 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if lazy_carrier && !crate::codegen::ir::build(parsed).derives {
|
let node = crate::codegen::ir::build(parsed);
|
||||||
|
if lazy_carrier && !node.derives {
|
||||||
emit_error!(parsed.input.pat_ident.span(), "a lazy record carrier evaluates at derived contexts; spell `impl Ctx + DeriveCtx`");
|
emit_error!(parsed.input.pat_ident.span(), "a lazy record carrier evaluates at derived contexts; spell `impl Ctx + DeriveCtx`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if node.output.gathers && crate::codegen::ir::gathered_subject(&node).is_none() {
|
||||||
|
emit_error!(parsed.output_type.span(), "a `Lane` output gathers a materialized subject; give the primary input an `IList` type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let no_carrier = matches!(carrier_ty, Type::Tuple(tuple) if tuple.elems.is_empty());
|
let no_carrier = matches!(carrier_ty, Type::Tuple(tuple) if tuple.elems.is_empty());
|
||||||
let token = match (no_carrier, &carrier.ty) {
|
let token = match (no_carrier, &carrier.ty) {
|
||||||
@@ -131,12 +136,12 @@ fn validate_record_io(parsed: &ParsedNodeFn) {
|
|||||||
None => {
|
None => {
|
||||||
if let Some(ident) = crate::codegen::unbounded_generic(parsed, element) {
|
if let Some(ident) = crate::codegen::unbounded_generic(parsed, element) {
|
||||||
emit_error!(parsed.output_type.span(), "the returned generic element `{}` has no matching input", ident);
|
emit_error!(parsed.output_type.span(), "the returned generic element `{}` has no matching input", ident);
|
||||||
} else if !no_carrier && crate::codegen::contains_open_generic(parsed, carrier_ty) {
|
} else if !no_carrier && !node.output.gathers && crate::codegen::contains_open_generic(parsed, carrier_ty) {
|
||||||
emit_error!(
|
emit_error!(
|
||||||
carrier.pat_ident.span(),
|
carrier.pat_ident.span(),
|
||||||
"record element reads are monomorphic for now; use a concrete element type or an unbounded passthrough generic"
|
"record element reads are monomorphic for now; use a concrete element type or an unbounded passthrough generic"
|
||||||
);
|
);
|
||||||
} else if crate::codegen::contains_open_generic(parsed, element) {
|
} else if !node.output.gathers && crate::codegen::contains_open_generic(parsed, element) {
|
||||||
emit_error!(parsed.output_type.span(), "a written element must be a concrete type");
|
emit_error!(parsed.output_type.span(), "a written element must be a concrete type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use glam::DAffine2;
|
|||||||
use core_types::context::{DeriveCtx, ExtractIndex, IndexLink, InjectIndex};
|
use core_types::context::{DeriveCtx, ExtractIndex, IndexLink, InjectIndex};
|
||||||
use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
|
use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
|
||||||
use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt, Level};
|
use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt, Level};
|
||||||
|
use core_types::node::Lane;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::Ctx;
|
use core_types::Ctx;
|
||||||
|
|
||||||
@@ -304,6 +305,25 @@ fn mirror_extent(content: ListIn<'_, f64>, keep_original: ValueIn<'_, bool>, lev
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gather-carrier kernel: the level's lanes in reverse. Returning a `Lane`
|
||||||
|
/// copies that lane's whole record, so undeclared attributes ride along and
|
||||||
|
/// only the declared opacity is rewritten.
|
||||||
|
#[node_macro::node(category("Test"), extent(reverse_lanes_extent))]
|
||||||
|
fn reverse_lanes(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList<f64>, opacity: f64) -> Result<IList<(Lane<f64>, Attr<Opacity>)>, Interrupt> {
|
||||||
|
let lane = ctx.innermost_index() as usize;
|
||||||
|
if lane >= content.len() {
|
||||||
|
return Err(GraphError::past_end().into());
|
||||||
|
}
|
||||||
|
Ok((content.lane(content.len() - 1 - lane), Attr(opacity)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reverse_lanes_extent(content: ListIn<'_, f64>, _opacity: ValueIn<'_, f64>, level: LevelIn) -> GPoll<Extent> {
|
||||||
|
match level.top() {
|
||||||
|
true => content.total(),
|
||||||
|
false => GPoll::Final(Extent::Exactly(1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Test"))]
|
#[node_macro::node(category("Test"))]
|
||||||
fn source_opacity(_: impl Ctx, _: (), element: f64, opacity: f64) -> (f64, Attr<Opacity>) {
|
fn source_opacity(_: impl Ctx, _: (), element: f64, opacity: f64) -> (f64, Attr<Opacity>) {
|
||||||
(element, Attr(opacity))
|
(element, Attr(opacity))
|
||||||
@@ -1355,6 +1375,49 @@ mod tests {
|
|||||||
assert_eq!(transform.translation.x, 30., "without originals every lane reflects");
|
assert_eq!(transform.translation.x, 30., "without originals every lane reflects");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_gathered_lane_carries_its_whole_record() {
|
||||||
|
let arena = Arena::new(1 << 16).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
// The subject carries Transform, which the kernel never declares.
|
||||||
|
let layout = Layout::default().with_writes(1, core_types::record::element_write::<f64>(), &[core_types::record::FieldWrite::of::<Transform>(0)]);
|
||||||
|
reserve_for(&[&layout]);
|
||||||
|
let rows = [(1., 10.), (2., 30.), (3., 20.)];
|
||||||
|
let content = LeveledTransformSource {
|
||||||
|
layout: layout.clone(),
|
||||||
|
rows: rows.iter().map(|&(element, x)| (element, DAffine2::from_translation(glam::DVec2::new(x, 0.)))).collect(),
|
||||||
|
};
|
||||||
|
let node = install(
|
||||||
|
ReverseLanesNode::new(RecordSource::new(content, &layout, &layout), ValueNode(0.25)),
|
||||||
|
reverse_lanes_layout_meta(),
|
||||||
|
&[Some(&layout)],
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||||
|
assert_eq!(out.depth, 1, "gathering preserves the subject's depth");
|
||||||
|
assert!(out.offset_of(<Transform as AttributeMarker>::NAME, 0).is_some(), "the undeclared attribute survives");
|
||||||
|
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)));
|
||||||
|
|
||||||
|
let head = ctx.index_head();
|
||||||
|
for (lane, &(element, x)) in rows.iter().rev().enumerate() {
|
||||||
|
let mark = stack::sp();
|
||||||
|
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
let rec = out.rec(&value);
|
||||||
|
assert_eq!(unsafe { rec.element::<f64>() }, element, "lane {lane} takes the gathered element");
|
||||||
|
let transform: DAffine2 = unsafe { rec.read(out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap()) };
|
||||||
|
assert_eq!(transform.translation.x, x, "lane {lane} carries the gathered transform");
|
||||||
|
let opacity: f64 = unsafe { rec.read(out.offset_of(<Opacity as AttributeMarker>::NAME, 0).unwrap()) };
|
||||||
|
assert_eq!(opacity, 0.25, "lane {lane} takes the declared write");
|
||||||
|
// SAFETY: every field was read out above, so no borrow into this lane's frames remains.
|
||||||
|
unsafe { stack::rewind(mark) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn batch_lanes_match_per_lane_eval() {
|
fn batch_lanes_match_per_lane_eval() {
|
||||||
let arena = Arena::new(1 << 16).unwrap();
|
let arena = Arena::new(1 << 16).unwrap();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use core_types::gpoll::GraphError;
|
|||||||
use core_types::gpoll::Interrupt;
|
use core_types::gpoll::Interrupt;
|
||||||
use core_types::gpoll::{Extent, GPoll};
|
use core_types::gpoll::{Extent, GPoll};
|
||||||
use core_types::list::{Item, ItemAttributeValues, List};
|
use core_types::list::{Item, ItemAttributeValues, List};
|
||||||
|
use core_types::node::Lane;
|
||||||
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||||
use core_types::transform::Transform;
|
use core_types::transform::Transform;
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
@@ -108,7 +109,7 @@ fn assign_colors<'e>(
|
|||||||
let park_existing = |paint: Option<&List<Graphic>>| -> Result<Option<&'e List<Graphic>>, Interrupt> { paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose() };
|
let park_existing = |paint: Option<&List<Graphic>>| -> Result<Option<&'e List<Graphic>>, Interrupt> { paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose() };
|
||||||
let existing_fill = park_existing(content.lane(lane).attr::<Fill>())?;
|
let existing_fill = park_existing(content.lane(lane).attr::<Fill>())?;
|
||||||
let existing_stroke = park_existing(content.lane(lane).attr::<StrokeAttr>())?;
|
let existing_stroke = park_existing(content.lane(lane).attr::<StrokeAttr>())?;
|
||||||
let carried = carried_lane_attrs(ctx.arena(), content.lane(lane))?;
|
let carried = carried_lane_attrs(ctx.arena(), *content.lane(lane))?;
|
||||||
let (transform, layer_path) = carried;
|
let (transform, layer_path) = carried;
|
||||||
|
|
||||||
if gradient.len() == 0 {
|
if gradient.len() == 0 {
|
||||||
@@ -176,7 +177,7 @@ fn assign_colors_graphic<'e>(
|
|||||||
return Err(GraphError::past_end().into());
|
return Err(GraphError::past_end().into());
|
||||||
}
|
}
|
||||||
let mut element = graphic_types::graphic::map_groups_to_legacy(content.element_ref(lane));
|
let mut element = graphic_types::graphic::map_groups_to_legacy(content.element_ref(lane));
|
||||||
let (transform, layer_path) = carried_lane_attrs(ctx.arena(), content.lane(lane))?;
|
let (transform, layer_path) = carried_lane_attrs(ctx.arena(), *content.lane(lane))?;
|
||||||
|
|
||||||
if gradient.len() == 0 {
|
if gradient.len() == 0 {
|
||||||
return Ok((element, transform, layer_path));
|
return Ok((element, transform, layer_path));
|
||||||
@@ -970,16 +971,10 @@ fn bilinear_interpolate(t: DVec2, quad: &[DVec2; 4]) -> DVec2 {
|
|||||||
tl * (1. - t.x) * (1. - t.y) + tr * t.x * (1. - t.y) + br * t.x * t.y + bl * (1. - t.x) * t.y
|
tl * (1. - t.x) * (1. - t.y) + tr * t.x * (1. - t.y) + br * t.x * t.y + bl * (1. - t.x) * t.y
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector), extent(pack_strips_extent))]
|
||||||
fn pack_strips<T: Send + Clone>(
|
fn pack_strips<'e, T: BoundingBox + Clone + Send + Sync + CacheHash + 'static>(
|
||||||
_: impl Ctx,
|
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||||
#[implementations(
|
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>)] elements: IList<T>,
|
||||||
List<Graphic>,
|
|
||||||
List<Vector>,
|
|
||||||
List<Raster<CPU>>,
|
|
||||||
List<Raster<GPU>>,
|
|
||||||
)]
|
|
||||||
elements: List<T>,
|
|
||||||
#[default(0.)]
|
#[default(0.)]
|
||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
separation: f64,
|
separation: f64,
|
||||||
@@ -987,60 +982,56 @@ fn pack_strips<T: Send + Clone>(
|
|||||||
#[unit(" px")]
|
#[unit(" px")]
|
||||||
strip_max_length: f64,
|
strip_max_length: f64,
|
||||||
strip_direction: RowsOrColumns,
|
strip_direction: RowsOrColumns,
|
||||||
) -> List<T>
|
) -> Result<IList<(Lane<T>, Attr<'e, TransformAttr>)>, Interrupt> {
|
||||||
where
|
// Best-Fit Decreasing Height: sort by cross-axis size, then place each item on
|
||||||
Graphic: From<List<T>>,
|
// the strip with the least remaining space that still fits it.
|
||||||
List<T>: BoundingBox,
|
|
||||||
{
|
|
||||||
// Packs shapes using bounds with Best-Fit Decreasing Height (BFDH) algorithm:
|
|
||||||
// - Sort shapes by cross-axis size (tallest first for rows, widest first for columns)
|
|
||||||
// - For each shape, find the existing strip with minimum remaining space that fits
|
|
||||||
// - Create new strip only if no existing strip can accommodate the shape
|
|
||||||
|
|
||||||
struct Strip {
|
struct Strip {
|
||||||
along_position: f64,
|
along_position: f64,
|
||||||
cross_position: f64,
|
cross_position: f64,
|
||||||
cross_extent: f64,
|
cross_extent: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the items to be sorted
|
let lane = ctx.innermost_index() as usize;
|
||||||
let mut items: Vec<(f64, f64, DVec2, Item<T>)> = elements
|
if lane >= elements.len() {
|
||||||
.into_iter()
|
return Err(GraphError::past_end().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut items: Vec<(f64, f64, DVec2, usize)> = (0..elements.len())
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
// Single-item `List` to query its bounding box
|
// The pre-flip single-item `List` wrap composed the item's own
|
||||||
let single = List::new_from_item(row.clone());
|
// transform into its bounds.
|
||||||
let (w, h, top_left) = match single.bounding_box(DAffine2::IDENTITY, false) {
|
let lane_transform: DAffine2 = elements.lane(row).attr::<TransformAttr>();
|
||||||
|
let (width, height, top_left) = match elements.element_ref(row).bounding_box(lane_transform, false) {
|
||||||
RenderBoundingBox::Rectangle([min, max]) => {
|
RenderBoundingBox::Rectangle([min, max]) => {
|
||||||
let size = max - min;
|
let size = max - min;
|
||||||
(size.x.max(0.), size.y.max(0.), min)
|
(size.x.max(0.), size.y.max(0.), min)
|
||||||
}
|
}
|
||||||
_ => (0., 0., DVec2::ZERO),
|
_ => (0., 0., DVec2::ZERO),
|
||||||
};
|
};
|
||||||
let (along, cross) = match strip_direction {
|
match strip_direction {
|
||||||
RowsOrColumns::Rows => (w, h),
|
RowsOrColumns::Rows => (width, height, top_left, row),
|
||||||
RowsOrColumns::Columns => (h, w),
|
RowsOrColumns::Columns => (height, width, top_left, row),
|
||||||
};
|
}
|
||||||
(along, cross, top_left, row)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Sort by cross-axis size, largest first
|
|
||||||
items.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
|
items.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
|
||||||
|
|
||||||
let mut result = List::new();
|
|
||||||
let mut strips: Vec<Strip> = Vec::new();
|
let mut strips: Vec<Strip> = Vec::new();
|
||||||
|
let mut gathered = (lane, DAffine2::IDENTITY);
|
||||||
|
|
||||||
// This looks n^2 but it is just n*k where k is the number of strips, which is generally much smaller than n
|
for (position, &(along, cross, top_left, source)) in items.iter().enumerate() {
|
||||||
for (along, cross, top_left, mut row) in items {
|
let lane_transform: DAffine2 = elements.lane(source).attr::<TransformAttr>();
|
||||||
if along <= 0. {
|
if along <= 0. {
|
||||||
result.push(row);
|
if position == lane {
|
||||||
|
gathered = (source, lane_transform);
|
||||||
|
break;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a good strip, minimum remaining space that can fit this item ideally
|
// n*k where k is the strip count, generally much smaller than n
|
||||||
let mut best_strip_index = None;
|
let mut best_strip_index = None;
|
||||||
let mut min_remaining_space = f64::INFINITY;
|
let mut min_remaining_space = f64::INFINITY;
|
||||||
|
|
||||||
for (index, strip) in strips.iter().enumerate() {
|
for (index, strip) in strips.iter().enumerate() {
|
||||||
let remaining_space = strip_max_length - strip.along_position;
|
let remaining_space = strip_max_length - strip.along_position;
|
||||||
if remaining_space >= along && remaining_space < min_remaining_space {
|
if remaining_space >= along && remaining_space < min_remaining_space {
|
||||||
@@ -1049,45 +1040,49 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(strip_index) = best_strip_index {
|
let target_position = match best_strip_index {
|
||||||
// Place on existing strip
|
Some(strip_index) => {
|
||||||
let strip = &mut strips[strip_index];
|
let strip = &mut strips[strip_index];
|
||||||
|
if cross > strip.cross_extent {
|
||||||
// Update strip cross extent if needed
|
strip.cross_extent = cross;
|
||||||
if cross > strip.cross_extent {
|
}
|
||||||
strip.cross_extent = cross;
|
let target = match strip_direction {
|
||||||
|
RowsOrColumns::Rows => DVec2::new(strip.along_position, strip.cross_position),
|
||||||
|
RowsOrColumns::Columns => DVec2::new(strip.cross_position, strip.along_position),
|
||||||
|
};
|
||||||
|
strip.along_position += along + separation;
|
||||||
|
target
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
let new_cross = strips.last().map_or(0., |last| last.cross_position + last.cross_extent + separation);
|
||||||
|
let target = match strip_direction {
|
||||||
|
RowsOrColumns::Rows => DVec2::new(0., new_cross),
|
||||||
|
RowsOrColumns::Columns => DVec2::new(new_cross, 0.),
|
||||||
|
};
|
||||||
|
strips.push(Strip {
|
||||||
|
along_position: along + separation,
|
||||||
|
cross_position: new_cross,
|
||||||
|
cross_extent: cross,
|
||||||
|
});
|
||||||
|
target
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let target_position = match strip_direction {
|
if position == lane {
|
||||||
RowsOrColumns::Rows => DVec2::new(strip.along_position, strip.cross_position),
|
gathered = (source, DAffine2::from_translation(target_position - top_left) * lane_transform);
|
||||||
RowsOrColumns::Columns => DVec2::new(strip.cross_position, strip.along_position),
|
break;
|
||||||
};
|
|
||||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
|
||||||
row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform);
|
|
||||||
|
|
||||||
strip.along_position += along + separation;
|
|
||||||
} else {
|
|
||||||
// Create new strip
|
|
||||||
let new_cross = strips.last().map_or(0., |last| last.cross_position + last.cross_extent + separation);
|
|
||||||
|
|
||||||
let target_position = match strip_direction {
|
|
||||||
RowsOrColumns::Rows => DVec2::new(0., new_cross),
|
|
||||||
RowsOrColumns::Columns => DVec2::new(new_cross, 0.),
|
|
||||||
};
|
|
||||||
let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM);
|
|
||||||
row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform);
|
|
||||||
|
|
||||||
strips.push(Strip {
|
|
||||||
along_position: along + separation,
|
|
||||||
cross_position: new_cross,
|
|
||||||
cross_extent: cross,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
let (source, placement) = gathered;
|
||||||
|
Ok((elements.lane(source), Attr(placement)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pack_strips_extent<T>(elements: ListIn<'_, T>, _separation: ValueIn<'_, f64>, _strip_max_length: ValueIn<'_, f64>, _strip_direction: ValueIn<'_, RowsOrColumns>, level: LevelIn) -> GPoll<Extent> {
|
||||||
|
match level.top() {
|
||||||
|
true => elements.total(),
|
||||||
|
false => GPoll::Final(Extent::Exactly(1)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
|
/// Automatically constructs tangents (Bézier handles) for anchor points in a vector path.
|
||||||
|
|||||||
Reference in New Issue
Block a user