diff --git a/node-graph/libraries/graphic-types/src/artboard.rs b/node-graph/libraries/graphic-types/src/artboard.rs index f59362bc74..fa3f1fc55a 100644 --- a/node-graph/libraries/graphic-types/src/artboard.rs +++ b/node-graph/libraries/graphic-types/src/artboard.rs @@ -30,8 +30,43 @@ impl Artboard { pub fn into_graphic_list(self) -> List { self.0 } + + /// The artboard with every content group converted to its legacy form, so + /// the value owns all of its content free of arena borrows. + pub fn with_legacy_groups(&self) -> Artboard { + let mut content = self.0.clone(); + for element in content.iter_element_values_mut() { + *element = crate::graphic::map_groups_to_legacy(element); + } + Artboard(content) + } } +/// The deep clone-out for `Artboard` elements: as for `Graphic`, a plain +/// clone of group content would carry frame pointers into the evaluation's +/// arena, so memo and capture seams copy out the legacy-converted form. +/// +/// # Safety +/// `ptr` must point at a live parked `Artboard` element field. +unsafe fn deep_clone_artboard(ptr: *const u8) -> Box { + let artboard = unsafe { core_types::record::borrow_element::(core_types::record::Rec::new(ptr)) }; + Box::new(artboard.with_legacy_groups()) +} + +const _: () = { + #[cfg(not(target_family = "wasm"))] + #[core_types::ctor::ctor] + fn register() { + core_types::record::register_deep_element_clone::(deep_clone_artboard); + } + + #[cfg(target_family = "wasm")] + #[unsafe(export_name = "__node_registry_deep_element_artboard")] + extern "C" fn register() { + core_types::record::register_deep_element_clone::(deep_clone_artboard); + } +}; + impl From> for Artboard { fn from(content: List) -> Self { Self(content) diff --git a/node-graph/libraries/graphic-types/src/boundary.rs b/node-graph/libraries/graphic-types/src/boundary.rs index 6bbc0b94d9..35592c827c 100644 --- a/node-graph/libraries/graphic-types/src/boundary.rs +++ b/node-graph/libraries/graphic-types/src/boundary.rs @@ -68,7 +68,16 @@ pub fn capture_to_legacy(capture: &RecordCapture, arena: &Arena) -> Option(&item)) + fn typed_artboards(item: &GroupItem) -> Option> { + let mut list = run_to_legacy_list::(item)?; + // The capture outlives the arena generation, so group content must + // leave in its owned legacy form. + for artboard in list.iter_element_values_mut() { + *artboard = artboard.with_legacy_groups(); + } + Some(Box::new(list)) + } + None.or_else(|| typed_artboards(&item)) .or_else(|| typed::(&item)) .or_else(|| typed::>(&item)) .or_else(|| typed::>(&item)) diff --git a/node-graph/nodes/graphic/src/artboard.rs b/node-graph/nodes/graphic/src/artboard.rs index cf626cad2b..5c7c8ad90b 100644 --- a/node-graph/nodes/graphic/src/artboard.rs +++ b/node-graph/nodes/graphic/src/artboard.rs @@ -37,10 +37,10 @@ pub fn create_artboard( ) -> (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()) }; - let content = graphic_types::graphic::group_to_legacy_list(&core_types::record::Group { + let content = core_types::list::List::new_from_element(Graphic::Group(core_types::record::Group { row: None, content: core_types::record::GroupContent::Run(item), - }); + })); // Normalize so `location` is the top-left corner and `dimensions` are positive (allowing negative input // dimensions to represent dragging from the opposite corner). Compute the corner using the raw signed