From d814b610da7f9afc95e72674251b2a023c6824c7 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 27 Aug 2026 21:17:31 +0000 Subject: [PATCH] Migrate test sources and assertions onto the safe record builders --- node-graph/libraries/core-types/src/record.rs | 74 +-- .../libraries/graphic-types/src/graphic.rs | 221 +++---- .../libraries/rendering/src/renderer.rs | 62 +- node-graph/nodes/gcore/src/memo.rs | 5 +- node-graph/nodes/gcore/src/record.rs | 568 +++++++----------- node-graph/nodes/graphic/src/record.rs | 126 ++-- node-graph/nodes/path-bool/src/lib.rs | 11 +- node-graph/nodes/repeat/src/repeat_nodes.rs | 87 +-- 8 files changed, 431 insertions(+), 723 deletions(-) diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 09208d5c8f..6ed3962a7c 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -2456,21 +2456,15 @@ mod tests { use crate::lane::LaneSource; use glam::DAffine2; - let layout = Layout::default().with_writes(0, element_write::(), &[FieldWrite::of::(0), FieldWrite::of::(0)]); - let stride = layout.lane_stride(); + let arena = crate::arena::Arena::new(4096).unwrap(); let transform = DAffine2::from_translation((5., 6.).into()); - let mut bytes = vec![0u8; stride * 2]; + let mut builder = RunBuilder::new(&arena, element_write::(), &[FieldWrite::of::(0), FieldWrite::of::(0)], 2).unwrap(); for lane in 0..2 { - // SAFETY: `bytes` is `stride` per lane, and the offsets come from `layout`. - unsafe { - let base = bytes.as_mut_ptr().add(lane * stride); - base.cast::().write(lane as f64); - base.add(layout.offset_of(Transform::NAME, 0).unwrap()).cast::().write(transform); - base.add(layout.offset_of(Opacity::NAME, 0).unwrap()).cast::().write(0.25); - } + let lane = builder.push(lane as f64).unwrap(); + builder.attr::(lane, transform); + builder.attr::(lane, 0.25); } - // SAFETY: `bytes` holds two lanes of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(crate::node::RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; + let item = builder.finish(); let run = RunView::::new(&item).expect("the run holds f64 elements"); let mut list = crate::list::List::new_from_element(0f64); @@ -2493,10 +2487,10 @@ mod tests { use crate::attribute::Opacity; use crate::lane::LaneSource; - let layout = Layout::default().with_writes(0, element_write::(), &[]); - let bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(crate::node::RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; + let arena = crate::arena::Arena::new(1024).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write::(), &[], 1).unwrap(); + builder.push(0f64).unwrap(); + let item = builder.finish(); let run = RunView::::new(&item).expect("the run holds f64 elements"); assert_eq!(run.attr::(0), 1.); @@ -2708,6 +2702,8 @@ mod tests { #[test] fn a_run_builds_from_a_legacy_list_and_serves_its_rows() { + use crate::lane::LaneSource; + let mut list = crate::list::List::new_from_element(String::from("row 0")); list.push(crate::list::Item::new_from_element(String::from("row 1"))); let transform = glam::DAffine2::from_translation(glam::DVec2::new(3., 4.)); @@ -2719,29 +2715,25 @@ mod tests { let arena = crate::arena::Arena::new(1 << 16).unwrap(); let item = GroupItem::from_list(list, &arena).unwrap(); assert_eq!(item.len(), 2); - let layout = item.layout().clone(); - let lanes = item.lanes(); + let run = RunView::::new(&item).expect("the run holds string elements"); - let rec = lanes.get(0).rec(); - assert_eq!(unsafe { read_element::(rec) }, "row 0"); - assert_eq!(unsafe { rec.read::(layout.offset_of(crate::ATTR_TRANSFORM, 0).unwrap()) }, transform); - assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, "first"); - assert!(unsafe { rec.read::<&[crate::uuid::NodeId]>(layout.offset_of(crate::ATTR_EDITOR_LAYER_PATH, 0).unwrap()) }.is_empty()); + assert_eq!(run.element(0).map(String::as_str), Some("row 0")); + assert_eq!(run.attr::(0), transform); + assert_eq!(run.attr::(0), "first"); + assert!(run.attr::(0).is_empty()); - let rec = lanes.get(1).rec(); - assert_eq!(unsafe { read_element::(rec) }, "row 1"); + assert_eq!(run.element(1).map(String::as_str), Some("row 1")); // A lane without the value reads the census default, not garbage. - assert_eq!(unsafe { rec.read::(layout.offset_of(crate::ATTR_TRANSFORM, 0).unwrap()) }, glam::DAffine2::IDENTITY); - assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, ""); - assert_eq!( - unsafe { rec.read::<&[crate::uuid::NodeId]>(layout.offset_of(crate::ATTR_EDITOR_LAYER_PATH, 0).unwrap()) }, - &[crate::uuid::NodeId(7), crate::uuid::NodeId(9)] - ); - assert_eq!(unsafe { rec.read::>(layout.offset_of("max_width", 0).unwrap()) }, Some(12.5)); + assert_eq!(run.attr::(1), glam::DAffine2::IDENTITY); + assert_eq!(run.attr::(1), ""); + assert_eq!(run.attr::(1), &[crate::uuid::NodeId(7), crate::uuid::NodeId(9)]); + assert_eq!(run.attr::(1), Some(12.5)); } #[test] fn a_built_run_replays_after_the_source_dies() { + use crate::lane::LaneSource; + let owned = { let arena = crate::arena::Arena::new(1 << 16).unwrap(); let mut list = crate::list::List::new_from_element(String::from("element")); @@ -2752,25 +2744,25 @@ mod tests { let arena = crate::arena::Arena::new(1 << 16).unwrap(); let replayed = owned.replay(&arena).unwrap(); - let layout = replayed.layout().clone(); - let rec = replayed.lanes().get(0).rec(); - assert_eq!(unsafe { read_element::(rec) }, "element"); - assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, "label"); - assert_eq!(unsafe { rec.read::<&[crate::uuid::NodeId]>(layout.offset_of(crate::ATTR_EDITOR_LAYER_PATH, 0).unwrap()) }, &[crate::uuid::NodeId(3)]); + let run = RunView::::new(&replayed).expect("the run holds string elements"); + assert_eq!(run.element(0).map(String::as_str), Some("element")); + assert_eq!(run.attr::(0), "label"); + assert_eq!(run.attr::(0), &[crate::uuid::NodeId(3)]); } #[test] fn a_wrong_typed_or_undeclared_column_leaves_the_default() { + use crate::lane::LaneSource; + let mut list = crate::list::List::new_from_element(String::from("element")); list.set_attribute(crate::ATTR_OPACITY, 0, String::from("not an f64")); list.set_attribute("never_declared", 0, 5u32); let arena = crate::arena::Arena::new(1 << 16).unwrap(); let item = GroupItem::from_list(list, &arena).unwrap(); - let layout = item.layout().clone(); - assert!(layout.offset_of("never_declared", 0).is_none(), "an undeclared key has no field form"); - let rec = item.lanes().get(0).rec(); - assert_eq!(unsafe { rec.read::(layout.offset_of(crate::ATTR_OPACITY, 0).unwrap()) }, 1., "the wrong-typed value reads as absent"); + assert!(item.layout().offset_of("never_declared", 0).is_none(), "an undeclared key has no field form"); + let run = RunView::::new(&item).expect("the run holds string elements"); + assert_eq!(run.attr::(0), 1., "the wrong-typed value reads as absent"); } #[test] diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index fb97a5a8dd..0725e36140 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -1494,8 +1494,7 @@ mod run_tests { use core_types::attribute::Attribute; use core_types::bounds::BoundingBox; use core_types::lane::LaneSource; - use core_types::node::RecordBatch; - use core_types::record::{FieldWrite, GroupItem, Layout, RunView, element_write_hashed}; + use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed}; use glam::{DAffine2, DVec2}; use vector_types::subpath::Subpath; use vector_types::vector::PointId; @@ -1509,17 +1508,11 @@ mod run_tests { let paint = List::new_from_element(Graphic::Color(Color::BLACK)); let vector = unit_square_at(DVec2::ZERO); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - let mut bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: `bytes` is one lane of `layout`; a parked element stores its - // reference, and the fill field stores the marker's value form. - unsafe { - let base = bytes.as_mut_ptr(); - base.cast::<&Vector>().write(&vector); - base.add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::>>().write(Some(&paint)); - } - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(vector.clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let item = builder.finish(); let run = RunView::::new(&item).expect("the run holds vector elements"); assert_eq!(run.attr::(0), Some(&paint)); @@ -1535,26 +1528,17 @@ mod run_tests { let paint = List::new_from_element(Graphic::Color(Color::BLACK)); let vector = unit_square_at(DVec2::ZERO); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - let mut bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: `bytes` is one lane of `layout`; a parked element stores its - // reference, and the fill field stores the marker's value form. - unsafe { - let base = bytes.as_mut_ptr(); - base.cast::<&Vector>().write(&vector); - base.add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::>>().write(Some(&paint)); - } - let (owned, expected) = { - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; - let group = core_types::record::Group { - row: None, - content: item, - }; - let expected = group_to_legacy_list(&group); - (map_groups_to_owned(&Graphic::Group(group)), expected) + let source = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&source, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(vector.clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let group = core_types::record::Group { + row: None, + content: builder.finish(), }; - bytes.fill(u8::MAX); + let expected = group_to_legacy_list(&group); + let owned = map_groups_to_owned(&Graphic::Group(group)); + drop(source); let arena = core_types::arena::Arena::new(1 << 16).unwrap(); let resident = map_groups_to_resident(&owned, &arena).expect("the arena holds the replay"); @@ -1565,35 +1549,23 @@ mod run_tests { #[test] fn an_owned_group_replays_nested_groups_through_the_element_glue() { let vector = unit_square_at(DVec2::ZERO); - let inner_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - // SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element - // stores its reference. - unsafe { inner_bytes.as_mut_ptr().cast::<&Vector>().write(&vector) }; - // SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride. - let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) }; + let source = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&source, element_write_hashed::(), &[], 1).unwrap(); + builder.push(vector.clone()).unwrap(); let nested = Graphic::Group(core_types::record::Group { row: None, - content: inner_item, + content: builder.finish(), }); - let outer_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut outer_bytes = vec![0u8; outer_layout.lane_stride()]; - // SAFETY: `outer_bytes` is one lane of `outer_layout`; a parked element - // stores its reference. - unsafe { outer_bytes.as_mut_ptr().cast::<&Graphic>().write(&nested) }; - let (owned, expected) = { - // SAFETY: `outer_bytes` holds one lane of `outer_layout` at its stride. - let outer_item = unsafe { GroupItem::from_resident(RecordBatch::new(outer_bytes.as_ptr(), 1, &outer_layout)) }; - let group = core_types::record::Group { - row: None, - content: outer_item, - }; - let expected = group_to_legacy_list(&group); - (map_groups_to_owned(&Graphic::Group(group)), expected) + let mut builder = RunBuilder::new(&source, element_write_hashed::(), &[], 1).unwrap(); + builder.push(nested).unwrap(); + let group = core_types::record::Group { + row: None, + content: builder.finish(), }; - outer_bytes.fill(u8::MAX); - inner_bytes.fill(u8::MAX); + let expected = group_to_legacy_list(&group); + let owned = map_groups_to_owned(&Graphic::Group(group)); + drop(source); let arena = core_types::arena::Arena::new(1 << 16).unwrap(); let resident = map_groups_to_resident(&owned, &arena).expect("the arena holds the replay"); @@ -1601,42 +1573,31 @@ mod run_tests { assert_eq!(group_to_legacy_list(group), expected); } - fn native_group_paint(vector: &Vector, inner_layout: &Layout, inner_bytes: &mut Vec) -> List { - // SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element - // stores its reference. - unsafe { inner_bytes.as_mut_ptr().cast::<&Vector>().write(vector) }; - // SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride. - let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, inner_layout)) }; + fn native_group_paint(vector: &Vector, arena: &core_types::arena::Arena) -> List { + let mut builder = RunBuilder::new(arena, element_write_hashed::(), &[], 1).unwrap(); + builder.push(vector.clone()).unwrap(); List::new_from_element(Graphic::Group(core_types::record::Group { row: None, - content: inner_item, + content: builder.finish(), })) } #[test] fn an_owned_run_deep_copies_graphic_list_fields() { let inner_vector = unit_square_at(DVec2::ZERO); - let inner_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes); + let source = core_types::arena::Arena::new(1 << 16).unwrap(); + let paint = native_group_paint(&inner_vector, &source); let vector = unit_square_at(DVec2::new(4., 4.)); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - let mut bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: `bytes` is one lane of `layout`; a parked element stores its - // reference, and the fill field stores the marker's value form. - unsafe { - bytes.as_mut_ptr().cast::<&Vector>().write(&vector); - bytes.as_mut_ptr().add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::>>().write(Some(&paint)); - } - let (owned, expected) = { - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; - (item.copy_out(), map_groups_to_legacy(paint.element(0).unwrap())) - }; - bytes.fill(u8::MAX); - inner_bytes.fill(u8::MAX); + let mut builder = RunBuilder::new(&source, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(vector.clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let item = builder.finish(); + let owned = item.copy_out(); + let expected = map_groups_to_legacy(paint.element(0).unwrap()); + drop(item); drop(paint); + drop(source); let arena = core_types::arena::Arena::new(1 << 16).unwrap(); let replayed = owned.replay(&arena).expect("the arena holds the replay"); @@ -1648,25 +1609,22 @@ mod run_tests { #[test] fn an_owned_record_deep_copies_graphic_list_fields() { let inner_vector = unit_square_at(DVec2::ZERO); - let inner_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes); + let source = core_types::arena::Arena::new(1 << 16).unwrap(); + let paint = native_group_paint(&inner_vector, &source); let vector = unit_square_at(DVec2::new(4., 4.)); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); + let mut builder = RunBuilder::new(&source, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(vector.clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let item = builder.finish(); + let layout = item.layout().clone(); let offset = layout.offset_of(Fill::NAME, 0).unwrap(); - let mut bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: as in the run test above. - unsafe { - bytes.as_mut_ptr().cast::<&Vector>().write(&vector); - bytes.as_mut_ptr().add(offset).cast::>>().write(Some(&paint)); - } - // SAFETY: `bytes` is a live record of `layout`. - let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, core_types::record::Rec::new(bytes.as_ptr())) }; + // SAFETY: the item's lane is a live record of `layout`. + let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) }; let expected = map_groups_to_legacy(paint.element(0).unwrap()); - bytes.fill(u8::MAX); - inner_bytes.fill(u8::MAX); + drop(item); drop(paint); + drop(source); let arena = core_types::arena::Arena::new(1 << 16).unwrap(); core_types::record::stack::reserve(layout.frame_bytes()); @@ -1679,28 +1637,19 @@ mod run_tests { #[test] fn a_legacy_list_owns_its_paint_attr_content() { let inner_vector = unit_square_at(DVec2::ZERO); - let inner_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes); + let source = core_types::arena::Arena::new(1 << 16).unwrap(); + let paint = native_group_paint(&inner_vector, &source); let vector = unit_square_at(DVec2::new(4., 4.)); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - let mut bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: `bytes` is one lane of `layout`; a parked element stores its - // reference, and the fill field stores the marker's value form. - unsafe { - bytes.as_mut_ptr().cast::<&Vector>().write(&vector); - bytes.as_mut_ptr().add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::>>().write(Some(&paint)); - } - let (legacy, expected) = { - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; - let legacy = run_to_legacy_list::(&item).expect("the run lowers to a legacy vector list"); - (legacy, map_groups_to_legacy(paint.element(0).unwrap())) - }; - bytes.fill(u8::MAX); - inner_bytes.fill(u8::MAX); + let mut builder = RunBuilder::new(&source, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(vector.clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let item = builder.finish(); + let legacy = run_to_legacy_list::(&item).expect("the run lowers to a legacy vector list"); + let expected = map_groups_to_legacy(paint.element(0).unwrap()); + drop(item); drop(paint); + drop(source); let served = legacy.attribute::>>(Fill::NAME, 0).expect("the fill attribute rides the list"); let served = served.as_ref().expect("the fill is present"); @@ -1710,18 +1659,13 @@ mod run_tests { #[test] fn a_run_list_keeps_native_group_elements() { let inner_vector = unit_square_at(DVec2::ZERO); - let inner_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - let content = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes); + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let content = native_group_paint(&inner_vector, &arena); let element = content.element(0).unwrap(); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut bytes = vec![0u8; layout.lane_stride()]; - // SAFETY: `bytes` is one lane of `layout`; a parked element stores its - // reference. - unsafe { bytes.as_mut_ptr().cast::<&Graphic>().write(element) }; - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[], 1).unwrap(); + builder.push(element.clone()).unwrap(); + let item = builder.finish(); let list = run_to_list::(&item).expect("the run holds graphic lanes"); assert!(matches!(list.element(0), Some(Graphic::Group(_))), "the list keeps the native group form"); } @@ -1729,13 +1673,10 @@ mod run_tests { #[test] fn the_vector_row_walk_matches_the_legacy_flatten() { let inner_vector = unit_square_at(DVec2::ZERO); - let inner_layout = Layout::default().with_writes(0, element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - // SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element - // stores its reference. - unsafe { inner_bytes.as_mut_ptr().cast::<&Vector>().write(&inner_vector) }; - // SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride. - let inner_item = unsafe { GroupItem::from_resident(RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[], 1).unwrap(); + builder.push(inner_vector.clone()).unwrap(); + let inner_item = builder.finish(); let mut painted = List::new(); painted.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ZERO)))); @@ -1793,19 +1734,13 @@ mod run_tests { let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(4., 4.))]; let transforms = [DAffine2::from_translation(DVec2::new(1., 2.)), DAffine2::from_scale(DVec2::splat(3.))]; - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - let stride = layout.lane_stride(); - let mut bytes = vec![0u8; stride * 2]; - // SAFETY: `bytes` is `stride` per lane, and the offsets come from `layout`. - unsafe { - for lane in 0..2 { - let base = bytes.as_mut_ptr().add(lane * stride); - base.cast::<&Vector>().write(&vectors[lane]); - base.add(layout.offset_of(core_types::ATTR_TRANSFORM, 0).unwrap()).cast::().write(transforms[lane]); - } + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[FieldWrite::of::(0)], 2).unwrap(); + for lane in 0..2 { + let lane = builder.push(vectors[lane].clone()).unwrap(); + builder.attr::(lane, transforms[lane]); } - // SAFETY: `bytes` holds two lanes of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; + let item = builder.finish(); let run = RunView::::new(&item).expect("the run holds vector elements"); let legacy = run_to_legacy_list::(&item).expect("the run lowers to a legacy vector list"); diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index 90b32fbe24..1d9a335df6 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -3025,9 +3025,7 @@ impl SvgRenderAttrs<'_> { #[cfg(test)] mod group_walk_tests { use super::*; - use core_types::attribute::Attribute; - use core_types::node::RecordBatch; - use core_types::record::{FieldWrite, GroupItem, Layout, element_write_hashed}; + use core_types::record::{FieldWrite, RunBuilder, element_write_hashed}; use graphic_types::markers::Fill; use graphic_types::vector_types::vector::PointId; @@ -3046,31 +3044,16 @@ mod group_walk_tests { (output.svg, output.svg_defs) } - /// One run lane per element of `elements`, each with a fill field, under the given layout writes. - unsafe fn write_lanes(layout: &Layout, elements: &[&T], fill: &[Option<&List>]) -> Vec { - let stride = layout.lane_stride(); - let mut bytes = vec![0u8; stride * elements.len()]; - for lane in 0..elements.len() { - // SAFETY: `bytes` is `stride` per lane; a parked element stores its - // reference, and the fill field stores the marker's value form. - unsafe { - let base = bytes.as_mut_ptr().add(lane * stride); - base.cast::<&T>().write(elements[lane]); - base.add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::>>().write(fill[lane]); - } - } - bytes - } - #[test] fn a_vector_run_group_renders_its_rows_without_layer_wrappers() { let paint = color_paint(); let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(3., 1.))]; - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - // SAFETY: the layout carries a parked vector element and the fill field. - let bytes = unsafe { write_lanes::(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; - // SAFETY: `bytes` holds two lanes of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[FieldWrite::of::(0)], 2).unwrap(); + let lane = builder.push(vectors[0].clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + builder.push(vectors[1].clone()).unwrap(); + let item = builder.finish(); let group = Group { row: None, content: item }; let params = RenderParams::default(); @@ -3084,11 +3067,11 @@ mod group_walk_tests { fn lane_paint_on_a_graphic_run_reaches_vector_interiors() { let paint = color_paint(); let inner = Graphic::Vector(unit_square_at(DVec2::ZERO)); - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - // SAFETY: the layout carries a parked graphic element and the fill field. - let bytes = unsafe { write_lanes::(&layout, &[&inner], &[Some(&paint)]) }; - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(inner.clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let item = builder.finish(); let group = Group { row: None, content: item }; let params = RenderParams::default(); @@ -3103,11 +3086,11 @@ mod group_walk_tests { fn a_group_collects_its_lane_metadata_for_the_caller() { let paint = color_paint(); let vectors = [unit_square_at(DVec2::ZERO)]; - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - // SAFETY: the layout carries a parked vector element and the fill field. - let bytes = unsafe { write_lanes::(&layout, &[&vectors[0]], &[Some(&paint)]) }; - // SAFETY: `bytes` holds one lane of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[FieldWrite::of::(0)], 1).unwrap(); + let lane = builder.push(vectors[0].clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + let item = builder.finish(); let group = Group { row: None, content: item }; let footprint = Footprint::default(); @@ -3128,11 +3111,12 @@ mod group_walk_tests { fn a_group_serves_its_legacy_lowerings_click_targets() { let paint = color_paint(); let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(2., 2.))]; - let layout = Layout::default().with_writes(0, element_write_hashed::(), &[FieldWrite::of::(0)]); - // SAFETY: the layout carries a parked vector element and the fill field. - let bytes = unsafe { write_lanes::(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; - // SAFETY: `bytes` holds two lanes of `layout` at its stride. - let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = RunBuilder::new(&arena, element_write_hashed::(), &[FieldWrite::of::(0)], 2).unwrap(); + let lane = builder.push(vectors[0].clone()).unwrap(); + builder.attr::(lane, Some(&paint)); + builder.push(vectors[1].clone()).unwrap(); + let item = builder.finish(); let group = Group { row: None, content: item }; let mut native = Vec::new(); diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index feab498ad1..657849bed4 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -274,11 +274,10 @@ mod tests { let io = handle.serialize().expect("the eval landed a snapshot"); let snapshot = io.downcast_ref::().expect("the monitor serializes its context snapshot"); let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains"); - let GPoll::Final(value) = edge.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&edge, &ctx) else { panic!("expected a final record"); }; - // SAFETY: the eval produced a live record of the edge's layout. - assert_eq!(unsafe { layout.rec(&value).element::() }, 11); + assert_eq!(served.element::(), 11); } #[test] diff --git a/node-graph/nodes/gcore/src/record.rs b/node-graph/nodes/gcore/src/record.rs index f46629733d..add057dd75 100644 --- a/node-graph/nodes/gcore/src/record.rs +++ b/node-graph/nodes/gcore/src/record.rs @@ -395,7 +395,7 @@ mod tests { use core_types::SourceId; use core_types::arena::Arena; use core_types::attribute::Attribute as AttributeMarker; - use core_types::context::{ContextImpl, EvalScope}; + use core_types::context::{ContextImpl, EvalScope, ExtractArena}; use core_types::gpoll::GPoll; use core_types::node::Node; use core_types::record::{Layout, Rec, RecordSource, RecordValue, stack}; @@ -413,29 +413,20 @@ mod tests { struct RecordSourceNode { layout: Layout, element: E, - fields: Vec<(usize, f64)>, + fields: Vec<(&'static str, f64)>, partial: bool, } - impl<'e, E: Copy> Node> for RecordSourceNode { + impl<'e, E: Copy + Send + Sync + 'static> Node> for RecordSourceNode { type Output = RecordValue<'e>; - fn eval(&self, _input: &ContextImpl<'e>) -> GPoll> { - let mut value = RecordValue::zeroed(); - let dst = match self.layout.frame_bytes() { - 0 => value.as_mut_ptr(), - bytes => stack::push(bytes), - }; - unsafe { - dst.cast::().write(self.element); - for (offset, field) in &self.fields { - dst.add(*offset).cast::().write(*field); - } - } - if self.layout.frame_bytes() != 0 { - stack::pop(dst); - value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }); + fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { + let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(self.element); + for (name, field) in &self.fields { + frame.field::(name, 0, *field); } + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; match self.partial { true => GPoll::Partial(value), false => GPoll::Final(value), @@ -446,7 +437,7 @@ mod tests { struct LeveledSourceNode { layout: Layout, elements: Vec, - field: Option<(usize, f64)>, + field: Option<(&'static str, f64)>, } impl<'e> Node> for LeveledSourceNode { @@ -454,15 +445,13 @@ mod tests { fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { let element = self.elements[input.innermost_index() as usize % self.elements.len()]; - let dst = stack::push(self.layout.frame_bytes()); - unsafe { - dst.cast::().write(element); - if let Some((offset, value)) = self.field { - dst.add(offset).cast::().write(value); - } + let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(element); + if let Some((name, value)) = self.field { + frame.field::(name, 0, value); } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -484,13 +473,11 @@ mod tests { fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { let (element, transform) = self.rows[input.innermost_index() as usize % self.rows.len()]; - let dst = stack::push(self.layout.frame_bytes()); - unsafe { - dst.cast::().write(element); - dst.add(self.layout.offset_of(::NAME, 0).unwrap()).cast::().write(transform); - } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(element); + frame.attr::(transform); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -517,10 +504,10 @@ mod tests { if lane >= self.count as u64 { return GPoll::past_end(); } - let dst = stack::push(self.layout.frame_bytes()); - unsafe { dst.cast::().write(lane as f64) }; - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(lane as f64); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -543,16 +530,9 @@ mod tests { // Depth-0 content varying per copy: the enclosing (pushed) level's // index sits one link above the content's own innermost lane. let element = input.try_index().and_then(|mut indices| indices.nth(1)).unwrap_or(0) as f64; - let mut value = RecordValue::zeroed(); - let dst = match self.layout.frame_bytes() { - 0 => value.as_mut_ptr(), - bytes => stack::push(bytes), - }; - unsafe { dst.cast::().write(element) }; - if self.layout.frame_bytes() != 0 { - stack::pop(dst); - value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }); - } + let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(element); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; GPoll::Final(value) } } @@ -570,6 +550,7 @@ mod tests { level: 0, size: 8, align: 8, + type_id: std::any::TypeId::of::(), read_erased: ::read_erased, repark: None, content_hash: None, @@ -587,6 +568,7 @@ mod tests { level: 0, size: 8, align: 8, + type_id: std::any::TypeId::of::(), read_erased: ::read_erased, repark: None, content_hash: None, @@ -660,13 +642,12 @@ mod tests { let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(8u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]); assert_eq!(node.layout(), &leveled); - let GPoll::Final(value) = node.eval(&indexed) else { + let GPoll::Final(served) = core_types::record::capture(&node, &indexed) else { panic!("expected a final record"); }; - let rec = leveled.rec(&value); - assert_eq!(unsafe { rec.element::() }, 7.); + assert_eq!(served.element::(), 7.); // The written opacity is this copy's own index (the head of the chain). - assert_eq!(unsafe { rec.read::(leveled.offset_of(Opacity::NAME, 0).unwrap()) }, 5.); + assert_eq!(served.attr::(), 5.); } #[test] @@ -713,10 +694,10 @@ mod tests { assert_eq!(leveled.depth, 1, "the IList return pushed one rank level above the depth-0 content"); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(core_types::gpoll::Extent::Exactly(3))); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { leveled.rec(&value).element::() }, 7., "the opaque generic element forwarded unchanged"); + assert_eq!(served.element::(), 7., "the opaque generic element forwarded unchanged"); } #[test] @@ -752,19 +733,14 @@ mod tests { meta, &[Some(&base)], ); - let leveled = Node::::layout(&repeat).clone(); - let head = ctx.index_head(); for copy in 0..4 { - let mark = stack::sp(); let lane = ctx.promoted(&head, copy); - let GPoll::Final(value) = repeat.eval(&lane) else { + let GPoll::Final(served) = core_types::record::capture(&repeat, &lane) else { panic!("expected a final record"); }; // The copy evaluated its content at its own pushed index. - assert_eq!(unsafe { leveled.rec(&value).element::() }, copy as f64); - // SAFETY: the copy's element was read out above, so no borrow into its frame remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), copy as f64); } } @@ -801,19 +777,14 @@ mod tests { meta, &[Some(&base)], ); - let leveled = Node::::layout(&repeat).clone(); - let head = ctx.index_head(); for copy in 0..4u64 { - let mark = stack::sp(); let lane = ctx.promoted(&head, copy); - let GPoll::Final(value) = repeat.eval(&lane) else { + let GPoll::Final(served) = core_types::record::capture(&repeat, &lane) else { panic!("expected a final record"); }; // Reversed: copy `j` evaluates its content at index `count - 1 - j`. - assert_eq!(unsafe { leveled.rec(&value).element::() }, (3 - copy) as f64); - // SAFETY: the copy's element was read out above, so no borrow into its frame remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), (3 - copy) as f64); } } @@ -859,18 +830,14 @@ mod tests { let head = ctx.index_head(); for flat in 0..6u64 { - let mark = stack::sp(); let lane = ctx.promoted(&head, flat); - let GPoll::Final(value) = repeat.eval(&lane) else { + let GPoll::Final(served) = core_types::record::capture(&repeat, &lane) else { panic!("expected a final record"); }; - let rec = two_level.rec(&value); - assert_eq!(unsafe { rec.element::() }, 7.); + assert_eq!(served.element::(), 7.); // The flat index decomposes: the content sees the remainder as its // own innermost lane, so its per-lane opacity is `flat % 3`. - assert_eq!(unsafe { rec.read::(two_level.offset_of(Opacity::NAME, 0).unwrap()) }, (flat % 3) as f64); - // SAFETY: the element and attr were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.attr::(), (flat % 3) as f64); } } @@ -882,11 +849,10 @@ mod tests { let ctx = ContextImpl::root(&scope); let base = f64_layout(&[Opacity::NAME]); - let opacity_offset = base.offset_of(Opacity::NAME, 0).unwrap(); let content = RecordSourceNode { layout: base.clone(), element: 7., - fields: vec![(opacity_offset, 0.5)], + fields: vec![(Opacity::NAME, 0.5)], partial: false, }; reserve_for(&[&base]); @@ -902,17 +868,13 @@ mod tests { let head = ctx.index_head(); for copy in 0..4u64 { - let mark = stack::sp(); let lane = ctx.promoted(&head, copy); - let GPoll::Final(value) = node.eval(&lane) else { + let GPoll::Final(served) = core_types::record::capture(&node, &lane) else { panic!("expected a final record"); }; - let rec = leveled.rec(&value); // The content row's element forwards; its opacity re-scales per copy. - assert_eq!(unsafe { rec.element::() }, 7.); - assert_eq!(unsafe { rec.read::(leveled.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5 * (copy + 1) as f64); - // SAFETY: the element and attr were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), 7.); + assert_eq!(served.attr::(), 0.5 * (copy + 1) as f64); } } @@ -931,12 +893,12 @@ mod tests { let base = LeveledSourceNode { layout: base_layout.clone(), elements: vec![10., 11.], - field: Some((base_layout.offset_of(Opacity::NAME, 0).unwrap(), 0.5)), + field: Some((Opacity::NAME, 0.5)), }; let new = LeveledSourceNode { layout: new_layout.clone(), elements: vec![100., 101., 102.], - field: Some((new_layout.offset_of(Length::NAME, 0).unwrap(), 7.)), + field: Some((Length::NAME, 7.)), }; let meta = core_types::record::LayoutMeta { sources: vec![0, 1], @@ -959,23 +921,19 @@ mod tests { let head = ctx.index_head(); let expected = [10., 11., 100., 101., 102.]; for (lane, &element) in expected.iter().enumerate() { - let mark = stack::sp(); let scoped = ctx.promoted(&head, lane as u64); - let GPoll::Final(value) = node.eval(&scoped) else { + let GPoll::Final(served) = core_types::record::capture(&node, &scoped) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(unsafe { rec.element::() }, element); - let opacity = unsafe { rec.read::(out.offset_of(Opacity::NAME, 0).unwrap()) }; - let length = unsafe { rec.read::(out.offset_of(Length::NAME, 0).unwrap()) }; + assert_eq!(served.element::(), element); + let opacity = served.attr::(); + let length = served.attr::(); match lane < 2 { // The base side wrote its opacity; length fills from the census. true => assert_eq!((opacity, length), (0.5, 0.)), // The new side wrote its length; opacity fills from the census. false => assert_eq!((opacity, length), (1., 7.)), } - // SAFETY: the element and attrs were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } } @@ -998,17 +956,17 @@ mod tests { let a = || LeveledSourceNode { layout: a_layout.clone(), elements: vec![10., 11.], - field: Some((a_layout.offset_of(Opacity::NAME, 0).unwrap(), 0.5)), + field: Some((Opacity::NAME, 0.5)), }; let b = || LeveledSourceNode { layout: b_layout.clone(), elements: vec![100., 101., 102.], - field: Some((b_layout.offset_of(Length::NAME, 0).unwrap(), 7.)), + field: Some((Length::NAME, 7.)), }; let c = || LeveledSourceNode { layout: c_layout.clone(), elements: vec![1000.], - field: Some((c_layout.offset_of(Opacity::NAME, 0).unwrap(), 0.25)), + field: Some((Opacity::NAME, 0.25)), }; let meta = || core_types::record::LayoutMeta { sources: vec![0, 1], @@ -1041,32 +999,23 @@ mod tests { &[Some(&a_layout), Some(&bc)], ); - let left_out = Node::::layout(&left).clone(); - let right_out = Node::::layout(&right).clone(); assert_eq!(left.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(6))); assert_eq!(right.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(6))); let head = ctx.index_head(); - let check = |poll: GPoll, out: &Layout, lane: usize, (element, opacity, length): (f64, f64, f64)| { - let GPoll::Final(value) = poll else { + let check = |poll: GPoll, lane: usize, (element, opacity, length): (f64, f64, f64)| { + let GPoll::Final(served) = poll else { panic!("expected a final record at lane {lane}"); }; - let rec = out.rec(&value); - assert_eq!(unsafe { rec.element::() }, element, "lane {lane}"); - assert_eq!(unsafe { rec.read::(out.offset_of(Opacity::NAME, 0).unwrap()) }, opacity, "lane {lane}"); - assert_eq!(unsafe { rec.read::(out.offset_of(Length::NAME, 0).unwrap()) }, length, "lane {lane}"); + assert_eq!(served.element::(), element, "lane {lane}"); + assert_eq!(served.attr::(), opacity, "lane {lane}"); + assert_eq!(served.attr::(), length, "lane {lane}"); }; let expected = [(10., 0.5, 0.), (11., 0.5, 0.), (100., 1., 7.), (101., 1., 7.), (102., 1., 7.), (1000., 0.25, 0.)]; for (lane, &row) in expected.iter().enumerate() { let scoped = ctx.promoted(&head, lane as u64); - let mark = stack::sp(); - check(left.eval(&scoped), &left_out, lane, row); - // SAFETY: the element and attrs were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; - let mark = stack::sp(); - check(right.eval(&scoped), &right_out, lane, row); - // SAFETY: as above. - unsafe { stack::rewind(mark) }; + check(core_types::record::capture(&left, &scoped), lane, row); + check(core_types::record::capture(&right, &scoped), lane, row); } } @@ -1086,12 +1035,12 @@ mod tests { let base = LeveledSourceNode { layout: base_layout.clone(), elements: vec![10., 11.], - field: Some((base_layout.offset_of(Opacity::NAME, 0).unwrap(), 0.5)), + field: Some((Opacity::NAME, 0.5)), }; let new = LeveledSourceNode { layout: new_layout.clone(), elements: vec![100., 101., 102.], - field: Some((new_layout.offset_of(Length::NAME, 0).unwrap(), 7.)), + field: Some((Length::NAME, 7.)), }; let meta = core_types::record::LayoutMeta { sources: vec![0, 1], @@ -1112,23 +1061,19 @@ mod tests { let head = ctx.index_head(); let per_lane: f64 = (0..5u64) .map(|lane| { - let mark = stack::sp(); - let GPoll::Final(value) = extend.eval(&ctx.promoted(&head, lane)) else { + let GPoll::Final(served) = core_types::record::capture(&extend, &ctx.promoted(&head, lane)) else { panic!("expected a final record at lane {lane}"); }; - let element = unsafe { wire.rec(&value).element::() }; - // SAFETY: the element was read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; - element + served.element::() }) .sum(); let node = install_flip(SumNode::new(extend, &wire), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; // The fold's batch walks both sides of the seam in one range. - let folded = unsafe { out.rec(&value).element::() }; + let folded = served.element::(); assert_eq!(folded, per_lane); assert_eq!(folded, 324.); } @@ -1198,13 +1143,10 @@ mod tests { let head = ctx.index_head(); for (lane, element) in [(0u64, 1.), (5, 3.), (6, 10.), (17, 30.)] { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane)) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx.promoted(&head, lane)) else { panic!("expected a final record at lane {lane}"); }; - assert_eq!(unsafe { out.rec(&value).element::() }, element, "flat lane {lane}"); - // SAFETY: the element was read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), element, "flat lane {lane}"); } // Mismatched inner extents: the top still sums, the inner query errors. @@ -1256,14 +1198,11 @@ mod tests { assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(expected.len())), "omit at {index}"); let head = ctx.index_head(); for (lane, &element) in expected.iter().enumerate() { - let mark = stack::sp(); let scoped = ctx.promoted(&head, lane as u64); - let GPoll::Final(value) = node.eval(&scoped) else { + let GPoll::Final(served) = core_types::record::capture(&node, &scoped) else { panic!("expected a final record"); }; - assert_eq!(unsafe { layout.rec(&value).element::() }, element, "omit at {index}, lane {lane}"); - // SAFETY: the element was read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), element, "omit at {index}, lane {lane}"); } } } @@ -1281,7 +1220,7 @@ mod tests { let content = LeveledSourceNode { layout: layout.clone(), elements: vec![10., 11., 12.], - field: Some((layout.offset_of(Opacity::NAME, 0).unwrap(), 0.5)), + field: Some((Opacity::NAME, 0.5)), }; let (index_edge, index_layout) = lifted_value(index); let meta = core_types::record::LayoutMeta { @@ -1305,17 +1244,13 @@ mod tests { assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(expected.is_some() as usize)), "index {index}"); let Some(element) = expected else { continue }; let head = ctx.index_head(); - let mark = stack::sp(); let scoped = ctx.promoted(&head, 0); - let GPoll::Final(value) = node.eval(&scoped) else { + let GPoll::Final(served) = core_types::record::capture(&node, &scoped) else { panic!("expected a final record"); }; - let rec = layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, element); + assert_eq!(served.element::(), element); // The selected item keeps its attributes. - assert_eq!(unsafe { rec.read::(layout.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); - // SAFETY: the element and attr were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; + assert_eq!(served.attr::(), 0.5); } } @@ -1338,10 +1273,10 @@ mod tests { }; let (index_edge, index_layout) = lifted_value(index); let node = install_flip(ExtractElementNode::new(RecordSource::new(content, &layout, &layout), index_edge, &layout, &index_layout), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { out.rec(&value).element::() }, expected, "extract at {index}"); + assert_eq!(served.element::(), expected, "extract at {index}"); } } @@ -1375,25 +1310,21 @@ mod tests { let expected = [(1., 10.), (2., 30.), (3., 20.), (1., 30.), (2., 10.), (3., 20.)]; let head = ctx.index_head(); for (lane, &(element, x)) in expected.iter().enumerate() { - let mark = stack::sp(); let scoped = ctx.promoted(&head, lane as u64); - let GPoll::Final(value) = kept.eval(&scoped) else { + let GPoll::Final(served) = core_types::record::capture(&kept, &scoped) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(unsafe { rec.element::() }, element, "lane {lane}"); - let transform: DAffine2 = unsafe { rec.read(out.offset_of(::NAME, 0).unwrap()) }; + assert_eq!(served.element::(), element, "lane {lane}"); + let transform: DAffine2 = served.attr::(); assert_eq!(transform.translation.x, x, "lane {lane}"); - // SAFETY: the element and transform were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } let replaced = build(false); assert_eq!(replaced.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3))); - let GPoll::Final(value) = replaced.eval(&ctx.promoted(&head, 0)) else { + let GPoll::Final(served) = core_types::record::capture(&replaced, &ctx.promoted(&head, 0)) else { panic!("expected a final record"); }; - let transform: DAffine2 = unsafe { out.rec(&value).read(out.offset_of(::NAME, 0).unwrap()) }; + let transform: DAffine2 = served.attr::(); assert_eq!(transform.translation.x, 30., "without originals every lane reflects"); } @@ -1425,18 +1356,14 @@ mod tests { let head = ctx.index_head(); for (lane, &(element, x)) in rows.iter().rev().enumerate() { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(unsafe { rec.element::() }, element, "lane {lane} takes the gathered element"); - let transform: DAffine2 = unsafe { rec.read(out.offset_of(::NAME, 0).unwrap()) }; + assert_eq!(served.element::(), element, "lane {lane} takes the gathered element"); + let transform: DAffine2 = served.attr::(); assert_eq!(transform.translation.x, x, "lane {lane} carries the gathered transform"); - let opacity: f64 = unsafe { rec.read(out.offset_of(::NAME, 0).unwrap()) }; + let opacity: f64 = served.attr::(); assert_eq!(opacity, 0.25, "lane {lane} takes the declared write"); - // SAFETY: every field was read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } } @@ -1470,17 +1397,13 @@ mod tests { let batch = batch.into_shared(); assert_eq!(batch.len(), 6); for lane in 0..6 { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(unsafe { batch.get(lane).element::() }, unsafe { rec.element::() }, "lane {lane}"); + assert_eq!(unsafe { batch.get(lane).element::() }, served.element::(), "lane {lane}"); let batched: DAffine2 = batch.get(lane).attr::(); - let single: DAffine2 = unsafe { rec.read(out.offset_of(::NAME, 0).unwrap()) }; + let single: DAffine2 = served.attr::(); assert_eq!(batched, single, "lane {lane}"); - // SAFETY: the lane was read out above, so no borrow into its frames remains. - unsafe { stack::rewind(mark) }; } } @@ -1625,11 +1548,11 @@ mod tests { let node = install(SumNode::new(nested, &two_level), sum_layout_meta(), &[Some(&two_level)]); let out = Node::::layout(&node).clone(); assert_eq!(out.depth, 0, "the fold consumes the whole wire"); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; // Two copies of three lanes, each lane the copy index: 0 * 3 + 1 * 3. - assert_eq!(unsafe { out.rec(&value).element::() }, 3.); + assert_eq!(served.element::(), 3.); } #[test] @@ -1672,11 +1595,11 @@ mod tests { assert_eq!(two_level.depth, 2); let node = install_flip(SumNestedNode::new(nested, &two_level), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; // Two copies of three lanes of 7: the nested fold flattens 2 x 3. - assert_eq!(unsafe { out.rec(&value).element::() }, 42.); + assert_eq!(served.element::(), 42.); } #[test] @@ -1702,12 +1625,10 @@ mod tests { 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 { + let GPoll::Final(served) = core_types::record::capture(&node, &base.promoted(&head, lane)) else { panic!("expected a final record at lane {lane}"); }; - assert_eq!(unsafe { out.rec(&value).element::() }, expected, "lane {lane}"); - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), expected, "lane {lane}"); } } @@ -1787,21 +1708,19 @@ mod tests { // flat index decomposes across both copies. let head = ctx.index_head(); for (lane, expected) in [(0u64, 0.), (7, 2.), (9, 4.)] { - let mark = stack::sp(); - let GPoll::Final(value) = repeat.eval(&ctx.promoted(&head, lane)) else { + let GPoll::Final(served) = core_types::record::capture(&repeat, &ctx.promoted(&head, lane)) else { panic!("expected a final record at lane {lane}"); }; - assert_eq!(unsafe { two_level.rec(&value).element::() }, expected, "lane {lane}"); - unsafe { stack::rewind(mark) }; + assert_eq!(served.element::(), expected, "lane {lane}"); } // A full fold over the composite drains through the structure node; // the partial fold stays excluded with M3. let node = install_flip(SumNestedNode::new(repeat, &two_level), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { out.rec(&value).element::() }, 20.); + assert_eq!(served.element::(), 20.); } #[test] @@ -1817,10 +1736,10 @@ mod tests { reserve_for(&[&leveled, &out]); let node = install_flip(SumNode::new(source, &leveled), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { out.rec(&value).element::() }, 7.5); + assert_eq!(served.element::(), 7.5); } #[test] @@ -1839,11 +1758,11 @@ mod tests { for count in [5usize, 20] { let source = DrainSourceNode { layout: leveled.clone(), count }; let node = install_flip(SumNode::new(source, &leveled), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record at count {count}"); }; let expected = (count * (count - 1) / 2) as f64; - assert_eq!(unsafe { out.rec(&value).element::() }, expected, "count {count}"); + assert_eq!(served.element::(), expected, "count {count}"); } } @@ -1863,11 +1782,11 @@ mod tests { let node = install_flip(SumNode::new(repeat, &leveled), &out); assert_eq!(node.layout().depth, 0, "the reducer collapsed the rank level"); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; // sum(repeat(3, 7)) folds three copies of the element back to a scalar. - assert_eq!(unsafe { out.rec(&value).element::() }, 21.); + assert_eq!(served.element::(), 21.); } #[test] @@ -1907,12 +1826,12 @@ mod tests { let leveled = Node::::layout(&repeat).clone(); let node = install_flip(SumNode::new(repeat, &leveled), &out); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; // Every copy evaluates at its own index, so the lanes must be distinct // storage: sum(0 + 1 + 2 + 3), not four aliases of the last copy. - assert_eq!(unsafe { out.rec(&value).element::() }, 6.); + assert_eq!(served.element::(), 6.); } #[test] @@ -1955,12 +1874,11 @@ mod tests { &[Some(&modified)], ); assert_eq!(chain.layout(), &stacked); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = stacked.rec(&value); - assert_eq!(unsafe { rec.element::() }, 2.); - assert_eq!(unsafe { rec.read::(stacked.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25); + assert_eq!(served.element::(), 2.); + assert_eq!(served.attr::(), 0.25); } #[test] @@ -1975,12 +1893,11 @@ mod tests { reserve_for(&[&source_layout, &measured]); let chain = install(MeasureNode::new(bare_source(&source_layout, -2.), &source_layout), measure_layout_meta(), &[Some(&source_layout)]); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = measured.rec(&value); - assert_eq!(unsafe { rec.element::() }, -2.); - assert_eq!(unsafe { rec.read::(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.); + assert_eq!(served.element::(), -2.); + assert_eq!(served.attr::(), 2.); } #[test] @@ -2007,12 +1924,11 @@ mod tests { measure_layout_meta(), &[Some(&modified)], ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = measured.rec(&value); - assert_eq!(unsafe { rec.read::(measured.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); - assert_eq!(unsafe { rec.read::(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.); + assert_eq!(served.attr::(), 0.5); + assert_eq!(served.attr::(), 2.); } #[test] @@ -2028,10 +1944,10 @@ mod tests { reserve_for(&[&source_layout, &modified, &shaded]); let bare = install(ShadeNode::new(bare_source(&source_layout, 4.), &source_layout), shade_layout_meta(), &[Some(&source_layout)]); - let GPoll::Final(value) = bare.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&bare, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { source_layout.rec(&value).element::() }, 4.); + assert_eq!(served.element::(), 4.); let chain = install( ShadeNode::new( @@ -2045,12 +1961,11 @@ mod tests { shade_layout_meta(), &[Some(&modified)], ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = shaded.rec(&value); - assert_eq!(unsafe { rec.element::() }, 2.); - assert_eq!(unsafe { rec.read::(shaded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); + assert_eq!(served.element::(), 2.); + assert_eq!(served.attr::(), 0.5); } #[test] @@ -2067,12 +1982,11 @@ mod tests { reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]); let wide = install(FadeNode::new(bare_source(&f64_source, 8.), ValueNode(0.5), &f64_source), fade_layout_meta(), &[Some(&f64_source)]); - let GPoll::Final(value) = wide.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&wide, &ctx) else { panic!("expected a final record"); }; - let rec = f64_faded.rec(&value); - assert_eq!(unsafe { rec.element::() }, 8.); - assert_eq!(unsafe { rec.read::(f64_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); + assert_eq!(served.element::(), 8.); + assert_eq!(served.attr::(), 0.5); let narrow = install( FadeNode::new( @@ -2088,12 +2002,11 @@ mod tests { fade_layout_meta(), &[Some(&u32_source)], ); - let GPoll::Final(value) = narrow.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&narrow, &ctx) else { panic!("expected a final record"); }; - let rec = u32_faded.rec(&value); - assert_eq!(unsafe { rec.element::() }, 7); - assert_eq!(unsafe { rec.read::(u32_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25); + assert_eq!(served.element::(), 7); + assert_eq!(served.attr::(), 0.25); } #[test] @@ -2108,12 +2021,11 @@ mod tests { let node = install(SourceOpacityNode::new(ValueNode(()), ValueNode(3.), ValueNode(0.25)), source_opacity_layout_meta(), &[]); assert_eq!(Node::::layout(&node), &layout); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - let rec = layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 3.); - assert_eq!(unsafe { rec.read::(layout.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25); + assert_eq!(served.element::(), 3.); + assert_eq!(served.attr::(), 0.25); } #[test] @@ -2141,10 +2053,10 @@ mod tests { multiply_opacity_layout_meta(), &[Some(&source_layout)], ); - let GPoll::Partial(value) = chain.eval(&ctx) else { + let GPoll::Partial(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a partial record"); }; - assert_eq!(unsafe { modified.rec(&value).read::(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); + assert_eq!(served.attr::(), 0.5); } #[test] @@ -2163,10 +2075,10 @@ mod tests { checked_multiply_opacity_layout_meta(), &[Some(&source_layout)], ); - let GPoll::Final(value) = ok.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&ok, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { modified.rec(&value).read::(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); + assert_eq!(served.attr::(), 0.5); let failing = install( CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout), @@ -2204,12 +2116,11 @@ mod tests { scale_layout_meta(), &[Some(&modified)], ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = scaled.rec(&value); - assert_eq!(unsafe { rec.element::() }, 6.); - assert_eq!(unsafe { rec.read::(scaled.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5); + assert_eq!(served.element::(), 6.); + assert_eq!(served.attr::(), 0.5); } #[test] @@ -2226,25 +2137,24 @@ mod tests { let chain = install( TransferOpacityNode::new( - f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.5)]), - f64_record_source(&secondary_layout, 3., vec![(secondary_layout.offset_of("opacity", 0).unwrap(), 0.25)]), + f64_record_source(&carrier_layout, 2., vec![("opacity", 0.5)]), + f64_record_source(&secondary_layout, 3., vec![("opacity", 0.25)]), &carrier_layout, &secondary_layout, ), transfer_opacity_layout_meta(), &[Some(&carrier_layout), None], ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = transferred.rec(&value); - assert_eq!(unsafe { rec.element::() }, 5.); - assert_eq!(unsafe { rec.read::(transferred.offset_of(Opacity::NAME, 0).unwrap()) }, 0.125); + assert_eq!(served.element::(), 5.); + assert_eq!(served.attr::(), 0.125); let bare_secondary = f64_layout(&[]); let defaulted = install( TransferOpacityNode::new( - f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.5)]), + f64_record_source(&carrier_layout, 2., vec![("opacity", 0.5)]), bare_source(&bare_secondary, 3.), &carrier_layout, &bare_secondary, @@ -2252,15 +2162,10 @@ mod tests { transfer_opacity_layout_meta(), &[Some(&carrier_layout), None], ); - let GPoll::Final(value) = defaulted.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&defaulted, &ctx) else { panic!("expected a final record"); }; - let rec = transferred.rec(&value); - assert_eq!( - unsafe { rec.read::(transferred.offset_of(Opacity::NAME, 0).unwrap()) }, - 0.5, - "an absent secondary attribute reads its default" - ); + assert_eq!(served.attr::(), 0.5, "an absent secondary attribute reads its default"); } #[test] @@ -2276,23 +2181,17 @@ mod tests { reserve_for(&[&source_layout]); let node = install( - BoostNode::new( - f64_record_source(&source_layout, 2., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]), - factor, - &source_layout, - &factor_layout, - ), + BoostNode::new(f64_record_source(&source_layout, 2., vec![("opacity", 0.25)]), factor, &source_layout, &factor_layout), boost_layout_meta(), &[Some(&source_layout)], ); let out_layout = Node::::layout(&node).clone(); - let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through to the output"); - let GPoll::Final(value) = node.eval(&ctx) else { + out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through to the output"); + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - let rec = out_layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 6.); - assert_eq!(unsafe { rec.read::(opacity_offset) }, 0.25); + assert_eq!(served.element::(), 6.); + assert_eq!(served.attr::(), 0.25); } #[test] @@ -2307,23 +2206,17 @@ mod tests { reserve_for(&[&source_layout]); let node = install( - BoostPollNode::new( - f64_record_source(&source_layout, 2., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]), - factor, - &source_layout, - &factor_layout, - ), + BoostPollNode::new(f64_record_source(&source_layout, 2., vec![("opacity", 0.25)]), factor, &source_layout, &factor_layout), boost_poll_layout_meta(), &[Some(&source_layout)], ); let out_layout = Node::::layout(&node).clone(); - let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through the poll kernel"); - let GPoll::Final(value) = node.eval(&ctx) else { + out_layout.offset_of(Opacity::NAME, 0).expect("the primary input's fields pass through the poll kernel"); + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - let rec = out_layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 6.); - assert_eq!(unsafe { rec.read::(opacity_offset) }, 0.25); + assert_eq!(served.element::(), 6.); + assert_eq!(served.attr::(), 0.25); } #[test] @@ -2340,7 +2233,7 @@ mod tests { let node = install( OffsetNode::new( - f64_record_source(&carrier_layout, 2., vec![(carrier_layout.offset_of("opacity", 0).unwrap(), 0.25)]), + f64_record_source(&carrier_layout, 2., vec![("opacity", 0.25)]), f64_record_source(&by_layout, 40., vec![]), &carrier_layout, &by_layout, @@ -2348,13 +2241,11 @@ mod tests { offset_layout_meta(), &[Some(&carrier_layout)], ); - let out_layout = Node::::layout(&node).clone(); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - let rec = out_layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 42., "the parked borrow survives the carrier evaluation reusing its frame"); - assert_eq!(unsafe { rec.read::(out_layout.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25); + assert_eq!(served.element::(), 42., "the parked borrow survives the carrier evaluation reusing its frame"); + assert_eq!(served.attr::(), 0.25); } struct InlineRuntime; @@ -2381,7 +2272,7 @@ mod tests { let node = install( DoubleAsyncNode::new( - f64_record_source(&source_layout, 3., vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25)]), + f64_record_source(&source_layout, 3., vec![("opacity", 0.25)]), runtime, source_id, &source_layout, @@ -2392,21 +2283,19 @@ mod tests { &[Some(&source_layout)], ); let out_layout = Node::::layout(&node).clone(); - let opacity_offset = out_layout.offset_of(Opacity::NAME, 0).expect("the carrier's fields pass through the async source"); + out_layout.offset_of(Opacity::NAME, 0).expect("the carrier's fields pass through the async source"); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("an inline completion is final on the spawning eval"); }; - let rec = out_layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 6.); - assert_eq!(unsafe { rec.read::(opacity_offset) }, 0.25); + assert_eq!(served.element::(), 6.); + assert_eq!(served.attr::(), 0.25); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("a slot hit is final"); }; - let rec = out_layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 6., "the slot hit replays the element"); - assert_eq!(unsafe { rec.read::(opacity_offset) }, 0.25, "the fields re-carry on every eval"); + assert_eq!(served.element::(), 6., "the slot hit replays the element"); + assert_eq!(served.attr::(), 0.25, "the fields re-carry on every eval"); } #[test] @@ -2426,7 +2315,7 @@ mod tests { let alternate = core_types::record::RecordLift::::new(CountingValue(evals.clone())); let alternate_layout = Node::::layout(&alternate).clone(); let (content_layout, fields) = match opacity { - Some(value) => (content_layout.clone(), vec![(content_layout.offset_of("opacity", 0).unwrap(), value)]), + Some(value) => (content_layout.clone(), vec![("opacity", value)]), None => (f64_layout(&[]), vec![]), }; let node = FallbackNode::new( @@ -2482,10 +2371,10 @@ mod tests { shade_layout_meta(), &[Some(&stripped)], ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { shaded.rec(&value).element::() }, 4., "a read after the removal yields the declared default"); + assert_eq!(served.element::(), 4., "a read after the removal yields the declared default"); } #[test] @@ -2501,23 +2390,15 @@ mod tests { reserve_for(&[&source_layout, &relengthed]); let chain = install( - RelengthNode::new( - f64_record_source( - &source_layout, - 3., - vec![(source_layout.offset_of("opacity", 0).unwrap(), 0.25), (source_layout.offset_of("length", 0).unwrap(), 9.)], - ), - &source_layout, - ), + RelengthNode::new(f64_record_source(&source_layout, 3., vec![("opacity", 0.25), ("length", 9.)]), &source_layout), relength_layout_meta(), &[Some(&source_layout)], ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = relengthed.rec(&value); - assert_eq!(unsafe { rec.element::() }, 3.); - assert_eq!(unsafe { rec.read::(relengthed.offset_of(Length::NAME, 0).unwrap()) }, 6.); + assert_eq!(served.element::(), 3.); + assert_eq!(served.attr::(), 6.); } #[test] @@ -2566,7 +2447,7 @@ mod tests { assert_eq!(unsafe { translated.read::<&str>(labeled.offset_of(Label::NAME, 0).unwrap()) }, ""); } - fn f64_record_source(layout: &Layout, element: f64, fields: Vec<(usize, f64)>) -> RecordSourceNode { + fn f64_record_source(layout: &Layout, element: f64, fields: Vec<(&'static str, f64)>) -> RecordSourceNode { RecordSourceNode { layout: layout.clone(), element, @@ -2583,25 +2464,24 @@ mod tests { let layout = f64_layout(&["opacity"]); reserve_for(&[&layout, &layout]); - let monitor = crate::memo::MonitorNode::new(f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]), &layout); + let monitor = crate::memo::MonitorNode::new(f64_record_source(&layout, 4., vec![("opacity", 0.25)]), &layout); let scope = scope_fixture(&generations, &arena); { let ctx = ContextImpl::root(&scope); - let GPoll::Final(value) = monitor.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&monitor, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { layout.rec(&value).element::() }, 4.); + assert_eq!(served.element::(), 4.); } let io = Node::::serialize(&monitor).unwrap(); let snapshot = io.downcast_ref::().expect("the monitor serializes its context snapshot"); let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains"); - let GPoll::Final(value) = monitor.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&monitor, &ctx) else { panic!("expected a final record"); }; - let rec = layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 4.); - assert_eq!(unsafe { rec.read::(layout.offset_of("opacity", 0).unwrap()) }, 0.25); + assert_eq!(served.element::(), 4.); + assert_eq!(served.field::("opacity", 0), 0.25); } #[test] @@ -2620,28 +2500,26 @@ mod tests { let (condition, condition_layout) = lifted_value(second); PickNode::new( condition, - RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union), - RecordSource::new(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union), + RecordSource::new(f64_record_source(&layout_a, 1., vec![("opacity", 0.5)]), &layout_a, &union), + RecordSource::new(f64_record_source(&layout_b, 3., vec![("length", 3.)]), &layout_b, &union), &union, &condition_layout, ) }; - let GPoll::Final(value) = taken(false).eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&taken(false), &ctx) else { panic!("expected a final record"); }; - let rec = union.rec(&value); - assert_eq!(unsafe { rec.element::() }, 1.); - assert_eq!(unsafe { rec.read::(union.offset_of("opacity", 0).unwrap()) }, 0.5); - assert_eq!(unsafe { rec.read::(union.offset_of("length", 0).unwrap()) }, 0.); + assert_eq!(served.element::(), 1.); + assert_eq!(served.field::("opacity", 0), 0.5); + assert_eq!(served.field::("length", 0), 0.); - let GPoll::Final(value) = taken(true).eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&taken(true), &ctx) else { panic!("expected a final record"); }; - let rec = union.rec(&value); - assert_eq!(unsafe { rec.element::() }, 3.); - assert_eq!(unsafe { rec.read::(union.offset_of("opacity", 0).unwrap()) }, 1.); - assert_eq!(unsafe { rec.read::(union.offset_of("length", 0).unwrap()) }, 3.); + assert_eq!(served.element::(), 3.); + assert_eq!(served.field::("opacity", 0), 1.); + assert_eq!(served.field::("length", 0), 3.); } #[test] @@ -2659,19 +2537,18 @@ mod tests { let (condition, condition_layout) = lifted_value(false); let chain = HoldFirstNode::new( condition, - RecordSource::new(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union), - RecordSource::new(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union), + RecordSource::new(f64_record_source(&layout_a, 1., vec![("opacity", 0.5)]), &layout_a, &union), + RecordSource::new(f64_record_source(&layout_b, 3., vec![("length", 3.)]), &layout_b, &union), &union, &condition_layout, ); - let GPoll::Final(value) = chain.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a final record"); }; - let rec = union.rec(&value); - assert_eq!(unsafe { rec.element::() }, 1.); - assert_eq!(unsafe { rec.read::(union.offset_of("opacity", 0).unwrap()) }, 0.5); - assert_eq!(unsafe { rec.read::(union.offset_of("length", 0).unwrap()) }, 0.); + assert_eq!(served.element::(), 1.); + assert_eq!(served.field::("opacity", 0), 0.5); + assert_eq!(served.field::("length", 0), 0.); } struct RealTimeProbe { @@ -2686,16 +2563,9 @@ mod tests { Some(_) => 1., None => 0., }; - let mut value = RecordValue::zeroed(); - let dst = match self.layout.frame_bytes() { - 0 => value.as_mut_ptr(), - bytes => stack::push(bytes), - }; - unsafe { dst.cast::().write(element) }; - if self.layout.frame_bytes() != 0 { - stack::pop(dst); - value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }); - } + let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(element); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; GPoll::Final(value) } } @@ -2716,10 +2586,10 @@ mod tests { let (modification, modification_layout) = lifted_value(ContextModification::from_sources(features, &[])); let node = crate::context_modification::ContextModificationNode::new(RealTimeProbe { layout: layout.clone() }, modification, &layout, &modification_layout); assert_eq!(Node::::layout(&node), &layout); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a final record"); }; - unsafe { layout.rec(&value).element::() } + served.element::() }; assert_eq!(probed(ContextFeatures::all()), 1., "kept features stay readable under the modification"); @@ -2743,7 +2613,7 @@ mod tests { RecordSourceNode { layout: layout.clone(), element: 4., - fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)], + fields: vec![("opacity", 0.25)], partial: true, }, modification, @@ -2751,12 +2621,11 @@ mod tests { &modification_layout, ); - let GPoll::Partial(value) = node.eval(&ctx) else { + let GPoll::Partial(served) = core_types::record::capture(&node, &ctx) else { panic!("expected a partial record"); }; - let rec = layout.rec(&value); - assert_eq!(unsafe { rec.element::() }, 4.); - assert_eq!(unsafe { rec.read::(layout.offset_of("opacity", 0).unwrap()) }, 0.25); + assert_eq!(served.element::(), 4.); + assert_eq!(served.field::("opacity", 0), 0.25); } #[test] @@ -2788,10 +2657,7 @@ mod tests { let base = stack::push(0); stack::pop(base); - let chain = ForwardRecordNode::new( - RecordSource::new(f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]), &layout, &layout.clone()), - &layout, - ); + let chain = ForwardRecordNode::new(RecordSource::new(f64_record_source(&layout, 4., vec![("opacity", 0.25)]), &layout, &layout.clone()), &layout); let GPoll::Final(value) = chain.eval(&ctx) else { panic!("expected a final record"); @@ -2826,10 +2692,10 @@ mod tests { &layout, ); - let GPoll::Partial(value) = chain.eval(&ctx) else { + let GPoll::Partial(served) = core_types::record::capture(&chain, &ctx) else { panic!("expected a partial record"); }; - assert_eq!(unsafe { layout.rec(&value).element::() }, 4.); + assert_eq!(served.element::(), 4.); } struct CountingValue(std::sync::Arc); @@ -2855,14 +2721,14 @@ mod tests { let layout = Node::::layout(&lift).clone(); let memo = crate::memo::MemoizeNode::new(lift, &layout); - let GPoll::Final(value) = memo.eval(&ctx) else { + let GPoll::Final(served) = core_types::record::capture(&memo, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { layout.rec(&value).element::() }, 21.); - let GPoll::Final(value) = memo.eval(&ctx) else { + assert_eq!(served.element::(), 21.); + let GPoll::Final(served) = core_types::record::capture(&memo, &ctx) else { panic!("expected a final record"); }; - assert_eq!(unsafe { layout.rec(&value).element::() }, 21.); + assert_eq!(served.element::(), 21.); assert_eq!(evals.load(std::sync::atomic::Ordering::Relaxed), 1, "a context hit must not re-evaluate the edge"); } @@ -2879,7 +2745,7 @@ mod tests { let source = RecordSourceNode { layout: layout.clone(), element: 4., - fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.5)], + fields: vec![("opacity", 0.5)], partial: true, }; let memo = crate::memo::MemoizeNode::new(source, &layout); @@ -2887,10 +2753,10 @@ mod tests { let GPoll::Partial(_) = memo.eval(&ctx) else { panic!("expected a partial record"); }; - let GPoll::Partial(value) = memo.eval(&ctx) else { + let GPoll::Partial(served) = core_types::record::capture(&memo, &ctx) else { panic!("expected the replay to keep the partial finality"); }; - assert_eq!(unsafe { layout.rec(&value).read::(layout.offset_of("opacity", 0).unwrap()) }, 0.5); + assert_eq!(served.field::("opacity", 0), 0.5); } #[test] diff --git a/node-graph/nodes/graphic/src/record.rs b/node-graph/nodes/graphic/src/record.rs index 34d2ef338b..8ebe033e47 100644 --- a/node-graph/nodes/graphic/src/record.rs +++ b/node-graph/nodes/graphic/src/record.rs @@ -226,7 +226,7 @@ mod tests { use core_types::context::{ContextImpl, EvalScope, ExtractArena, ExtractIndices}; use core_types::list::{Item, List}; use core_types::node::Node; - use core_types::record::{self, Layout, Rec, RecordSource, RecordValue, stack}; + use core_types::record::{self, Layout, RecordSource, RecordValue, stack}; struct ValueNode(T); @@ -248,15 +248,11 @@ mod tests { fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { let (graphic, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()]; - let dst = stack::push(self.layout.frame_bytes()); - if unsafe { record::write_element(dst, graphic.clone(), input.arena()) }.is_none() { - return GPoll::arena_exhausted(); - } - unsafe { - dst.add(self.layout.offset_of(::NAME, 0).unwrap()).cast::().write(*transform); - } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(graphic.clone()); + frame.attr::(*transform); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -380,15 +376,11 @@ mod tests { let lane = input.innermost_index(); let graphic = text(&format!("{label}{lane}")); let translated = DAffine2::from_translation(glam::DVec2::new(lane as f64, 0.)); - let dst = stack::push(self.layout.frame_bytes()); - if unsafe { record::write_element(dst, graphic, input.arena()) }.is_none() { - return GPoll::arena_exhausted(); - } - unsafe { - dst.add(self.layout.offset_of(::NAME, 0).unwrap()).cast::().write(translated); - } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(graphic); + frame.attr::(translated); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -453,17 +445,13 @@ mod tests { assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0))); let head = ctx.index_head(); - let offset = out.offset_of(::NAME, 0).unwrap(); for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(text_of(unsafe { record::borrow_element::(rec) }), label, "lane {lane}"); - let transform: DAffine2 = unsafe { rec.read(offset) }; + assert_eq!(text_of(&record.element::()), label, "lane {lane}"); + let transform: DAffine2 = record.attr::(); assert_eq!(transform.translation.x, x, "lane {lane}"); - unsafe { stack::rewind(mark) }; } } @@ -520,23 +508,20 @@ mod tests { assert_eq!(composed.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0))); let head = ctx.index_head(); - let offset = flat_out.offset_of(::NAME, 0).unwrap(); for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() { - let mark = stack::sp(); let scoped = ctx.promoted(&head, lane as u64); - let GPoll::Final(direct) = flat.eval(&scoped) else { + let GPoll::Final(direct) = record::capture(&flat, &scoped) else { panic!("expected a final record from flat_map"); }; - let direct_label = text_of(unsafe { record::borrow_element::(flat_out.rec(&direct)) }).to_string(); - let direct_x: DAffine2 = unsafe { flat_out.rec(&direct).read(offset) }; - let GPoll::Final(value) = composed.eval(&scoped) else { + let direct_label = text_of(&direct.element::()).to_string(); + let direct_x: DAffine2 = direct.attr::(); + let GPoll::Final(value) = record::capture(&composed, &scoped) else { panic!("expected a final record from flatten(map)"); }; - assert_eq!(text_of(unsafe { record::borrow_element::(composed_out.rec(&value)) }), direct_label, "lane {lane}"); - let composed_x: DAffine2 = unsafe { composed_out.rec(&value).read(offset) }; + assert_eq!(text_of(&value.element::()), direct_label, "lane {lane}"); + let composed_x: DAffine2 = value.attr::(); assert_eq!(composed_x, direct_x, "lane {lane}"); assert_eq!((direct_label.as_str(), direct_x.translation.x), (label, x), "lane {lane}"); - unsafe { stack::rewind(mark) }; } } @@ -589,17 +574,14 @@ mod tests { assert_eq!(batch.len(), 5); let offset = out.offset_of(::NAME, 0).unwrap(); for lane in 0..5 { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - let single = text_of(unsafe { record::borrow_element::(rec) }).to_string(); + let single = text_of(&record.element::()).to_string(); assert_eq!(text_of(unsafe { record::borrow_element::(batch.get(lane).rec()) }), single, "lane {lane}"); let batched: DAffine2 = unsafe { batch.get(lane).rec().read(offset) }; - let direct: DAffine2 = unsafe { rec.read(offset) }; + let direct: DAffine2 = record.attr::(); assert_eq!(batched, direct, "lane {lane}"); - unsafe { stack::rewind(mark) }; } } @@ -617,27 +599,21 @@ mod tests { assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3))); let head = ctx.index_head(); - let offset = out.offset_of(::NAME, 0).unwrap(); // Lane 2 is the unexpanded subgroup H, riding as a leaf at G's depth. let expected: [(&str, f64); 2] = [("a", 1.), ("b", 20.5)]; for (lane, &(label, x)) in expected.iter().enumerate() { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(text_of(unsafe { record::borrow_element::(rec) }), label, "lane {lane}"); - let transform: DAffine2 = unsafe { rec.read(offset) }; + assert_eq!(text_of(&record.element::()), label, "lane {lane}"); + let transform: DAffine2 = record.attr::(); assert_eq!(transform.translation.x, x, "lane {lane}"); - unsafe { stack::rewind(mark) }; } - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, 2)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, 2)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - let Graphic::Graphic(children) = (unsafe { record::borrow_element::(rec) }) else { + let Graphic::Graphic(children) = record.element::() else { panic!("lane 2 keeps the subgroup element"); }; assert_eq!(children.len(), 1); @@ -647,9 +623,8 @@ mod tests { 300., "embedded transforms ride untouched" ); - let transform: DAffine2 = unsafe { rec.read(offset) }; + let transform: DAffine2 = record.attr::(); assert_eq!(transform.translation.x, 4000.5); - unsafe { stack::rewind(mark) }; } #[test] @@ -777,10 +752,10 @@ mod tests { fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { let color = self.colors[input.innermost_index() as usize]; - let dst = stack::push(self.layout.frame_bytes()); - unsafe { dst.cast::().write(color) }; - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = record::FrameBuilder::new(&self.layout, input.arena()); + frame.element(color); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -802,10 +777,10 @@ mod tests { let build = |colors: Vec| install_flip(ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout), &out); let stops_of = |colors: Vec| { let node = build(colors); - let GPoll::Final(value) = node.eval(&ctx) else { + let GPoll::Final(record) = record::capture(&node, &ctx) else { panic!("expected a final record"); }; - unsafe { record::borrow_element::(out.rec(&value)) }.clone() + record.element::() }; let three = stops_of(vec![Color::BLACK, Color::WHITE, Color::BLACK]); @@ -880,21 +855,16 @@ mod tests { // One row holding the wrapped group flattens back to the lanes, the // group's identity transform composed onto each child's. let node = build!(layout, vec![(group, DAffine2::IDENTITY)], false); - let out = Node::::layout(&node).clone(); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(2))); let head = ctx.index_head(); - let offset = out.offset_of(::NAME, 0).unwrap(); for (lane, &(label, x)) in [("a", 1.), ("b", 2.)].iter().enumerate() { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(text_of(unsafe { record::borrow_element::(rec) }), label, "lane {lane}"); - let transform: DAffine2 = unsafe { rec.read(offset) }; + assert_eq!(text_of(&record.element::()), label, "lane {lane}"); + let transform: DAffine2 = record.attr::(); assert_eq!(transform.translation.x, x, "lane {lane}"); - unsafe { stack::rewind(mark) }; } } @@ -909,22 +879,17 @@ mod tests { rows.push((group(vec![]), translation(9.))); let layout = graphic_layout(); let node = build!(layout, rows, true); - let out = Node::::layout(&node).clone(); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves"); let head = ctx.index_head(); - let offset = out.offset_of(::NAME, 0).unwrap(); let expected: [(&str, f64); 3] = [("a", 1.), ("b", 20.5), ("c", 4300.5)]; for (lane, &(label, x)) in expected.iter().enumerate() { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - assert_eq!(text_of(unsafe { record::borrow_element::(rec) }), label, "lane {lane}"); - let transform: DAffine2 = unsafe { rec.read(offset) }; + assert_eq!(text_of(&record.element::()), label, "lane {lane}"); + let transform: DAffine2 = record.attr::(); assert_eq!(transform.translation.x, x, "lane {lane}"); - unsafe { stack::rewind(mark) }; } } @@ -949,17 +914,14 @@ mod tests { assert_eq!(batch.len(), 3); let offset = out.offset_of(::NAME, 0).unwrap(); for lane in 0..3 { - let mark = stack::sp(); - let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else { + let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else { panic!("expected a final record"); }; - let rec = out.rec(&value); - let single = text_of(unsafe { record::borrow_element::(rec) }).to_string(); + let single = text_of(&record.element::()).to_string(); assert_eq!(text_of(unsafe { record::borrow_element::(batch.get(lane).rec()) }), single, "lane {lane}"); let batched: DAffine2 = unsafe { batch.get(lane).rec().read(offset) }; - let direct: DAffine2 = unsafe { rec.read(offset) }; + let direct: DAffine2 = record.attr::(); assert_eq!(batched, direct, "lane {lane}"); - unsafe { stack::rewind(mark) }; } } } diff --git a/node-graph/nodes/path-bool/src/lib.rs b/node-graph/nodes/path-bool/src/lib.rs index efffaf0c97..6ace3f5f16 100644 --- a/node-graph/nodes/path-bool/src/lib.rs +++ b/node-graph/nodes/path-bool/src/lib.rs @@ -562,13 +562,10 @@ mod tests { #[test] fn the_native_flatten_reads_lanes_groups_and_reach() { let inner_vector = square(DVec2::ZERO); - let inner_layout = core_types::record::Layout::default().with_writes(0, core_types::record::element_write_hashed::(), &[]); - let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; - // SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element - // stores its reference. - unsafe { inner_bytes.as_mut_ptr().cast::<&Vector>().write(&inner_vector) }; - // SAFETY: `inner_bytes` holds one lane of `inner_layout` at its stride. - let inner_item = unsafe { core_types::record::GroupItem::from_resident(core_types::node::RecordBatch::new(inner_bytes.as_ptr(), 1, &inner_layout)) }; + let arena = core_types::arena::Arena::new(1 << 16).unwrap(); + let mut builder = core_types::record::RunBuilder::new(&arena, core_types::record::element_write_hashed::(), &[], 1).unwrap(); + builder.push(inner_vector.clone()).unwrap(); + let inner_item = builder.finish(); let mut top = List::new(); top.push(Item::new_from_element(Graphic::Vector(square(DVec2::ZERO)))); diff --git a/node-graph/nodes/repeat/src/repeat_nodes.rs b/node-graph/nodes/repeat/src/repeat_nodes.rs index 6b69fcec04..ce9ddfa224 100644 --- a/node-graph/nodes/repeat/src/repeat_nodes.rs +++ b/node-graph/nodes/repeat/src/repeat_nodes.rs @@ -181,10 +181,9 @@ mod test { use super::*; use core_types::SourceId; use core_types::arena::Arena; - use core_types::attribute::Attribute as AttributeMarker; use core_types::context::{ContextImpl, EvalScope}; use core_types::node::Node; - use core_types::record::{FieldWrite, Layout, Rec, RecordSource, RecordValue, element_write, stack}; + use core_types::record::{FieldWrite, FrameBuilder, Layout, RecordSource, RecordValue, capture, element_write, stack}; use vector_types::subpath::Subpath; struct ValueNode(T); @@ -206,14 +205,13 @@ mod test { impl<'e> Node> for TransformSource { type Output = RecordValue<'e>; - fn eval(&self, _input: &ContextImpl<'e>) -> GPoll> { - let dst = stack::push(self.layout.frame_bytes()); - unsafe { - dst.cast::().write(self.element); - dst.add(self.layout.offset_of(TransformAttr::NAME, 0).unwrap()).cast::().write(self.transform); - } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { + use core_types::context::ExtractArena; + let mut frame = FrameBuilder::new(&self.layout, input.arena()); + frame.element(self.element); + frame.attr::(self.transform); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } } @@ -233,16 +231,11 @@ mod test { fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { use core_types::context::{ExtractArena, ExtractIndices}; let (vector, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()]; - let dst = stack::push(self.layout.frame_bytes()); - // SAFETY: dst is the claimed frame of this layout; offsets are the layout's own. - unsafe { - if core_types::record::write_element(dst, vector.clone(), input.arena()).is_none() { - return GPoll::Error(Box::new(core_types::gpoll::GraphError::new("arena exhausted"))); - } - core_types::record::write_field(dst, self.layout.offset_of(::NAME, 0).unwrap(), *transform); - } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = FrameBuilder::new(&self.layout, input.arena()); + frame.element(vector.clone()); + frame.attr::(*transform); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll { @@ -266,16 +259,13 @@ mod test { type Output = RecordValue<'e>; fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { - use core_types::context::ExtractPosition; + use core_types::context::{ExtractArena, ExtractPosition}; let position = input.try_position().and_then(|mut positions| positions.next()).unwrap_or(DVec2::ZERO); - let dst = stack::push(self.layout.frame_bytes()); - // SAFETY: dst is the claimed frame of this layout; offsets are the layout's own. - unsafe { - dst.cast::().write(position.x); - core_types::record::write_field(dst, self.layout.offset_of(::NAME, 0).unwrap(), DAffine2::IDENTITY); - } - stack::pop(dst); - GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) + let mut frame = FrameBuilder::new(&self.layout, input.arena()); + frame.element(position.x); + frame.attr::(DAffine2::IDENTITY); + let Some(value) = frame.finish() else { return GPoll::arena_exhausted() }; + GPoll::Final(value) } fn layout(&self) -> &Layout { @@ -315,19 +305,15 @@ mod test { let head = ctx.index_head(); for copy in 0..3u64 { - let mark = stack::sp(); let lane = ctx.promoted(&head, copy); - let GPoll::Final(value) = node.eval(&lane) else { + let GPoll::Final(record) = capture(&node, &lane) else { panic!("expected a final record"); }; - let rec = leveled.rec(&value); - assert_eq!(unsafe { rec.element::() }, 7.); + assert_eq!(record.element::(), 7.); // Zero angle, direction (10, 0), count 3: copy `j` steps j * (5, 0) // past the row's own (5, 5) translation. - let composed: DAffine2 = unsafe { rec.read(leveled.offset_of(TransformAttr::NAME, 0).unwrap()) }; + let composed: DAffine2 = record.attr::(); assert_eq!(composed, DAffine2::from_translation(DVec2::new(5. + copy as f64 * 5., 5.))); - // SAFETY: the element and transform were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } } @@ -348,25 +334,20 @@ mod test { let mut node = RepeatRadialNode::new(RecordSource::new(content, &layout, &layout), ValueNode(90.0f64), ValueNode(2.0f64), ValueNode(4u32), &layout); Node::::set_layout(&mut node, repeat_radial_layout_meta().resolve(&[Some(&layout)])); - let leveled = Node::::layout(&node).clone(); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(4))); let head = ctx.index_head(); for copy in 0..4u64 { - let mark = stack::sp(); let lane = ctx.promoted(&head, copy); - let GPoll::Final(value) = node.eval(&lane) else { + let GPoll::Final(record) = capture(&node, &lane) else { panic!("expected a final record"); }; - let rec = leveled.rec(&value); - assert_eq!(unsafe { rec.element::() }, 7.); + assert_eq!(record.element::(), 7.); // The kernel's own formula, so the float operations match exactly. let step = DAffine2::from_angle((TAU / 4.) * copy as f64 + 90.0f64.to_radians()) * DAffine2::from_translation(2. * DVec2::Y); let expected = DAffine2::from_translation(local.translation) * step * DAffine2::from_mat2(local.matrix2); - let composed: DAffine2 = unsafe { rec.read(leveled.offset_of(TransformAttr::NAME, 0).unwrap()) }; + let composed: DAffine2 = record.attr::(); assert_eq!(composed, expected); - // SAFETY: the element and transform were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } } @@ -400,18 +381,14 @@ mod test { let head = ctx.index_head(); for (flat, &point) in expected.iter().enumerate() { - let mark = stack::sp(); let lane = ctx.promoted(&head, flat as u64); - let GPoll::Final(value) = node.eval(&lane) else { + let GPoll::Final(record) = capture(&node, &lane) else { panic!("expected a final record"); }; - let rec = leveled.rec(&value); // The content saw the pushed position, and the output transform lands on it. - assert_eq!(unsafe { rec.element::() }, point.x); - let composed: DAffine2 = unsafe { rec.read(leveled.offset_of(::NAME, 0).unwrap()) }; + assert_eq!(record.element::(), point.x); + let composed: DAffine2 = record.attr::(); assert_eq!(composed.translation, point); - // SAFETY: the element and transform were read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } } @@ -432,21 +409,17 @@ mod test { let mut node = RepeatOnPointsNode::new(RecordSource::new(content, &content_layout, &content_layout), points, ValueNode(true), &content_layout); Node::::set_layout(&mut node, repeat_on_points_layout_meta().resolve(&[Some(&content_layout)])); - let leveled = Node::::layout(&node).clone(); let mut expected = positions.clone(); expected.reverse(); let head = ctx.index_head(); for (flat, &point) in expected.iter().enumerate() { - let mark = stack::sp(); let lane = ctx.promoted(&head, flat as u64); - let GPoll::Final(value) = node.eval(&lane) else { + let GPoll::Final(record) = capture(&node, &lane) else { panic!("expected a final record"); }; - let composed: DAffine2 = unsafe { leveled.rec(&value).read(leveled.offset_of(::NAME, 0).unwrap()) }; + let composed: DAffine2 = record.attr::(); assert_eq!(composed.translation, point); - // SAFETY: the transform was read out above, so no borrow into this lane's frames remains. - unsafe { stack::rewind(mark) }; } } }