Swap the map and index family nodes to their leveled forms in place

This commit is contained in:
Dennis Kobert
2026-08-22 12:27:53 +00:00
parent 93e34d00e5
commit 56c772de4c
2 changed files with 87 additions and 104 deletions
+86 -103
View File
@@ -13,63 +13,81 @@ use raster_types::{CPU, GPU, Raster};
use vector_types::gradient::{GradientSpreadMethod, GradientType}; use vector_types::gradient::{GradientSpreadMethod, GradientType};
use vector_types::{GradientStop, GradientStops, ReferencePoint}; use vector_types::{GradientStop, GradientStops, ReferencePoint};
/// Returns the value at the specified index in the list. /// Resolves a signed index over `total` lanes: negatives count from the end,
/// If no value exists at that index, the type's default value is returned. /// out of range resolves to nothing.
#[node_macro::node(category("General"))] fn resolve_index(index: f64, total: u64) -> Option<u64> {
pub fn index_elements<T: graphic_types::graphic::AtIndex + Clone + Default>( let index = index as i64;
_: impl Ctx, 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<T>(
ctx: impl Ctx + ExtractIndex + InjectIndex + Copy,
/// The list of data. /// The list of data.
#[implementations( list: impl Node<Context<'_>, Output = T>,
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
List<String>,
List<f64>,
List<u8>,
List<NodeId>,
)]
list: 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. /// 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, index: SignedInteger,
) -> T::Output ) -> Result<T, Interrupt> {
where let total = match list.extent(ctx, Level::Total) {
T::Output: Clone + Default, GPoll::Final(Extent::Exactly(count)) => count as u64,
{ GPoll::Pending => return Err(Interrupt::Pending),
let index = index as i32; _ => 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<Extent> {
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. /// Returns the list with the element at the specified index removed.
/// If no value exists at that index, the list is returned unchanged. /// If no value exists at that index, the list is returned unchanged.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"), extent(omit_element_extent))]
pub fn omit_element<T: graphic_types::graphic::OmitIndex + Clone + Default>( pub fn omit_element<T>(
_: impl Ctx, ctx: impl Ctx + ExtractIndex + InjectIndex + Copy,
/// The list of data. /// The list of data.
#[implementations( list: impl Node<Context<'_>, Output = T>,
List<String>,
List<Artboard>,
List<Graphic>,
List<Vector>,
List<Raster<CPU>>,
List<Raster<GPU>>,
List<Color>,
List<GradientStops>,
)]
list: 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. /// 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, index: SignedInteger,
) -> T { ) -> Result<T, Interrupt> {
let index = index as i32; 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 { fn omit_element_extent(list: ExtentIn<'_>, index: ValueIn<'_, f64>, level: LevelIn) -> GPoll<Extent> {
list.omit_index_from_end(index.unsigned_abs() as usize) match level.top() {
} else { true => index.get().zip(list.at(level)).map(|(index, extent)| match extent {
list.omit_index(index as usize) 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<T: graphic_types::graphic::OmitIndex + Clone + Default>(
/// Use this when downstream nodes want just the inner value rather than a `List` containing a single item. /// 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. /// If no value exists at that index, the element type's default is returned.
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
pub fn extract_element<T: Clone + Default + Send + Sync + 'static>( pub fn extract_element<T: Clone + Default + Send + Sync + CacheHash + 'static>(
_: impl Ctx, _: impl Ctx + ExtractIndex + InjectIndex + Copy,
/// The `List` of data to extract from. /// The `List` of data to extract from.
#[implementations( #[implementations(String, f64, NodeId, Color, GradientStops, Vector, Raster<CPU>, Graphic, Artboard)] list: IList<T>,
List<String>,
List<f64>,
List<u8>,
List<NodeId>,
List<Color>,
List<GradientStops>,
List<Vector>,
List<Raster<CPU>>,
List<Graphic>,
List<Artboard>,
)]
list: List<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. /// 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, index: SignedInteger,
) -> T { ) -> T {
let len = list.len(); resolve_index(index, list.len() as u64).map(|resolved| list.element_ref(resolved as usize).clone()).unwrap_or_default()
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()
} }
/// 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"))] #[node_macro::node(category("General"))]
fn map<Item: AnyHash + Clone + Send + Sync + CacheHash>( fn map<Row: Clone + Send + Sync + CacheHash + 'static, T>(
ctx: impl Ctx + DeriveCtx, ctx: impl Ctx + DeriveCtx + ExtractIndex + InjectIndex + Copy,
#[implementations( #[implementations(Graphic, Vector, Raster<CPU>, Color, GradientStops, String)] content: IList<Row>,
List<Graphic>, mapped: impl Node<Context<'_>, Output = IList<T>>,
List<Vector>, ) -> Result<IList<T>, Interrupt> {
List<Raster<CPU>>, let mut remaining = ctx.innermost_index();
List<Color>, for row in 0..content.len() {
List<GradientStops>, let item = crate::record::vararg_row(content, row);
List<String>,
)]
content: List<Item>,
#[implementations(
Context -> List<Graphic>,
Context -> List<Vector>,
Context -> List<Raster<CPU>>,
Context -> List<Color>,
Context -> List<GradientStops>,
Context -> List<String>,
)]
mapped: impl Node<Context<'_>, Output = List<Item>>,
) -> Result<List<Item>, 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);
let scoped = ctx.push_vararg(&item); let scoped = ctx.push_vararg(&item);
let list = mapped.eval(&scoped.ctx().promoted(&spilled, i as u64))?; let lanes = mapped.inner_extent_at(&scoped.ctx(), row as u64)?;
if remaining >= lanes {
rows.extend(list); 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));
} }
Err(GraphError::past_end().into())
Ok(rows)
} }
#[node_macro::node(category("General"))] #[node_macro::node(category("General"))]
+1 -1
View File
@@ -144,7 +144,7 @@ fn to_gradient(_: impl Ctx + ExtractIndex + InjectIndex + Copy, colors: IList<Co
/// One content row as the production vararg shape: a single-item legacy list /// One content row as the production vararg shape: a single-item legacy list
/// carrying the row's element only, so the list's dyn-hash is a complete /// carrying the row's element only, so the list's dyn-hash is a complete
/// cache key over the observables. /// cache key over the observables.
fn vararg_row<Row: Clone + Send + Sync + 'static>(content: core_types::node::List<'_, Row>, row: usize) -> core_types::list::List<Row> { pub(crate) fn vararg_row<Row: Clone + Send + Sync + 'static>(content: core_types::node::List<'_, Row>, row: usize) -> core_types::list::List<Row> {
core_types::list::List::new_from_element(content.element_ref(row).clone()) core_types::list::List::new_from_element(content.element_ref(row).clone())
} }