From 56c772de4c86b8042025fc8147dfca4068c7a94f Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 22 Aug 2026 12:27:53 +0000 Subject: [PATCH] Swap the map and index family nodes to their leveled forms in place --- node-graph/nodes/graphic/src/graphic.rs | 189 +++++++++++------------- node-graph/nodes/graphic/src/record.rs | 2 +- 2 files changed, 87 insertions(+), 104 deletions(-) diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 7d69dd4631..685caf678a 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -13,63 +13,81 @@ use raster_types::{CPU, GPU, Raster}; use vector_types::gradient::{GradientSpreadMethod, GradientType}; use vector_types::{GradientStop, GradientStops, ReferencePoint}; -/// Returns the value at the specified index in the list. -/// If no value exists at that index, the type's default value is returned. -#[node_macro::node(category("General"))] -pub fn index_elements( - _: impl Ctx, +/// Resolves a signed index over `total` lanes: negatives count from the end, +/// out of range resolves to nothing. +fn resolve_index(index: f64, total: u64) -> Option { + let index = index as i64; + match index < 0 { + true => total.checked_sub(index.unsigned_abs()), + false => ((index as u64) < total).then_some(index as u64), + } +} + +/// Returns a one-lane level holding the item at the specified index with its +/// attributes, or an empty level when the index is out of range. +#[node_macro::node(category("General"), extent(index_elements_extent))] +pub fn index_elements( + ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, /// The list of data. - #[implementations( - List, - List, - List, - List>, - List>, - List, - List, - List, - List, - List, - List, - )] - list: T, + list: impl Node, Output = T>, /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item. index: SignedInteger, -) -> T::Output -where - T::Output: Clone + Default, -{ - let index = index as i32; +) -> Result { + let total = match list.extent(ctx, Level::Total) { + GPoll::Final(Extent::Exactly(count)) => count as u64, + GPoll::Pending => return Err(Interrupt::Pending), + _ => return Err(GraphError::new("index elements over a non-exact extent").into()), + }; + let Some(source) = resolve_index(index, total) else { + return Err(GraphError::new("index elements addressed its empty selection").into()); + }; + let mut shifted = *ctx; + shifted.set_index(source); + list.eval(&shifted) +} - if index < 0 { list.at_index_from_end(-index as usize) } else { list.at_index(index as usize) }.unwrap_or_default() +fn index_elements_extent(list: ExtentIn<'_>, index: ValueIn<'_, f64>, level: LevelIn) -> GPoll { + match level.top() { + true => index.get().zip(list.at(level)).map(|(index, extent)| match extent { + Extent::Exactly(count) => Extent::Exactly(resolve_index(index, count as u64).is_some() as usize), + _ => Extent::Exactly(1), + }), + false => list.at(level), + } } /// Returns the list with the element at the specified index removed. /// If no value exists at that index, the list is returned unchanged. -#[node_macro::node(category("General"))] -pub fn omit_element( - _: impl Ctx, +#[node_macro::node(category("General"), extent(omit_element_extent))] +pub fn omit_element( + ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, /// The list of data. - #[implementations( - List, - List, - List, - List, - List>, - List>, - List, - List, - )] - list: T, + list: impl Node, Output = T>, /// The index of the item to remove, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item. index: SignedInteger, -) -> T { - let index = index as i32; +) -> Result { + let total = match list.extent(ctx, Level::Total) { + GPoll::Final(Extent::Exactly(count)) => count as u64, + GPoll::Pending => return Err(Interrupt::Pending), + _ => return Err(GraphError::new("omit over a non-exact extent").into()), + }; + let lane = ctx.innermost_index(); + let source = match resolve_index(index, total) { + Some(omitted) if lane >= omitted => lane + 1, + _ => lane, + }; + let mut shifted = *ctx; + shifted.set_index(source); + list.eval(&shifted) +} - if index < 0 { - list.omit_index_from_end(index.unsigned_abs() as usize) - } else { - list.omit_index(index as usize) +fn omit_element_extent(list: ExtentIn<'_>, index: ValueIn<'_, f64>, level: LevelIn) -> GPoll { + match level.top() { + true => index.get().zip(list.at(level)).map(|(index, extent)| match extent { + Extent::Exactly(count) if resolve_index(index, count as u64).is_some() => Extent::Exactly(count - 1), + extent => extent, + }), + false => list.at(level), } } @@ -77,73 +95,38 @@ pub fn omit_element( /// Use this when downstream nodes want just the inner value rather than a `List` containing a single item. /// If no value exists at that index, the element type's default is returned. #[node_macro::node(category("General"))] -pub fn extract_element( - _: impl Ctx, +pub fn extract_element( + _: impl Ctx + ExtractIndex + InjectIndex + Copy, /// The `List` of data to extract from. - #[implementations( - List, - List, - List, - List, - List, - List, - List, - List>, - List, - List, - )] - list: List, + #[implementations(String, f64, NodeId, Color, GradientStops, Vector, Raster, Graphic, Artboard)] list: IList, /// The index of the item to retrieve, starting from 0 for the first item. Negative indices count backwards from the end of the list, starting from -1 for the last item. index: SignedInteger, ) -> T { - let len = list.len(); - let index = index as i32; - let resolved = if index < 0 { - let from_end = index.unsigned_abs() as usize; - if from_end > len { - return T::default(); - } - len - from_end - } else { - index as usize - }; - list.element(resolved).cloned().unwrap_or_default() + resolve_index(index, list.len() as u64).map(|resolved| list.element_ref(resolved as usize).clone()).unwrap_or_default() } +/// One subgraph invocation per content row, the row riding as a vararg, with +/// the subgraph's lanes concatenated into one flat level. The level reports a +/// lower bound; consumers drain to the past-end signal. #[node_macro::node(category("General"))] -fn map( - ctx: impl Ctx + DeriveCtx, - #[implementations( - List, - List, - List>, - List, - List, - List, - )] - content: List, - #[implementations( - Context -> List, - Context -> List, - Context -> List>, - Context -> List, - Context -> List, - Context -> List, - )] - mapped: impl Node, Output = List>, -) -> Result, Interrupt> { - let spilled = ctx.index_head(); - let mut rows = List::new(); - - for (i, row) in content.into_iter().enumerate() { - let item = List::new_from_item(row); +fn map( + ctx: impl Ctx + DeriveCtx + ExtractIndex + InjectIndex + Copy, + #[implementations(Graphic, Vector, Raster, Color, GradientStops, String)] content: IList, + mapped: impl Node, Output = IList>, +) -> Result, Interrupt> { + let mut remaining = ctx.innermost_index(); + for row in 0..content.len() { + let item = crate::record::vararg_row(content, row); let scoped = ctx.push_vararg(&item); - let list = mapped.eval(&scoped.ctx().promoted(&spilled, i as u64))?; - - rows.extend(list); + let lanes = mapped.inner_extent_at(&scoped.ctx(), row as u64)?; + if remaining >= lanes { + remaining -= lanes; + continue; + } + let mut frame = core_types::context::IndexLink { index: 0, outer: None }; + return mapped.eval(&scoped.ctx().push_level(&mut frame, row as u64, remaining)); } - - Ok(rows) + Err(GraphError::past_end().into()) } #[node_macro::node(category("General"))] diff --git a/node-graph/nodes/graphic/src/record.rs b/node-graph/nodes/graphic/src/record.rs index 3c6bc5d0c3..26aead193a 100644 --- a/node-graph/nodes/graphic/src/record.rs +++ b/node-graph/nodes/graphic/src/record.rs @@ -144,7 +144,7 @@ fn to_gradient(_: impl Ctx + ExtractIndex + InjectIndex + Copy, colors: IList(content: core_types::node::List<'_, Row>, row: usize) -> core_types::list::List { +pub(crate) fn vararg_row(content: core_types::node::List<'_, Row>, row: usize) -> core_types::list::List { core_types::list::List::new_from_element(content.element_ref(row).clone()) }