Build native groups at the legacy conversion seams

This commit is contained in:
Dennis Kobert
2026-08-27 20:22:24 +00:00
parent 6f809fda73
commit e8aea0728a
5 changed files with 115 additions and 97 deletions

View File

@@ -242,11 +242,11 @@ where
))
}
/// The materialized level as its legacy render list.
/// The materialized level as its legacy list, content kept native.
fn legacy_render_list_of<T: Clone + Send + Sync + 'static>(content: core_types::node::List<'_, T>) -> List<T> {
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
graphic_types::graphic::run_to_list::<T>(&item).expect("the run holds the row's element type")
}
#[node_macro::node(category("General"), extent(mirror_extent))]
@@ -474,10 +474,10 @@ fn wrap_graphic_extent<T>(_content: ListIn<'_, T>, _level: LevelIn) -> GPoll<Ext
/// Converts graphical content into a `Graphic` level. A `Graphic` level passes through
/// unchanged; a typed level nests as one graphic lane, keeping the pre-flip list
/// collapse (`to_graphic_typed` serves those rows). The legacy list rows accept an
/// unconverted producer's list value as one element.
/// unconverted producer's list value as one element, built as a native group.
#[node_macro::node(category("General"))]
pub fn to_graphic<T: Into<Graphic> + Clone + Send + Sync + core_types::CacheHash + 'static>(
_: impl Ctx,
pub fn to_graphic<T: graphic_types::graphic::IntoGraphicElement>(
ctx: impl Ctx + core_types::context::BorrowArena,
#[implementations(
Graphic,
List<Graphic>,
@@ -489,16 +489,16 @@ pub fn to_graphic<T: Into<Graphic> + Clone + Send + Sync + core_types::CacheHash
List<String>,
)]
content: T,
) -> Graphic {
content.into()
) -> Result<Graphic, Interrupt> {
content.into_graphic_element(ctx.borrow_arena()).ok_or_else(|| GraphError::new("the arena is exhausted").into())
}
/// The elementwise `Graphic` coercion the compiler-inserted converts use: each
/// lane's element converts on its own, so a typed wire feeds a graphic input
/// without changing the level's shape. Registered under the convert identifier.
#[node_macro::node(category(""))]
pub fn to_graphic_element<T: Into<Graphic> + Clone + Send + Sync + core_types::CacheHash + 'static>(
_: impl Ctx,
pub fn to_graphic_element<T: graphic_types::graphic::IntoGraphicElement>(
ctx: impl Ctx + core_types::context::BorrowArena,
#[implementations(
Graphic,
Vector,
@@ -516,8 +516,8 @@ pub fn to_graphic_element<T: Into<Graphic> + Clone + Send + Sync + core_types::C
List<String>,
)]
content: T,
) -> Graphic {
content.into()
) -> Result<Graphic, Interrupt> {
content.into_graphic_element(ctx.borrow_arena()).ok_or_else(|| GraphError::new("the arena is exhausted").into())
}
/// The typed-level conversion: the whole level nests as one graphic lane, as
@@ -549,8 +549,8 @@ fn to_graphic_unit_extent(_content: core_types::extent::ValueIn<'_, ()>, _level:
/// The transitional level bridge: the wire's records as the legacy list an
/// unconverted consumer expects, attributes copied through their erased
/// reads. Registered under the legacy convert identifiers; the rows die with
/// the last legacy consumer.
/// reads and content kept in its native form. Registered under the legacy
/// convert identifiers; the rows die with the last legacy consumer.
#[node_macro::node(category(""))]
pub fn level_to_list<T: Clone + Send + Sync + CacheHash + 'static>(
_: impl Ctx,
@@ -559,7 +559,7 @@ pub fn level_to_list<T: Clone + Send + Sync + CacheHash + 'static>(
) -> List<T> {
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(value.batch()) };
graphic_types::graphic::run_to_render_list::<T>(&item).expect("the run holds the row's element type")
graphic_types::graphic::run_to_list::<T>(&item).expect("the run holds the row's element type")
}
pub use _level_to_list_mod::level_to_list_entries;

View File

