diff --git a/node-graph/libraries/core-types/src/context.rs b/node-graph/libraries/core-types/src/context.rs index 1631833148..88bb398239 100644 --- a/node-graph/libraries/core-types/src/context.rs +++ b/node-graph/libraries/core-types/src/context.rs @@ -899,6 +899,19 @@ pub trait ExtractArena { fn arena(&self) -> Self::ArenaRef; } +/// The arena borrowed at the caller's scope, for kernels whose node shape +/// cannot carry the evaluation lifetime (a flipped value node takes no fn +/// lifetime). +pub trait BorrowArena { + fn borrow_arena(&self) -> &crate::arena::Arena; +} + +impl BorrowArena for ContextImpl<'_> { + fn borrow_arena(&self) -> &crate::arena::Arena { + ExtractArena::arena(self) + } +} + pub trait CtxFamily { type Ctx<'s>: Ctx + DeriveCtx; } diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 8d2acd9e13..fb97a5a8dd 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -36,13 +36,6 @@ impl Default for Graphic { } } -// Graphic -impl From> for Graphic { - fn from(graphic: List) -> Self { - Graphic::Graphic(graphic) - } -} - /// A typed legacy list as a legacy graphic list: each item de-tables to a /// leaf element, keeping its attributes on the containing lane. fn detable_items(list: List, leaf: fn(T) -> Graphic) -> List { @@ -54,19 +47,66 @@ fn detable_items(list: List, leaf: fn(T) -> out } +/// The element-space coercion into `Graphic`: a leaf converts in place and a +/// legacy list becomes a native group built over the arena, so the coercion +/// never constructs a legacy interior. +pub trait IntoGraphicElement: Clone + Send + Sync + CacheHash + 'static { + /// `None` reports arena exhaustion. + fn into_graphic_element(self, arena: &core_types::arena::Arena) -> Option; +} + +fn list_group(list: List, arena: &core_types::arena::Arena) -> Option { + Some(Graphic::Group(core_types::record::Group { + row: None, + content: core_types::record::GroupItem::from_list(list, arena)?, + })) +} + +macro_rules! into_graphic_element { + ($($leaf:ident: $element:ty;)*) => { + $( + impl IntoGraphicElement for $element { + fn into_graphic_element(self, _arena: &core_types::arena::Arena) -> Option { + Some(Graphic::$leaf(self)) + } + } + + impl IntoGraphicElement for List<$element> { + fn into_graphic_element(self, arena: &core_types::arena::Arena) -> Option { + list_group(self, arena) + } + } + )* + }; +} + +into_graphic_element! { + Vector: Vector; + RasterCPU: Raster; + RasterGPU: Raster; + Color: Color; + Gradient: GradientStops; + Text: String; +} + +impl IntoGraphicElement for Graphic { + fn into_graphic_element(self, _arena: &core_types::arena::Arena) -> Option { + Some(self) + } +} + +impl IntoGraphicElement for List { + fn into_graphic_element(self, arena: &core_types::arena::Arena) -> Option { + list_group(self, arena) + } +} + // Vector impl From for Graphic { fn from(vector: Vector) -> Self { Graphic::Vector(vector) } } -impl From> for Graphic { - fn from(vector: List) -> Self { - Graphic::Graphic(detable_items(vector, Graphic::Vector)) - } -} - -// Note: List -> List conversion handled by blanket impl in gcore // Raster impl From> for Graphic { @@ -74,12 +114,6 @@ impl From> for Graphic { Graphic::RasterCPU(raster) } } -impl From>> for Graphic { - fn from(raster: List>) -> Self { - Graphic::Graphic(detable_items(raster, Graphic::RasterCPU)) - } -} -// Note: List conversions handled by blanket impl in gcore // Raster impl From> for Graphic { @@ -87,12 +121,6 @@ impl From> for Graphic { Graphic::RasterGPU(raster) } } -impl From>> for Graphic { - fn from(raster: List>) -> Self { - Graphic::Graphic(detable_items(raster, Graphic::RasterGPU)) - } -} -// Note: List conversions handled by blanket impl in gcore // Color impl From for Graphic { @@ -100,12 +128,6 @@ impl From for Graphic { Graphic::Color(color) } } -impl From> for Graphic { - fn from(color: List) -> Self { - Graphic::Graphic(detable_items(color, Graphic::Color)) - } -} -// Note: List conversions handled by blanket impl in gcore // Note: List -> Option is in gcore (Color is defined there) // GradientStops @@ -114,11 +136,6 @@ impl From for Graphic { Graphic::Gradient(gradient) } } -impl From> for Graphic { - fn from(gradient: List) -> Self { - Graphic::Graphic(detable_items(gradient, Graphic::Gradient)) - } -} // String impl From for Graphic { @@ -126,11 +143,6 @@ impl From for Graphic { Graphic::Text(text) } } -impl From> for Graphic { - fn from(text: List) -> Self { - Graphic::Graphic(detable_items(text, Graphic::Text)) - } -} /// Deeply flattens a `List`, collecting only elements matching a specific variant (extracted by `extract_variant`) /// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-`List`s composes transforms and opacity. @@ -172,6 +184,11 @@ fn flatten_graphic_list(content: List, extract_variant: fn(Graphic) flatten_recursive(output, sub_list, extract_variant, lane_layer_path.as_deref()); } + // A bridge row's native group flattens through its legacy lowering; the arm dies with the legacy interior. + Graphic::Group(group) => { + let lowered = List::new_from_item(Item::from_parts(group_to_legacy_graphic(&group), attributes.clone())); + flatten_recursive(output, lowered, extract_variant, parent_layer_path); + } // A de-tabled leaf is one attr-less element; the extracted row rides with its containing lane's full attributes, paint included. // The enclosing group lane's own layer path overrides, one hop only, matching the native walk. other => { @@ -1086,19 +1103,6 @@ pub fn flatten_vector_rows(level: GraphicLevel<'_>) -> List { out } -/// One typed run as the legacy list its `Render` impl consumes, nested -/// groups converted to their legacy form. -pub fn run_to_render_list(item: &core_types::record::GroupItem) -> Option> { - let mut list = run_to_legacy_list::(item)?; - if let Some(graphics) = (&mut list as &mut dyn std::any::Any).downcast_mut::>() { - for element in graphics.iter_element_values_mut() { - *element = map_groups_to_legacy(element); - } - push_lane_paint_into_interiors(graphics); - } - Some(list) -} - /// The transitional paint placement: a lane-level fill or stroke paint /// attribute moves onto the vector interiors the legacy paint readers /// inspect, reaching as far as the pre-flip broadcast did. diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 3cc2f99998..d76d3e54f7 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -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(content: core_types::node::List<'_, T>) -> List { // 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::(&item).expect("the run holds the row's element type") + graphic_types::graphic::run_to_list::(&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(_content: ListIn<'_, T>, _level: LevelIn) -> GPoll + Clone + Send + Sync + core_types::CacheHash + 'static>( - _: impl Ctx, +pub fn to_graphic( + ctx: impl Ctx + core_types::context::BorrowArena, #[implementations( Graphic, List, @@ -489,16 +489,16 @@ pub fn to_graphic + Clone + Send + Sync + core_types::CacheHash List, )] content: T, -) -> Graphic { - content.into() +) -> Result { + 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 + Clone + Send + Sync + core_types::CacheHash + 'static>( - _: impl Ctx, +pub fn to_graphic_element( + ctx: impl Ctx + core_types::context::BorrowArena, #[implementations( Graphic, Vector, @@ -516,8 +516,8 @@ pub fn to_graphic_element + Clone + Send + Sync + core_types::C List, )] content: T, -) -> Graphic { - content.into() +) -> Result { + 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( _: impl Ctx, @@ -559,7 +559,7 @@ pub fn level_to_list( ) -> List { // 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::(&item).expect("the run holds the row's element type") + graphic_types::graphic::run_to_list::(&item).expect("the run holds the row's element type") } pub use _level_to_list_mod::level_to_list_entries; diff --git a/node-graph/nodes/path-bool/src/lib.rs b/node-graph/nodes/path-bool/src/lib.rs index 11dc8094a2..efffaf0c97 100644 --- a/node-graph/nodes/path-bool/src/lib.rs +++ b/node-graph/nodes/path-bool/src/lib.rs @@ -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::(&item) + let snapshot = graphic_types::graphic::run_to_list::(&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::(&item).expect("the run holds vector lanes"); - let snapshot = graphic_types::graphic::run_to_render_list::(&item) + let snapshot = graphic_types::graphic::run_to_list::(&item) .expect("the run holds the row's element type") .into_graphic_list(); boolean_core(ctx.arena(), flattened, snapshot, operation) diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 9a14fc78b4..09aec587d8 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -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::(&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::(&item) + graphic_types::graphic::run_to_list::(&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::(&item).expect("the run holds the row's element type"); + let snapshot = graphic_types::graphic::run_to_list::(&item).expect("the run holds the row's element type"); flatten_path_core(ctx.arena(), flattened, snapshot) }