From e5b910f840fca71055737c383ee62ddcf48f2221 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Fri, 28 Aug 2026 13:30:06 +0000 Subject: [PATCH] Adopt serving lifetimes and the safe record surfaces in the node crates --- node-graph/nodes/gcore/src/context.rs | 4 +- node-graph/nodes/graphic/src/artboard.rs | 9 +-- node-graph/nodes/graphic/src/graphic.rs | 55 +++++++------ node-graph/nodes/graphic/src/record.rs | 23 +++--- node-graph/nodes/gstd/src/render_node.rs | 7 +- node-graph/nodes/path-bool/src/lib.rs | 21 +++-- node-graph/nodes/vector/src/vector_nodes.rs | 89 ++++++++++----------- 7 files changed, 98 insertions(+), 110 deletions(-) diff --git a/node-graph/nodes/gcore/src/context.rs b/node-graph/nodes/gcore/src/context.rs index 27b338ff50..b15dcd6d91 100644 --- a/node-graph/nodes/gcore/src/context.rs +++ b/node-graph/nodes/gcore/src/context.rs @@ -8,7 +8,7 @@ use graphic_types::{Graphic, Vector}; use raster_types::{CPU, Raster}; #[node_macro::node(category("Context"), path(graphene_core::vector))] -fn read_graphic(ctx: impl Ctx + ExtractVarArgs) -> List { +fn read_graphic(ctx: impl Ctx + ExtractVarArgs) -> List> { let Ok(var_arg) = ctx.vararg(0) else { return Default::default() }; let var_arg = var_arg as &dyn std::any::Any; @@ -71,7 +71,7 @@ fn vararg_element(ctx: &(impl ExtractVarArgs + ExtractIndex) /// 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, Interrupt> { +pub fn read_graphic_row(ctx: impl Ctx + ExtractVarArgs + ExtractIndex) -> Result>, Interrupt> { vararg_element(ctx) } diff --git a/node-graph/nodes/graphic/src/artboard.rs b/node-graph/nodes/graphic/src/artboard.rs index 0d3c83fce4..225a8e2955 100644 --- a/node-graph/nodes/graphic/src/artboard.rs +++ b/node-graph/nodes/graphic/src/artboard.rs @@ -21,10 +21,10 @@ fn translate_footprint_extent(content: ExtentIn<'_>, _offset: ValueIn<'_, DVec2> /// Constructs an artboard element with the given content and metadata stored as attributes. #[node_macro::node(category(""))] -pub fn create_artboard( +pub fn create_artboard<'e>( _: impl Ctx, /// Graphics to include within the artboard. - content: IList, + content: IList>, /// Coordinate of the top-left corner of the artboard within the document. location: DVec2, /// Width and height of the artboard within the document. @@ -34,9 +34,8 @@ pub fn create_artboard( /// Whether to cut off the contained content that extends outside the artboard, or keep it visible. #[default(true)] clip: bool, -) -> (Artboard, Attr, Attr, Attr, Attr) { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; +) -> (Artboard<'e>, Attr, Attr, Attr, Attr) { + let item = content.as_group_item(); let content = core_types::list::List::new_from_element(Graphic::Group(core_types::record::Group { row: None, content: item, diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index d76d3e54f7..9682fc5251 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -212,7 +212,7 @@ where trace: Vec::new(), }) }; - let park_paint = |paint: Option>| -> Result>, Interrupt> { + let park_paint = |paint: Option>>| -> Result>>, Interrupt> { match paint { Some(paint) => Ok(Some(arena.alloc(paint).ok_or_else(exhausted)?.0)), None => Ok(None), @@ -243,16 +243,18 @@ where } /// 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_list::(&item).expect("the run holds the row's element type") +fn legacy_render_list_of(content: core_types::node::List<'_, T>) -> List +where + T::Static: Clone + Send + Sync + dyn_any::StaticTypeSized, +{ + let item = content.as_group_item(); + graphic_types::graphic::run_to_list::(&item).expect("the run holds the row's element type") } #[node_macro::node(category("General"), extent(mirror_extent))] fn mirror<'e>( ctx: impl Ctx + core_types::context::ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, - content: IList, + content: IList>, #[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint, #[unit(" px")] offset: f64, #[range] @@ -261,7 +263,7 @@ fn mirror<'e>( #[default(true)] keep_original: bool, ) -> Result< IList<( - Graphic, + Graphic<'static>, Attr<'e, TransformAttr>, Attr<'e, graphic_types::markers::Fill>, Attr<'e, graphic_types::markers::Stroke>, @@ -454,12 +456,11 @@ pub fn legacy_layer_extend( /// lower a wrapped vector level to the bare typed graphic the pre-flip wrap made. /// The inverse of this node is 'Flatten Graphic'. #[node_macro::node(category("General"), extent(wrap_graphic_extent))] -pub fn wrap_graphic( +pub fn wrap_graphic<'e, T: Clone + Send + Sync + core_types::CacheHash + 'static>( _: impl Ctx, #[implementations(Graphic, Vector, Raster, Raster, Color, GradientStops, String)] content: IList, -) -> Result, Interrupt> { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; +) -> Result>, Interrupt> { + let item = content.as_group_item(); Ok(Graphic::Group(core_types::record::Group { row: None, content: item, @@ -476,8 +477,8 @@ fn wrap_graphic_extent(_content: ListIn<'_, T>, _level: LevelIn) -> GPoll( - ctx: impl Ctx + core_types::context::BorrowArena, +pub fn to_graphic<'e, T: graphic_types::graphic::IntoGraphicElement>( + ctx: impl Ctx + core_types::context::ExtractArena<'e>, #[implementations( Graphic, List, @@ -489,16 +490,16 @@ pub fn to_graphic( List, )] content: T, -) -> Result { - content.into_graphic_element(ctx.borrow_arena()).ok_or_else(|| GraphError::new("the arena is exhausted").into()) +) -> Result, Interrupt> { + content.into_graphic_element(ctx.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( - ctx: impl Ctx + core_types::context::BorrowArena, +pub fn to_graphic_element<'e, T: graphic_types::graphic::IntoGraphicElement>( + ctx: impl Ctx + core_types::context::ExtractArena<'e>, #[implementations( Graphic, Vector, @@ -516,20 +517,19 @@ pub fn to_graphic_element( List, )] content: T, -) -> Result { - content.into_graphic_element(ctx.borrow_arena()).ok_or_else(|| GraphError::new("the arena is exhausted").into()) +) -> Result, Interrupt> { + content.into_graphic_element(ctx.arena()).ok_or_else(|| GraphError::new("the arena is exhausted").into()) } /// The typed-level conversion: the whole level nests as one graphic lane, as /// the pre-flip `Into` list collapse did. Registered under the to /// graphic identifier. #[node_macro::node(category(""), extent(wrap_graphic_extent))] -pub fn to_graphic_typed( +pub fn to_graphic_typed<'e, T: Clone + Send + Sync + core_types::CacheHash + 'static>( _: impl Ctx, #[implementations(Vector, Raster, Raster, Color, GradientStops, String)] content: IList, -) -> Result, Interrupt> { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; +) -> Result>, Interrupt> { + let item = content.as_group_item(); Ok(Graphic::Group(core_types::record::Group { row: None, content: item, @@ -539,7 +539,7 @@ pub fn to_graphic_typed Result, Interrupt> { +pub fn to_graphic_unit(_: impl Ctx, _content: ()) -> Result>, Interrupt> { Err(core_types::gpoll::GraphError::past_end().into()) } @@ -552,13 +552,12 @@ fn to_graphic_unit_extent(_content: core_types::extent::ValueIn<'_, ()>, _level: /// 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( +pub fn level_to_list( _: impl Ctx, #[implementations(Graphic, Vector, Raster, Raster, Color, GradientStops, String)] value: IList, _converter: (), ) -> List { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(value.batch()) }; + let item = value.as_group_item(); graphic_types::graphic::run_to_list::(&item).expect("the run holds the row's element type") } @@ -569,7 +568,7 @@ pub use _to_graphic_unit_mod::to_graphic_unit_entries; /// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled. #[node_macro::node(category("General"), extent(flatten_graphic_extent))] -pub fn flatten_graphic(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList, fully_flatten: bool) -> Result)>, Interrupt> { +pub fn flatten_graphic(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList>, fully_flatten: bool) -> Result, Attr)>, Interrupt> { let mut remaining = ctx.index() as usize; for row in 0..content.len() { let graphic = content.element_ref(row); diff --git a/node-graph/nodes/graphic/src/record.rs b/node-graph/nodes/graphic/src/record.rs index 8ebe033e47..57a45e05b6 100644 --- a/node-graph/nodes/graphic/src/record.rs +++ b/node-graph/nodes/graphic/src/record.rs @@ -24,7 +24,7 @@ pub(crate) fn group_leaf_count(group: &core_types::record::Group, fully_flatten: (0..lanes.len()).map(|lane| leaf_count(lanes.element_ref(lane), fully_flatten, depth + 1)).sum() } -pub(crate) fn group_locate(group: &core_types::record::Group, transform: DAffine2, fully_flatten: bool, depth: usize, remaining: &mut usize) -> Option<(Graphic, DAffine2)> { +pub(crate) fn group_locate<'e>(group: &core_types::record::Group<'e>, transform: DAffine2, fully_flatten: bool, depth: usize, remaining: &mut usize) -> Option<(Graphic<'e>, DAffine2)> { let item = &group.content; let lanes = item.typed_lanes::().expect("guarded by group_expands"); let offset = item.layout().offset_of(ATTR_TRANSFORM, 0); @@ -50,7 +50,7 @@ pub(crate) fn leaf_count(graphic: &Graphic, fully_flatten: bool, depth: usize) - /// The `remaining`-th leaf of `graphic` in walk order, with the transforms /// along its path composed onto `transform`. -pub(crate) fn locate(graphic: &Graphic, transform: DAffine2, fully_flatten: bool, depth: usize, remaining: &mut usize) -> Option<(Graphic, DAffine2)> { +pub(crate) fn locate<'e>(graphic: &Graphic<'e>, transform: DAffine2, fully_flatten: bool, depth: usize, remaining: &mut usize) -> Option<(Graphic<'e>, DAffine2)> { match graphic { Graphic::Graphic(children) if fully_flatten || depth == 0 => (0..children.len()).find_map(|index| { let child = children.element(index)?; @@ -70,7 +70,7 @@ pub(crate) fn locate(graphic: &Graphic, transform: DAffine2, fully_flatten: bool /// the transforms along its path composed; a group beyond the walk's depth /// rides as a leaf with its embedded transforms untouched. #[node_macro::node(category("Test"), extent(flatten_extent))] -fn flatten(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList, fully_flatten: bool) -> Result)>, Interrupt> { +fn flatten(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList>, fully_flatten: bool) -> Result, Attr)>, Interrupt> { let mut remaining = ctx.index() as usize; for row in 0..content.len() { let graphic = content.element_ref(row); @@ -101,9 +101,8 @@ fn flatten_extent(content: ListIn<'_, Graphic>, fully_flatten: ValueIn<'_, bool> /// Rank-model Wrap: the content level as one group element on a one-lane /// level, the inverse of flatten's one-level descent. #[node_macro::node(category("Test"), extent(wrap_extent))] -fn wrap(_: impl Ctx, content: IList) -> Result, Interrupt> { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; +fn wrap<'e>(_: impl Ctx, content: IList>) -> Result>, Interrupt> { + let item = content.as_group_item(); Ok(Graphic::Group(core_types::record::Group { row: None, content: item, @@ -240,7 +239,7 @@ mod tests { struct GraphicSource { layout: Layout, - rows: Vec<(Graphic, DAffine2)>, + rows: Vec<(Graphic<'static>, DAffine2)>, } impl<'e> Node> for GraphicSource { @@ -295,11 +294,11 @@ mod tests { Layout::default().with_writes(1, record::element_write_hashed::(), &[record::FieldWrite::of::(0)]) } - fn text(label: &str) -> Graphic { + fn text(label: &str) -> Graphic<'static> { Graphic::Text(label.to_string()) } - fn group(children: Vec<(Graphic, DAffine2)>) -> Graphic { + fn group(children: Vec<(Graphic<'static>, DAffine2)>) -> Graphic { let mut list = List::new(); for (index, (child, transform)) in children.into_iter().enumerate() { list.push(Item::new_from_element(child)); @@ -308,7 +307,7 @@ mod tests { Graphic::Graphic(list) } - fn text_of(graphic: &Graphic) -> &str { + fn text_of<'a>(graphic: &'a Graphic<'_>) -> &'a str { let Graphic::Text(text) = graphic else { panic!("expected a text leaf, got {graphic:?}"); }; @@ -321,7 +320,7 @@ mod tests { /// [a, G[b, H[c]]] with translations picked so each composed path is a /// distinct sum. - fn fixture_rows() -> Vec<(Graphic, DAffine2)> { + fn fixture_rows() -> Vec<(Graphic<'static>, DAffine2)> { vec![ (text("a"), translation(1.)), ( @@ -395,7 +394,7 @@ mod tests { } } - fn ragged_rows() -> Vec<(Graphic, DAffine2)> { + fn ragged_rows() -> Vec<(Graphic<'static>, DAffine2)> { vec![(text("ab"), translation(10.)), (text("xyz"), translation(20.))] } diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 32e451aeaa..d4c4b503fa 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -52,7 +52,7 @@ fn intermediate_of(data: &R, render_params: &RenderParams) -> RenderI } #[node_macro::node(category(""))] -fn render_intermediate( +fn render_intermediate( ctx: impl Ctx + ExtractVarArgs + DeriveCtx, #[implementations( Context -> List, @@ -78,15 +78,14 @@ fn render_intermediate( /// The leveled form of `render_intermediate`: the wire's records materialize /// into a run, which renders directly. #[node_macro::node(category(""))] -fn render_intermediate_leveled( +fn render_intermediate_leveled( ctx: impl Ctx + ExtractVarArgs + ExtractIndex + InjectIndex + Copy, #[implementations(Artboard, Graphic, Vector, Raster, Color, GradientStops, String)] data: IList, ) -> Result where for<'a> core_types::record::RunView<'a, T>: Render, { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(data.batch()) }; + let item = data.as_group_item(); let run = core_types::record::RunView::::new(&item).expect("the run holds the row's element type"); let render_params = ctx .vararg(0) diff --git a/node-graph/nodes/path-bool/src/lib.rs b/node-graph/nodes/path-bool/src/lib.rs index 6ace3f5f16..06e96d24c1 100644 --- a/node-graph/nodes/path-bool/src/lib.rs +++ b/node-graph/nodes/path-bool/src/lib.rs @@ -3,7 +3,7 @@ use core_types::list::{Item, List}; use core_types::uuid::NodeId; use core_types::{ATTR_BLEND_MODE, ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, BlendMode, Color, Ctx}; use glam::{DAffine2, DVec2}; -use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, set_paint_attribute, set_paint_attribute_at}; +use graphic_types::graphic::{GraphicLevel, PaintColumns, PaintReach, bake_paint_transforms, is_paint_present, set_paint_attribute, set_paint_attribute_at}; use graphic_types::raster_types::{CPU, GPU, Raster}; use graphic_types::vector_types::GradientStops; use graphic_types::markers::{EditorMergedLayers, Fill, Stroke}; @@ -27,7 +27,7 @@ pub use vector_types::vector::misc::BooleanOperation; fn boolean_core<'e>( arena: &'e core_types::arena::Arena, flattened: List, - snapshot: List, + snapshot: List>, operation: BooleanOperation, ) -> Result< ( @@ -67,7 +67,7 @@ fn boolean_core<'e>( trace: Vec::new(), }) }; - let park_paint = |paint: Option>| -> Result>, core_types::gpoll::Interrupt> { + let park_paint = |paint: Option>>| -> Result>, core_types::gpoll::Interrupt> { match paint { Some(list) => Ok(Some(arena.alloc(list).ok_or_else(exhausted)?.0)), None => Ok(None), @@ -75,8 +75,9 @@ fn boolean_core<'e>( }; let element = result_vector_list.element(0).cloned().unwrap_or_default(); - let fill = park_paint(graphic_types::graphic::paint_graphics::(&result_vector_list, 0).cloned())?; - let stroke = park_paint(graphic_types::graphic::paint_graphics::(&result_vector_list, 0).cloned())?; + use core_types::lane::LaneSource; + let fill = park_paint(result_vector_list.attr::(0).filter(|paint| is_paint_present(paint)).cloned())?; + let stroke = park_paint(result_vector_list.attr::(0).filter(|paint| is_paint_present(paint)).cloned())?; let layer_path: Vec = result_vector_list.attribute::>(ATTR_EDITOR_LAYER_PATH, 0).map(|path| path.clone()).unwrap_or_default(); let layer_path = arena.alloc(layer_path).ok_or_else(exhausted)?.0; // Snapshot the input layers so the renderer can recurse into them for @@ -102,7 +103,7 @@ fn boolean_core<'e>( fn boolean_operation<'e>( ctx: impl Ctx + ExtractArena<'e> + core_types::InjectIndex + Copy, /// The wire of vector paths to perform the boolean operation on. Nested groups are automatically flattened. - content: IList, + content: IList>, /// Which boolean operation to perform on the paths. /// /// Union combines all paths while cutting out overlapping areas (even the interiors of a single path). @@ -125,8 +126,7 @@ fn boolean_operation<'e>( ), core_types::gpoll::Interrupt, > { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; + let item = content.as_group_item(); let flattened = flatten_vector_run(GraphicLevel::Run(&item), DAffine2::IDENTITY, PaintReach::NONE); let snapshot = graphic_types::graphic::run_to_list::(&item) .expect("the run holds the row's element type") @@ -155,8 +155,7 @@ fn boolean_operation_vector<'e>( ), core_types::gpoll::Interrupt, > { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; + let item = content.as_group_item(); let flattened = graphic_types::graphic::run_to_list::(&item).expect("the run holds vector lanes"); let snapshot = graphic_types::graphic::run_to_list::(&item) .expect("the run holds the row's element type") @@ -555,7 +554,7 @@ mod tests { Vector::from_subpath(Subpath::::new_rectangle(corner, corner + DVec2::ONE)) } - fn black_paint() -> List { + fn black_paint() -> List> { List::new_from_element(Graphic::Color(Color::BLACK)) } diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 09aec587d8..38e76c8678 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -106,7 +106,7 @@ fn assign_colors<'e>( return Err(GraphError::past_end().into()); } let element = content.element_ref(lane).clone(); - let park_existing = |paint: Option<&List>| -> Result>, Interrupt> { paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose() }; + let park_existing = |paint: Option<&List>>| -> Result>, Interrupt> { paint.map(|paint| park_paint(ctx.arena(), paint.clone())).transpose() }; let existing_fill = park_existing(content.lane(lane).attr::())?; let existing_stroke = park_existing(content.lane(lane).attr::())?; let carried = carried_lane_attrs(ctx.arena(), *content.lane(lane))?; @@ -164,7 +164,7 @@ fn assign_colors_extent( #[node_macro::node(category(""), extent(assign_colors_graphic_extent))] fn assign_colors_graphic<'e>( ctx: impl Ctx + CacheHash + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, - content: IList, + content: IList>, #[data] lane_offsets: std::sync::Arc>>, #[default(true)] fill: bool, stroke: bool, @@ -173,7 +173,7 @@ fn assign_colors_graphic<'e>( randomize: bool, seed: SeedValue, repeat_every: u32, -) -> Result, Attr<'e, EditorLayerPath>)>, Interrupt> { +) -> Result, Attr<'e, TransformAttr>, Attr<'e, EditorLayerPath>)>, Interrupt> { let lane = ctx.index() as usize; if lane >= content.len() { return Err(GraphError::past_end().into()); @@ -276,7 +276,7 @@ fn assign_colors_graphic_extent( pub use _assign_colors_graphic_mod::assign_colors_graphic_entries; -fn park_paint(arena: &core_types::arena::Arena, paint: List) -> Result<&List, Interrupt> { +fn park_paint<'e>(arena: &'e core_types::arena::Arena, paint: List>) -> Result<&'e List>, Interrupt> { let (parked, _) = arena.alloc(paint).ok_or(GraphError { kind: core_types::gpoll::ErrorKind::ArenaExhausted, trace: Vec::new(), @@ -319,9 +319,8 @@ fn default_gradient_paint(paint: &mut List, bounds: Option<[DVec2; 2]>, /// The materialized paint level as the canonical owned paint list, content /// kept in its native form. -fn paint_table(paint: core_types::node::List<'_, Graphic>) -> List { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(paint.batch()) }; +fn paint_table(paint: core_types::node::List<'_, Graphic<'_>>) -> List> { + let item = paint.as_group_item(); graphic_types::graphic::run_to_list::(&item).expect("a paint level holds graphic lanes") } @@ -331,10 +330,10 @@ fn paint_table(paint: core_types::node::List<'_, Graphic>) -> List { fn fill<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, /// The content with vector paths to apply the fill style to. - (element, _content_fill): (Vector, Attr<'e, Fill>), + (element, _content_fill): (Vector, Attr), /// The fill to paint the path with. #[default(Color::BLACK)] - fill: IList, + fill: IList>, _backup_color: IList, _backup_gradient: IList, _gradient_type: GradientType, @@ -353,14 +352,14 @@ fn fill<'e>( #[node_macro::node(category(""))] fn fill_graphic_leveled<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, - (element, _content_fill): (Graphic, Attr<'e, Fill>), - #[default(Color::BLACK)] fill: IList, + (element, _content_fill): (Graphic<'static>, Attr), + #[default(Color::BLACK)] fill: IList>, _backup_color: IList, _backup_gradient: IList, _gradient_type: GradientType, _spread_method: GradientSpreadMethod, _transform: Option, -) -> Result<(Graphic, Attr<'e, Fill>), Interrupt> { +) -> Result<(Graphic<'static>, Attr<'e, Fill>), Interrupt> { let bounds = match BoundingBox::bounding_box(&element, DAffine2::IDENTITY, false) { RenderBoundingBox::Rectangle(bounds) => Some(bounds), _ => None, @@ -379,7 +378,7 @@ fn stroke<'e>( (element, content_transform): (Vector, Attr), /// The stroke paint. #[default(Color::BLACK)] - paint: IList, + paint: IList>, /// The stroke thickness. #[unit(" px")] #[default(2.)] @@ -446,8 +445,8 @@ fn for_each_interior_vector_mut(element: &mut Graphic, mut f: impl FnMut(&mut Ve #[node_macro::node(category(""))] fn stroke_graphic_leveled<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, - (element, content_transform): (Graphic, Attr), - #[default(Color::BLACK)] paint: IList, + (element, content_transform): (Graphic<'static>, Attr), + #[default(Color::BLACK)] paint: IList>, #[unit(" px")] #[default(2.)] weight: f64, @@ -458,7 +457,7 @@ fn stroke_graphic_leveled<'e>( paint_order: PaintOrder, dash_lengths: IList, #[unit(" px")] dash_offset: f64, -) -> Result<(Graphic, Attr, Attr<'e, StrokeAttr>), Interrupt> { +) -> Result<(Graphic<'static>, Attr, Attr<'e, StrokeAttr>), Interrupt> { let dash_lengths = (0..dash_lengths.len()).map(|index| dash_lengths.get(index).max(0.)).collect(); let stroke = Stroke { weight, @@ -1453,7 +1452,7 @@ fn solidify_rows(flattened: List) -> List { fn solidify_native_lane<'e>( arena: &'e core_types::arena::Arena, level: graphic_types::graphic::GraphicLevel<'_>, - snapshot: impl FnOnce() -> List, + snapshot: impl FnOnce() -> List>, lane: usize, ) -> Result< ( @@ -1575,9 +1574,8 @@ fn emit_legacy_lane<'e>( /// The wrap the legacy list collapse applied to a vector level: the run as /// one group lane, lane 0's layer path stamped on the wrapper. -fn wrap_vector_level(content: core_types::node::List<'_, Vector>) -> List { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; +fn wrap_vector_level(content: core_types::node::List<'_, Vector>) -> List> { + let item = content.as_group_item(); let layer_path: Vec = match content.len() > 0 { true => content.lane(0).attr::().to_vec(), false => Vec::new(), @@ -1591,13 +1589,13 @@ fn wrap_vector_level(content: core_types::node::List<'_, Vector>) -> List(content: core_types::node::List<'_, T>) -> List +fn legacy_graphic_list_of(content: core_types::node::List<'_, T>) -> List> where - List: IntoGraphicList, + T::Static: Clone + Send + Sync + dyn_any::StaticTypeSized, + List: IntoGraphicList, { - // 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_list::(&item) + let item = content.as_group_item(); + graphic_types::graphic::run_to_list::(&item) .expect("the run holds the row's element type") .into_graphic_list() } @@ -1605,7 +1603,7 @@ where #[node_macro::node(category("Vector: Modifier"), path(core_types::vector), extent(solidify_stroke_extent))] fn solidify_stroke<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, - content: IList, + content: IList>, ) -> Result< IList<( Vector, @@ -1621,8 +1619,7 @@ fn solidify_stroke<'e>( )>, Interrupt, > { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; + let item = content.as_group_item(); solidify_native_lane(ctx.arena(), graphic_types::graphic::GraphicLevel::Run(&item), || legacy_graphic_list_of(content), ctx.index() as usize) } @@ -1812,7 +1809,7 @@ fn map_points_extent(content: ListIn<'_, Vector>, _mapped: ExtentIn<'_>, level: fn flatten_path_core<'e>( arena: &'e core_types::arena::Arena, flattened: List, - snapshot: List, + snapshot: List>, ) -> Result< ( Vector, @@ -1891,7 +1888,7 @@ fn flatten_path_core<'e>( #[node_macro::node(category("Vector"), path(graphene_core::vector))] pub fn flatten_path<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, - content: IList, + content: IList>, ) -> Result< ( Vector, @@ -1903,8 +1900,7 @@ pub fn flatten_path<'e>( ), Interrupt, > { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; + let item = content.as_group_item(); let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item)); 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) @@ -2176,8 +2172,7 @@ fn decimate( /// The materialized vector level as the owned rows the cross-lane cores walk, /// content kept native. fn vector_rows_of(content: core_types::node::List<'_, Vector>) -> List { - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; + let item = content.as_group_item(); graphic_types::graphic::run_to_list::(&item).expect("the run holds vector lanes") } @@ -2625,7 +2620,8 @@ fn offset_points( /// Interpolates the geometry, appearance, and transform between multiple vector layers, producing a single morphed vector shape. /// /// *Progression* morphs through all objects. Interpolation is linear unless *Path* geometry is provided to control the trajectory between key objects. The **Origins to Polyline** node may be used to create a path with anchor points corresponding to each object. Other nodes can modify its path segments. -fn morph_core(flattened: List, snapshot: List, progression: f64, reverse: bool, distribution: InterpolationDistribution, path: List) -> List { +fn morph_core(flattened: List, snapshot: List>, progression: f64, reverse: bool, distribution: InterpolationDistribution, path: List) -> List { + use core_types::lane::LaneSource; /// Promotes a segment's handle pair to cubic-equivalent Bézier control points. /// For linear segments (both None), handles are placed at their respective anchors (zero-length) /// so that interpolation against another zero-length cubic doesn't introduce unwanted curvature. @@ -2740,7 +2736,7 @@ fn morph_core(flattened: List, snapshot: List, progression: f64 } // Lerp between two graphics. Solid color and gradient pairings interpolate; all other pairings step at the midpoint. - fn lerp_graphic(a: Option<&List>, b: Option<&List>, time: f64) -> Option> { + fn lerp_graphic(a: Option<&List>>, b: Option<&List>>, time: f64) -> Option>> { let transparent = List::new_from_element(Color::TRANSPARENT).into_graphic_list(); let a = a.filter(|graphic_list| is_paint_present(graphic_list)); @@ -3064,13 +3060,13 @@ fn morph_core(flattened: List, snapshot: List, progression: f64 let mut vector = Vector { stroke, ..Default::default() }; let fill_paint = { - let source = paint_graphics::(&content, source_index); - let target = paint_graphics::(&content, target_index); + let source = content.attr::(source_index).filter(|paint| is_paint_present(paint)); + let target = content.attr::(target_index).filter(|paint| is_paint_present(paint)); lerp_graphic(source, target, time) }; let stroke_paint = { - let source = paint_graphics::(&content, source_index); - let target = paint_graphics::(&content, target_index); + let source = content.attr::(source_index).filter(|paint| is_paint_present(paint)); + let target = content.attr::(target_index).filter(|paint| is_paint_present(paint)); lerp_graphic(source, target, time) }; @@ -3247,7 +3243,7 @@ fn morph_core(flattened: List, snapshot: List, progression: f64 fn morph_lane<'e>( arena: &'e core_types::arena::Arena, flattened: List, - snapshot: List, + snapshot: List>, progression: f64, reverse: bool, distribution: InterpolationDistribution, @@ -3281,7 +3277,7 @@ fn morph_lane<'e>( fn morph<'e>( ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, /// The vector objects to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements. - content: IList, + content: IList>, /// The fractional part `[0, 1)` traverses the morph uniformly along the path. If the control path has multiple subpaths, each added integer selects the next subpath. progression: Progression, /// Swap the direction of the progression between objects or along the control path. @@ -3307,11 +3303,9 @@ fn morph<'e>( ), Interrupt, > { - // SAFETY: a materialized input's frames are arena-resident. - let path_item = unsafe { core_types::record::GroupItem::from_resident(path.batch()) }; + let path_item = path.as_group_item(); let path = graphic_types::graphic::run_to_list::(&path_item).expect("the run holds vector lanes"); - // SAFETY: a materialized input's frames are arena-resident. - let item = unsafe { core_types::record::GroupItem::from_resident(content.batch()) }; + let item = content.as_group_item(); let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Run(&item)); morph_lane(ctx.arena(), flattened, legacy_graphic_list_of(content), progression, reverse, distribution, path) } @@ -3341,8 +3335,7 @@ fn morph_vector<'e>( ), Interrupt, > { - // SAFETY: a materialized input's frames are arena-resident. - let path_item = unsafe { core_types::record::GroupItem::from_resident(path.batch()) }; + let path_item = path.as_group_item(); let path = graphic_types::graphic::run_to_list::(&path_item).expect("the run holds vector lanes"); let wrapper = wrap_vector_level(content); let flattened = graphic_types::graphic::flatten_vector_rows(graphic_types::graphic::GraphicLevel::Legacy(&wrapper));