Add leveled vararg sources beside the production read nodes

This commit is contained in:
Dennis Kobert
2026-08-21 14:38:26 +00:00
parent 7e24f810c6
commit 74fddff589
2 changed files with 105 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
use core_types::gpoll::{Extent, GPoll, GraphError, Interrupt};
use core_types::list::List;
use core_types::{Color, ExtractVarArgs};
use core_types::{Ctx, ExtractIndex, ExtractPosition};
@@ -46,6 +47,78 @@ fn read_gradient(ctx: impl Ctx + ExtractVarArgs) -> List<GradientStops> {
var_arg.downcast_ref().cloned().unwrap_or_default()
}
/// The mapped row riding as vararg 0, in the production single-item shape.
fn vararg_list<'a, T: 'static>(ctx: &'a impl ExtractVarArgs) -> Option<&'a List<T>> {
let arg = ctx.vararg(0).ok()?;
(arg as &dyn std::any::Any).downcast_ref::<List<T>>()
}
/// Lanes of a leveled vararg source: one per item, none without a row,
/// matching the legacy empty-list return.
fn vararg_lanes<T: 'static>(ctx: &impl ExtractVarArgs, level: u8) -> GPoll<Extent> {
match level {
0 => GPoll::Final(Extent::Exactly(vararg_list::<T>(ctx).map_or(0, List::len))),
_ => GPoll::Final(Extent::Exactly(1)),
}
}
fn vararg_element<T: Clone + 'static>(ctx: &(impl ExtractVarArgs + ExtractIndex)) -> Result<T, Interrupt> {
vararg_list::<T>(ctx)
.and_then(|list| list.element(ctx.innermost_index() as usize))
.cloned()
.ok_or_else(|| GraphError::new("vararg row addressed past its items").into())
}
/// Rank-model vararg source: the mapped row's items as lanes, elements only.
#[node_macro::node(category("Test"), extent_raw(read_graphic_row_extent))]
pub fn read_graphic_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<Graphic>, Interrupt> {
vararg_element(ctx)
}
fn read_graphic_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadGraphicRowNode, ctx: &C, level: u8) -> GPoll<Extent> {
vararg_lanes::<Graphic>(ctx, level)
}
/// Rank-model vararg source: the mapped row's items as lanes, elements only.
#[node_macro::node(category("Test"), extent_raw(read_vector_row_extent))]
pub fn read_vector_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<Vector>, Interrupt> {
vararg_element(ctx)
}
fn read_vector_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadVectorRowNode, ctx: &C, level: u8) -> GPoll<Extent> {
vararg_lanes::<Vector>(ctx, level)
}
/// Rank-model vararg source: the mapped row's items as lanes, elements only.
#[node_macro::node(category("Test"), extent_raw(read_raster_row_extent))]
pub fn read_raster_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<Raster<CPU>>, Interrupt> {
vararg_element(ctx)
}
fn read_raster_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadRasterRowNode, ctx: &C, level: u8) -> GPoll<Extent> {
vararg_lanes::<Raster<CPU>>(ctx, level)
}
/// Rank-model vararg source: the mapped row's items as lanes, elements only.
#[node_macro::node(category("Test"), extent_raw(read_color_row_extent))]
pub fn read_color_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<Color>, Interrupt> {
vararg_element(ctx)
}
fn read_color_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadColorRowNode, ctx: &C, level: u8) -> GPoll<Extent> {
vararg_lanes::<Color>(ctx, level)
}
/// Rank-model vararg source: the mapped row's items as lanes, elements only.
#[node_macro::node(category("Test"), extent_raw(read_gradient_row_extent))]
pub fn read_gradient_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result<IList<GradientStops>, Interrupt> {
vararg_element(ctx)
}
fn read_gradient_row_extent<C: Ctx + ExtractVarArgs>(_: &ReadGradientRowNode, ctx: &C, level: u8) -> GPoll<Extent> {
vararg_lanes::<GradientStops>(ctx, level)
}
#[node_macro::node(category("Context"), path(core_types::vector))]
fn read_position(
ctx: impl Ctx + ExtractPosition,

View File

@@ -1518,6 +1518,38 @@ mod tests {
assert_eq!(unsafe { out.rec(&value).element::<f64>() }, 42.);
}
#[test]
fn read_row_exposes_the_vararg_items_as_lanes() {
use core_types::Color;
let arena = Arena::new(1024).unwrap();
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let node = install(crate::context::ReadColorRowNode::new(), crate::context::read_color_row_layout_meta(), &[]);
let out = Node::<ContextImpl>::layout(&node).clone();
assert_eq!(out.depth, 1);
// No row pushed: an empty level, matching the legacy empty-list return.
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(0)));
let mut item = core_types::list::List::new_from_element(Color::BLACK);
item.push(core_types::list::Item::new_from_element(Color::WHITE));
let scoped = ctx.push_vararg(&item);
let base = scoped.ctx();
assert_eq!(node.extent_at(&base, 0), GPoll::Final(Extent::Exactly(2)));
let head = base.index_head();
for (lane, expected) in [(0u64, Color::BLACK), (1, Color::WHITE)] {
let mark = stack::sp();
let GPoll::Final(value) = node.eval(&base.promoted(&head, lane)) else {
panic!("expected a final record at lane {lane}");
};
assert_eq!(unsafe { out.rec(&value).element::<Color>() }, expected, "lane {lane}");
unsafe { stack::rewind(mark) };
}
}
#[test]
fn reducer_drains_a_lower_bound_level() {
let arena = Arena::new(1 << 16).unwrap();