@@ -128,7 +128,7 @@ fn boolean_operation<'e>(
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
let flattened = flatten_vector_run(GraphicLevel::Run(&item), DAffine2::IDENTITY, PaintReach::NONE);
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item)
let snapshot = graphic_types::graphic::run_to_list::<Graphic>(&item)
.expect("the run holds the row's element type")
.into_graphic_list();
boolean_core(ctx.arena(), flattened, snapshot, operation)
@@ -158,7 +158,7 @@ fn boolean_operation_vector<'e>(
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
let flattened = graphic_types::graphic::run_to_list::<Vector>(&item).expect("the run holds vector lanes");
let snapshot = graphic_types::graphic::run_to_render_list::<Vector>(&item)
let snapshot = graphic_types::graphic::run_to_list::<Vector>(&item)
.expect("the run holds the row's element type")
.into_graphic_list();
boolean_core(ctx.arena(), flattened, snapshot, operation)

View File

@@ -178,11 +178,11 @@ fn assign_colors_graphic<'e>(
if lane >= content.len() {
return Err(GraphError::past_end().into());
}
let mut element = graphic_types::graphic::map_groups_to_legacy(content.element_ref(lane));
let original = content.element_ref(lane);
let (transform, layer_path) = carried_lane_attrs(ctx.arena(), *content.lane(lane))?;
if gradient.is_empty() {
return Ok((element, transform, layer_path));
return Ok((original.clone(), transform, layer_path));
}
let gradient_element = gradient.element_ref(0);
let reversed;
@@ -218,30 +218,31 @@ fn assign_colors_graphic<'e>(
(entry.offsets[content.len()], entry.offsets[lane])
};
// A de-tabled vector leaf carries no attributes, so the paint rides the
// containing lane: a bare leaf wraps into a one-lane list, and a lowered
// vector run's leaves take one color per lane.
if graphic_types::graphic::direct_vector_len(content.element_ref(lane)) > 0 {
let mut children = match element {
Graphic::Graphic(children) => children,
leaf => List::new_from_element(leaf),
};
let mut consumed = 0;
for index in 0..children.len() {
let Some(Graphic::Vector(vector)) = children.element(index) else { continue };
let has_stroke = vector.stroke.is_some();
let color = assign_color_at(gradient_element, position + consumed, length, randomize, seed, repeat_every);
let paint = List::new_from_element(color).into_graphic_list();
if fill {
set_paint_attribute_at(&mut children, index, ATTR_FILL, paint.clone());
// The direct vector rows as a scratch list, one color per row, rebuilt as
// a native run; a lane without direct rows passes through untouched.
let rows = match original {
Graphic::Vector(vector) => Some(List::new_from_element(vector.clone())),
Graphic::Group(group) if group.row.is_none() => graphic_types::graphic::run_to_list::<Vector>(&group.content),
_ => None,
};
let element = match rows {
Some(mut rows) => {
for row in 0..rows.len() {
let has_stroke = rows.element(row).is_some_and(|vector| vector.stroke.is_some());
let color = assign_color_at(gradient_element, position + row, length, randomize, seed, repeat_every);
let paint = List::new_from_element(color).into_graphic_list();
if fill {
set_paint_attribute_at(&mut rows, row, ATTR_FILL, paint.clone());
}
if stroke && has_stroke {
set_paint_attribute_at(&mut rows, row, ATTR_STROKE, paint.clone());
}
}
if stroke && has_stroke {
set_paint_attribute_at(&mut children, index, ATTR_STROKE, paint.clone());
}
consumed += 1;
let content = core_types::record::GroupItem::from_list(rows, ctx.arena()).ok_or_else(|| Interrupt::from(GraphError::new("the arena is exhausted")))?;
Graphic::Group(core_types::record::Group { row: None, content })
}
element = Graphic::Graphic(children);
}
None => original.clone(),
};
Ok((element, transform, layer_path))
}
@@ -1596,7 +1597,7 @@ where
{
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
graphic_types::graphic::run_to_render_list::<T>(&item)
graphic_types::graphic::run_to_list::<T>(&item)
.expect("the run holds the row's element type")
.into_graphic_list()
}
@@ -1905,7 +1906,7 @@ pub fn flatten_path<'e>(
// SAFETY: a materialized input's frames are arena-resident.
let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) };
let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item));
let snapshot = graphic_types::graphic::run_to_render_list::<Graphic>(&item).expect("the run holds the row's element type");
let snapshot = graphic_types::graphic::run_to_list::<Graphic>(&item).expect("the run holds the row's element type");
flatten_path_core(ctx.arena(), flattened, snapshot)
}