Migrate test sources and assertions onto the safe record builders

This commit is contained in:
Dennis Kobert
2026-08-27 21:17:31 +00:00
parent 5181c74205
commit d814b610da
8 changed files with 431 additions and 723 deletions
+33 -41
View File
@@ -2456,21 +2456,15 @@ mod tests {
use crate::lane::LaneSource; use crate::lane::LaneSource;
use glam::DAffine2; use glam::DAffine2;
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[FieldWrite::of::<Transform>(0), FieldWrite::of::<Opacity>(0)]); let arena = crate::arena::Arena::new(4096).unwrap();
let stride = layout.lane_stride();
let transform = DAffine2::from_translation((5., 6.).into()); let transform = DAffine2::from_translation((5., 6.).into());
let mut bytes = vec![0u8; stride * 2]; let mut builder = RunBuilder::new(&arena, element_write::<f64>(), &[FieldWrite::of::<Transform>(0), FieldWrite::of::<Opacity>(0)], 2).unwrap();
for lane in 0..2 { for lane in 0..2 {
// SAFETY: `bytes` is `stride` per lane, and the offsets come from `layout`. let lane = builder.push(lane as f64).unwrap();
unsafe { builder.attr::<Transform>(lane, transform);
let base = bytes.as_mut_ptr().add(lane * stride); builder.attr::<Opacity>(lane, 0.25);
base.cast::<f64>().write(lane as f64);
base.add(layout.offset_of(Transform::NAME, 0).unwrap()).cast::<DAffine2>().write(transform);
base.add(layout.offset_of(Opacity::NAME, 0).unwrap()).cast::<f64>().write(0.25);
}
} }
// SAFETY: `bytes` holds two lanes of `layout` at its stride. let item = builder.finish();
let item = unsafe { GroupItem::from_resident(crate::node::RecordBatch::new(bytes.as_ptr(), 2, &layout)) };
let run = RunView::<f64>::new(&item).expect("the run holds f64 elements"); let run = RunView::<f64>::new(&item).expect("the run holds f64 elements");
let mut list = crate::list::List::new_from_element(0f64); let mut list = crate::list::List::new_from_element(0f64);
@@ -2493,10 +2487,10 @@ mod tests {
use crate::attribute::Opacity; use crate::attribute::Opacity;
use crate::lane::LaneSource; use crate::lane::LaneSource;
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[]); let arena = crate::arena::Arena::new(1024).unwrap();
let bytes = vec![0u8; layout.lane_stride()]; let mut builder = RunBuilder::new(&arena, element_write::<f64>(), &[], 1).unwrap();
// SAFETY: `bytes` holds one lane of `layout` at its stride. builder.push(0f64).unwrap();
let item = unsafe { GroupItem::from_resident(crate::node::RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; let item = builder.finish();
let run = RunView::<f64>::new(&item).expect("the run holds f64 elements"); let run = RunView::<f64>::new(&item).expect("the run holds f64 elements");
assert_eq!(run.attr::<Opacity>(0), 1.); assert_eq!(run.attr::<Opacity>(0), 1.);
@@ -2708,6 +2702,8 @@ mod tests {
#[test] #[test]
fn a_run_builds_from_a_legacy_list_and_serves_its_rows() { 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")); 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"))); list.push(crate::list::Item::new_from_element(String::from("row 1")));
let transform = glam::DAffine2::from_translation(glam::DVec2::new(3., 4.)); 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 arena = crate::arena::Arena::new(1 << 16).unwrap();
let item = GroupItem::from_list(list, &arena).unwrap(); let item = GroupItem::from_list(list, &arena).unwrap();
assert_eq!(item.len(), 2); assert_eq!(item.len(), 2);
let layout = item.layout().clone(); let run = RunView::<String>::new(&item).expect("the run holds string elements");
let lanes = item.lanes();
let rec = lanes.get(0).rec(); assert_eq!(run.element(0).map(String::as_str), Some("row 0"));
assert_eq!(unsafe { read_element::<String>(rec) }, "row 0"); assert_eq!(run.attr::<crate::attribute::Transform>(0), transform);
assert_eq!(unsafe { rec.read::<glam::DAffine2>(layout.offset_of(crate::ATTR_TRANSFORM, 0).unwrap()) }, transform); assert_eq!(run.attr::<crate::attribute::Name>(0), "first");
assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, "first"); assert!(run.attr::<crate::attribute::EditorLayerPath>(0).is_empty());
assert!(unsafe { rec.read::<&[crate::uuid::NodeId]>(layout.offset_of(crate::ATTR_EDITOR_LAYER_PATH, 0).unwrap()) }.is_empty());
let rec = lanes.get(1).rec(); assert_eq!(run.element(1).map(String::as_str), Some("row 1"));
assert_eq!(unsafe { read_element::<String>(rec) }, "row 1");
// A lane without the value reads the census default, not garbage. // A lane without the value reads the census default, not garbage.
assert_eq!(unsafe { rec.read::<glam::DAffine2>(layout.offset_of(crate::ATTR_TRANSFORM, 0).unwrap()) }, glam::DAffine2::IDENTITY); assert_eq!(run.attr::<crate::attribute::Transform>(1), glam::DAffine2::IDENTITY);
assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, ""); assert_eq!(run.attr::<crate::attribute::Name>(1), "");
assert_eq!( assert_eq!(run.attr::<crate::attribute::EditorLayerPath>(1), &[crate::uuid::NodeId(7), crate::uuid::NodeId(9)]);
unsafe { rec.read::<&[crate::uuid::NodeId]>(layout.offset_of(crate::ATTR_EDITOR_LAYER_PATH, 0).unwrap()) }, assert_eq!(run.attr::<crate::attribute::MaxWidth>(1), Some(12.5));
&[crate::uuid::NodeId(7), crate::uuid::NodeId(9)]
);
assert_eq!(unsafe { rec.read::<Option<f64>>(layout.offset_of("max_width", 0).unwrap()) }, Some(12.5));
} }
#[test] #[test]
fn a_built_run_replays_after_the_source_dies() { fn a_built_run_replays_after_the_source_dies() {
use crate::lane::LaneSource;
let owned = { let owned = {
let arena = crate::arena::Arena::new(1 << 16).unwrap(); let arena = crate::arena::Arena::new(1 << 16).unwrap();
let mut list = crate::list::List::new_from_element(String::from("element")); 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 arena = crate::arena::Arena::new(1 << 16).unwrap();
let replayed = owned.replay(&arena).unwrap(); let replayed = owned.replay(&arena).unwrap();
let layout = replayed.layout().clone(); let run = RunView::<String>::new(&replayed).expect("the run holds string elements");
let rec = replayed.lanes().get(0).rec(); assert_eq!(run.element(0).map(String::as_str), Some("element"));
assert_eq!(unsafe { read_element::<String>(rec) }, "element"); assert_eq!(run.attr::<crate::attribute::Name>(0), "label");
assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, "label"); assert_eq!(run.attr::<crate::attribute::EditorLayerPath>(0), &[crate::uuid::NodeId(3)]);
assert_eq!(unsafe { rec.read::<&[crate::uuid::NodeId]>(layout.offset_of(crate::ATTR_EDITOR_LAYER_PATH, 0).unwrap()) }, &[crate::uuid::NodeId(3)]);
} }
#[test] #[test]
fn a_wrong_typed_or_undeclared_column_leaves_the_default() { 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")); 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(crate::ATTR_OPACITY, 0, String::from("not an f64"));
list.set_attribute("never_declared", 0, 5u32); list.set_attribute("never_declared", 0, 5u32);
let arena = crate::arena::Arena::new(1 << 16).unwrap(); let arena = crate::arena::Arena::new(1 << 16).unwrap();
let item = GroupItem::from_list(list, &arena).unwrap(); let item = GroupItem::from_list(list, &arena).unwrap();
let layout = item.layout().clone(); assert!(item.layout().offset_of("never_declared", 0).is_none(), "an undeclared key has no field form");
assert!(layout.offset_of("never_declared", 0).is_none(), "an undeclared key has no field form"); let run = RunView::<String>::new(&item).expect("the run holds string elements");
let rec = item.lanes().get(0).rec(); assert_eq!(run.attr::<crate::attribute::Opacity>(0), 1., "the wrong-typed value reads as absent");
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of(crate::ATTR_OPACITY, 0).unwrap()) }, 1., "the wrong-typed value reads as absent");
} }
#[test] #[test]
+78 -143
View File
@@ -1494,8 +1494,7 @@ mod run_tests {
use core_types::attribute::Attribute; use core_types::attribute::Attribute;
use core_types::bounds::BoundingBox; use core_types::bounds::BoundingBox;
use core_types::lane::LaneSource; use core_types::lane::LaneSource;
use core_types::node::RecordBatch; use core_types::record::{FieldWrite, RunBuilder, RunView, element_write_hashed};
use core_types::record::{FieldWrite, GroupItem, Layout, RunView, element_write_hashed};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use vector_types::subpath::Subpath; use vector_types::subpath::Subpath;
use vector_types::vector::PointId; use vector_types::vector::PointId;
@@ -1509,17 +1508,11 @@ mod run_tests {
let paint = List::new_from_element(Graphic::Color(Color::BLACK)); let paint = List::new_from_element(Graphic::Color(Color::BLACK));
let vector = unit_square_at(DVec2::ZERO); let vector = unit_square_at(DVec2::ZERO);
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let mut bytes = vec![0u8; layout.lane_stride()]; let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
// SAFETY: `bytes` is one lane of `layout`; a parked element stores its let lane = builder.push(vector.clone()).unwrap();
// reference, and the fill field stores the marker's value form. builder.attr::<Fill>(lane, Some(&paint));
unsafe { let item = builder.finish();
let base = bytes.as_mut_ptr();
base.cast::<&Vector>().write(&vector);
base.add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::<Option<&List<Graphic>>>().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 run = RunView::<Vector>::new(&item).expect("the run holds vector elements"); let run = RunView::<Vector>::new(&item).expect("the run holds vector elements");
assert_eq!(run.attr::<Fill>(0), Some(&paint)); assert_eq!(run.attr::<Fill>(0), Some(&paint));
@@ -1535,26 +1528,17 @@ mod run_tests {
let paint = List::new_from_element(Graphic::Color(Color::BLACK)); let paint = List::new_from_element(Graphic::Color(Color::BLACK));
let vector = unit_square_at(DVec2::ZERO); let vector = unit_square_at(DVec2::ZERO);
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let source = core_types::arena::Arena::new(1 << 16).unwrap();
let mut bytes = vec![0u8; layout.lane_stride()]; let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
// SAFETY: `bytes` is one lane of `layout`; a parked element stores its let lane = builder.push(vector.clone()).unwrap();
// reference, and the fill field stores the marker's value form. builder.attr::<Fill>(lane, Some(&paint));
unsafe { let group = core_types::record::Group {
let base = bytes.as_mut_ptr(); row: None,
base.cast::<&Vector>().write(&vector); content: builder.finish(),
base.add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::<Option<&List<Graphic>>>().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)
}; };
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 arena = core_types::arena::Arena::new(1 << 16).unwrap();
let resident = map_groups_to_resident(&owned, &arena).expect("the arena holds the replay"); let resident = map_groups_to_resident(&owned, &arena).expect("the arena holds the replay");
@@ -1565,35 +1549,23 @@ mod run_tests {
#[test] #[test]
fn an_owned_group_replays_nested_groups_through_the_element_glue() { fn an_owned_group_replays_nested_groups_through_the_element_glue() {
let vector = unit_square_at(DVec2::ZERO); let vector = unit_square_at(DVec2::ZERO);
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]); let source = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[], 1).unwrap();
// SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element builder.push(vector.clone()).unwrap();
// 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 nested = Graphic::Group(core_types::record::Group { let nested = Graphic::Group(core_types::record::Group {
row: None, row: None,
content: inner_item, content: builder.finish(),
}); });
let outer_layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[]); let mut builder = RunBuilder::new(&source, element_write_hashed::<Graphic>(), &[], 1).unwrap();
let mut outer_bytes = vec![0u8; outer_layout.lane_stride()]; builder.push(nested).unwrap();
// SAFETY: `outer_bytes` is one lane of `outer_layout`; a parked element let group = core_types::record::Group {
// stores its reference. row: None,
unsafe { outer_bytes.as_mut_ptr().cast::<&Graphic>().write(&nested) }; content: builder.finish(),
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)
}; };
outer_bytes.fill(u8::MAX); let expected = group_to_legacy_list(&group);
inner_bytes.fill(u8::MAX); let owned = map_groups_to_owned(&Graphic::Group(group));
drop(source);
let arena = core_types::arena::Arena::new(1 << 16).unwrap(); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let resident = map_groups_to_resident(&owned, &arena).expect("the arena holds the replay"); 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); assert_eq!(group_to_legacy_list(group), expected);
} }
fn native_group_paint(vector: &Vector, inner_layout: &Layout, inner_bytes: &mut Vec<u8>) -> List<Graphic> { fn native_group_paint(vector: &Vector, arena: &core_types::arena::Arena) -> List<Graphic> {
// SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element let mut builder = RunBuilder::new(arena, element_write_hashed::<Vector>(), &[], 1).unwrap();
// stores its reference. builder.push(vector.clone()).unwrap();
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)) };
List::new_from_element(Graphic::Group(core_types::record::Group { List::new_from_element(Graphic::Group(core_types::record::Group {
row: None, row: None,
content: inner_item, content: builder.finish(),
})) }))
} }
#[test] #[test]
fn an_owned_run_deep_copies_graphic_list_fields() { fn an_owned_run_deep_copies_graphic_list_fields() {
let inner_vector = unit_square_at(DVec2::ZERO); let inner_vector = unit_square_at(DVec2::ZERO);
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]); let source = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let paint = native_group_paint(&inner_vector, &source);
let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes);
let vector = unit_square_at(DVec2::new(4., 4.)); let vector = unit_square_at(DVec2::new(4., 4.));
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
let mut bytes = vec![0u8; layout.lane_stride()]; let lane = builder.push(vector.clone()).unwrap();
// SAFETY: `bytes` is one lane of `layout`; a parked element stores its builder.attr::<Fill>(lane, Some(&paint));
// reference, and the fill field stores the marker's value form. let item = builder.finish();
unsafe { let owned = item.copy_out();
bytes.as_mut_ptr().cast::<&Vector>().write(&vector); let expected = map_groups_to_legacy(paint.element(0).unwrap());
bytes.as_mut_ptr().add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::<Option<&List<Graphic>>>().write(Some(&paint)); drop(item);
}
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);
drop(paint); drop(paint);
drop(source);
let arena = core_types::arena::Arena::new(1 << 16).unwrap(); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let replayed = owned.replay(&arena).expect("the arena holds the replay"); let replayed = owned.replay(&arena).expect("the arena holds the replay");
@@ -1648,25 +1609,22 @@ mod run_tests {
#[test] #[test]
fn an_owned_record_deep_copies_graphic_list_fields() { fn an_owned_record_deep_copies_graphic_list_fields() {
let inner_vector = unit_square_at(DVec2::ZERO); let inner_vector = unit_square_at(DVec2::ZERO);
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]); let source = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let paint = native_group_paint(&inner_vector, &source);
let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes);
let vector = unit_square_at(DVec2::new(4., 4.)); let vector = unit_square_at(DVec2::new(4., 4.));
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
let lane = builder.push(vector.clone()).unwrap();
builder.attr::<Fill>(lane, Some(&paint));
let item = builder.finish();
let layout = item.layout().clone();
let offset = layout.offset_of(Fill::NAME, 0).unwrap(); let offset = layout.offset_of(Fill::NAME, 0).unwrap();
let mut bytes = vec![0u8; layout.lane_stride()]; // SAFETY: the item's lane is a live record of `layout`.
// SAFETY: as in the run test above. let owned = unsafe { core_types::record::OwnedRecord::copy_out(&layout, item.lanes().get(0).rec()) };
unsafe {
bytes.as_mut_ptr().cast::<&Vector>().write(&vector);
bytes.as_mut_ptr().add(offset).cast::<Option<&List<Graphic>>>().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())) };
let expected = map_groups_to_legacy(paint.element(0).unwrap()); let expected = map_groups_to_legacy(paint.element(0).unwrap());
bytes.fill(u8::MAX); drop(item);
inner_bytes.fill(u8::MAX);
drop(paint); drop(paint);
drop(source);
let arena = core_types::arena::Arena::new(1 << 16).unwrap(); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
core_types::record::stack::reserve(layout.frame_bytes()); core_types::record::stack::reserve(layout.frame_bytes());
@@ -1679,28 +1637,19 @@ mod run_tests {
#[test] #[test]
fn a_legacy_list_owns_its_paint_attr_content() { fn a_legacy_list_owns_its_paint_attr_content() {
let inner_vector = unit_square_at(DVec2::ZERO); let inner_vector = unit_square_at(DVec2::ZERO);
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]); let source = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let paint = native_group_paint(&inner_vector, &source);
let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes);
let vector = unit_square_at(DVec2::new(4., 4.)); let vector = unit_square_at(DVec2::new(4., 4.));
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let mut builder = RunBuilder::new(&source, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
let mut bytes = vec![0u8; layout.lane_stride()]; let lane = builder.push(vector.clone()).unwrap();
// SAFETY: `bytes` is one lane of `layout`; a parked element stores its builder.attr::<Fill>(lane, Some(&paint));
// reference, and the fill field stores the marker's value form. let item = builder.finish();
unsafe { let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list");
bytes.as_mut_ptr().cast::<&Vector>().write(&vector); let expected = map_groups_to_legacy(paint.element(0).unwrap());
bytes.as_mut_ptr().add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::<Option<&List<Graphic>>>().write(Some(&paint)); drop(item);
}
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::<Vector>(&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);
drop(paint); drop(paint);
drop(source);
let served = legacy.attribute::<Option<List<Graphic>>>(Fill::NAME, 0).expect("the fill attribute rides the list"); let served = legacy.attribute::<Option<List<Graphic>>>(Fill::NAME, 0).expect("the fill attribute rides the list");
let served = served.as_ref().expect("the fill is present"); let served = served.as_ref().expect("the fill is present");
@@ -1710,18 +1659,13 @@ mod run_tests {
#[test] #[test]
fn a_run_list_keeps_native_group_elements() { fn a_run_list_keeps_native_group_elements() {
let inner_vector = unit_square_at(DVec2::ZERO); let inner_vector = unit_square_at(DVec2::ZERO);
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let content = native_group_paint(&inner_vector, &arena);
let content = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes);
let element = content.element(0).unwrap(); let element = content.element(0).unwrap();
let layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[]); let mut builder = RunBuilder::new(&arena, element_write_hashed::<Graphic>(), &[], 1).unwrap();
let mut bytes = vec![0u8; layout.lane_stride()]; builder.push(element.clone()).unwrap();
// SAFETY: `bytes` is one lane of `layout`; a parked element stores its let item = builder.finish();
// 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 list = run_to_list::<Graphic>(&item).expect("the run holds graphic lanes"); let list = run_to_list::<Graphic>(&item).expect("the run holds graphic lanes");
assert!(matches!(list.element(0), Some(Graphic::Group(_))), "the list keeps the native group form"); assert!(matches!(list.element(0), Some(Graphic::Group(_))), "the list keeps the native group form");
} }
@@ -1729,13 +1673,10 @@ mod run_tests {
#[test] #[test]
fn the_vector_row_walk_matches_the_legacy_flatten() { fn the_vector_row_walk_matches_the_legacy_flatten() {
let inner_vector = unit_square_at(DVec2::ZERO); let inner_vector = unit_square_at(DVec2::ZERO);
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[], 1).unwrap();
// SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element builder.push(inner_vector.clone()).unwrap();
// stores its reference. let inner_item = builder.finish();
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 mut painted = List::new(); let mut painted = List::new();
painted.push(Item::new_from_element(Graphic::Vector(unit_square_at(DVec2::ZERO)))); 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 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 transforms = [DAffine2::from_translation(DVec2::new(1., 2.)), DAffine2::from_scale(DVec2::splat(3.))];
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<core_types::attribute::Transform>(0)]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let stride = layout.lane_stride(); let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<core_types::attribute::Transform>(0)], 2).unwrap();
let mut bytes = vec![0u8; stride * 2]; for lane in 0..2 {
// SAFETY: `bytes` is `stride` per lane, and the offsets come from `layout`. let lane = builder.push(vectors[lane].clone()).unwrap();
unsafe { builder.attr::<core_types::attribute::Transform>(lane, transforms[lane]);
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::<DAffine2>().write(transforms[lane]);
}
} }
// SAFETY: `bytes` holds two lanes of `layout` at its stride. let item = builder.finish();
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) };
let run = RunView::<Vector>::new(&item).expect("the run holds vector elements"); let run = RunView::<Vector>::new(&item).expect("the run holds vector elements");
let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list"); let legacy = run_to_legacy_list::<Vector>(&item).expect("the run lowers to a legacy vector list");
+23 -39
View File
@@ -3025,9 +3025,7 @@ impl SvgRenderAttrs<'_> {
#[cfg(test)] #[cfg(test)]
mod group_walk_tests { mod group_walk_tests {
use super::*; use super::*;
use core_types::attribute::Attribute; use core_types::record::{FieldWrite, RunBuilder, element_write_hashed};
use core_types::node::RecordBatch;
use core_types::record::{FieldWrite, GroupItem, Layout, element_write_hashed};
use graphic_types::markers::Fill; use graphic_types::markers::Fill;
use graphic_types::vector_types::vector::PointId; use graphic_types::vector_types::vector::PointId;
@@ -3046,31 +3044,16 @@ mod group_walk_tests {
(output.svg, output.svg_defs) (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<T>(layout: &Layout, elements: &[&T], fill: &[Option<&List<Graphic>>]) -> Vec<u8> {
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::<Option<&List<Graphic>>>().write(fill[lane]);
}
}
bytes
}
#[test] #[test]
fn a_vector_run_group_renders_its_rows_without_layer_wrappers() { fn a_vector_run_group_renders_its_rows_without_layer_wrappers() {
let paint = color_paint(); let paint = color_paint();
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(3., 1.))]; let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(3., 1.))];
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
// SAFETY: the layout carries a parked vector element and the fill field. let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 2).unwrap();
let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; let lane = builder.push(vectors[0].clone()).unwrap();
// SAFETY: `bytes` holds two lanes of `layout` at its stride. builder.attr::<Fill>(lane, Some(&paint));
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; builder.push(vectors[1].clone()).unwrap();
let item = builder.finish();
let group = Group { row: None, content: item }; let group = Group { row: None, content: item };
let params = RenderParams::default(); let params = RenderParams::default();
@@ -3084,11 +3067,11 @@ mod group_walk_tests {
fn lane_paint_on_a_graphic_run_reaches_vector_interiors() { fn lane_paint_on_a_graphic_run_reaches_vector_interiors() {
let paint = color_paint(); let paint = color_paint();
let inner = Graphic::Vector(unit_square_at(DVec2::ZERO)); let inner = Graphic::Vector(unit_square_at(DVec2::ZERO));
let layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[FieldWrite::of::<Fill>(0)]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
// SAFETY: the layout carries a parked graphic element and the fill field. let mut builder = RunBuilder::new(&arena, element_write_hashed::<Graphic>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
let bytes = unsafe { write_lanes::<Graphic>(&layout, &[&inner], &[Some(&paint)]) }; let lane = builder.push(inner.clone()).unwrap();
// SAFETY: `bytes` holds one lane of `layout` at its stride. builder.attr::<Fill>(lane, Some(&paint));
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; let item = builder.finish();
let group = Group { row: None, content: item }; let group = Group { row: None, content: item };
let params = RenderParams::default(); let params = RenderParams::default();
@@ -3103,11 +3086,11 @@ mod group_walk_tests {
fn a_group_collects_its_lane_metadata_for_the_caller() { fn a_group_collects_its_lane_metadata_for_the_caller() {
let paint = color_paint(); let paint = color_paint();
let vectors = [unit_square_at(DVec2::ZERO)]; let vectors = [unit_square_at(DVec2::ZERO)];
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
// SAFETY: the layout carries a parked vector element and the fill field. let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 1).unwrap();
let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0]], &[Some(&paint)]) }; let lane = builder.push(vectors[0].clone()).unwrap();
// SAFETY: `bytes` holds one lane of `layout` at its stride. builder.attr::<Fill>(lane, Some(&paint));
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 1, &layout)) }; let item = builder.finish();
let group = Group { row: None, content: item }; let group = Group { row: None, content: item };
let footprint = Footprint::default(); let footprint = Footprint::default();
@@ -3128,11 +3111,12 @@ mod group_walk_tests {
fn a_group_serves_its_legacy_lowerings_click_targets() { fn a_group_serves_its_legacy_lowerings_click_targets() {
let paint = color_paint(); let paint = color_paint();
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(2., 2.))]; let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(2., 2.))];
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
// SAFETY: the layout carries a parked vector element and the fill field. let mut builder = RunBuilder::new(&arena, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(0)], 2).unwrap();
let bytes = unsafe { write_lanes::<Vector>(&layout, &[&vectors[0], &vectors[1]], &[Some(&paint), None]) }; let lane = builder.push(vectors[0].clone()).unwrap();
// SAFETY: `bytes` holds two lanes of `layout` at its stride. builder.attr::<Fill>(lane, Some(&paint));
let item = unsafe { GroupItem::from_resident(RecordBatch::new(bytes.as_ptr(), 2, &layout)) }; builder.push(vectors[1].clone()).unwrap();
let item = builder.finish();
let group = Group { row: None, content: item }; let group = Group { row: None, content: item };
let mut native = Vec::new(); let mut native = Vec::new();
+2 -3
View File
@@ -274,11 +274,10 @@ mod tests {
let io = handle.serialize().expect("the eval landed a snapshot"); let io = handle.serialize().expect("the eval landed a snapshot");
let snapshot = io.downcast_ref::<CtxSnapshot>().expect("the monitor serializes its context snapshot"); let snapshot = io.downcast_ref::<CtxSnapshot>().expect("the monitor serializes its context snapshot");
let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains"); 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"); panic!("expected a final record");
}; };
// SAFETY: the eval produced a live record of the edge's layout. assert_eq!(served.element::<u32>(), 11);
assert_eq!(unsafe { layout.rec(&value).element::<u32>() }, 11);
} }
#[test] #[test]
File diff suppressed because it is too large Load Diff
+44 -82
View File
@@ -226,7 +226,7 @@ mod tests {
use core_types::context::{ContextImpl, EvalScope, ExtractArena, ExtractIndices}; use core_types::context::{ContextImpl, EvalScope, ExtractArena, ExtractIndices};
use core_types::list::{Item, List}; use core_types::list::{Item, List};
use core_types::node::Node; 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>(T); struct ValueNode<T>(T);
@@ -248,15 +248,11 @@ mod tests {
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> { fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
let (graphic, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()]; let (graphic, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()];
let dst = stack::push(self.layout.frame_bytes()); let mut frame = record::FrameBuilder::new(&self.layout, input.arena());
if unsafe { record::write_element(dst, graphic.clone(), input.arena()) }.is_none() { frame.element(graphic.clone());
return GPoll::arena_exhausted(); frame.attr::<Transform>(*transform);
} let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
unsafe { GPoll::Final(value)
dst.add(self.layout.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap()).cast::<DAffine2>().write(*transform);
}
stack::pop(dst);
GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }))
} }
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> { fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
@@ -380,15 +376,11 @@ mod tests {
let lane = input.innermost_index(); let lane = input.innermost_index();
let graphic = text(&format!("{label}{lane}")); let graphic = text(&format!("{label}{lane}"));
let translated = DAffine2::from_translation(glam::DVec2::new(lane as f64, 0.)); let translated = DAffine2::from_translation(glam::DVec2::new(lane as f64, 0.));
let dst = stack::push(self.layout.frame_bytes()); let mut frame = record::FrameBuilder::new(&self.layout, input.arena());
if unsafe { record::write_element(dst, graphic, input.arena()) }.is_none() { frame.element(graphic);
return GPoll::arena_exhausted(); frame.attr::<Transform>(translated);
} let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
unsafe { GPoll::Final(value)
dst.add(self.layout.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap()).cast::<DAffine2>().write(translated);
}
stack::pop(dst);
GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }))
} }
fn extent_at(&self, input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> { fn extent_at(&self, input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
@@ -453,17 +445,13 @@ mod tests {
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0))); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
let head = ctx.index_head(); let head = ctx.index_head();
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() { for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() {
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(rec) }), label, "lane {lane}"); let transform: DAffine2 = record.attr::<Transform>();
let transform: DAffine2 = unsafe { rec.read(offset) };
assert_eq!(transform.translation.x, x, "lane {lane}"); 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))); assert_eq!(composed.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
let head = ctx.index_head(); let head = ctx.index_head();
let offset = flat_out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() { for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() {
let mark = stack::sp();
let scoped = ctx.promoted(&head, lane as u64); 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"); panic!("expected a final record from flat_map");
}; };
let direct_label = text_of(unsafe { record::borrow_element::<Graphic>(flat_out.rec(&direct)) }).to_string(); let direct_label = text_of(&direct.element::<Graphic>()).to_string();
let direct_x: DAffine2 = unsafe { flat_out.rec(&direct).read(offset) }; let direct_x: DAffine2 = direct.attr::<Transform>();
let GPoll::Final(value) = composed.eval(&scoped) else { let GPoll::Final(value) = record::capture(&composed, &scoped) else {
panic!("expected a final record from flatten(map)"); panic!("expected a final record from flatten(map)");
}; };
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(composed_out.rec(&value)) }), direct_label, "lane {lane}"); assert_eq!(text_of(&value.element::<Graphic>()), direct_label, "lane {lane}");
let composed_x: DAffine2 = unsafe { composed_out.rec(&value).read(offset) }; let composed_x: DAffine2 = value.attr::<Transform>();
assert_eq!(composed_x, direct_x, "lane {lane}"); assert_eq!(composed_x, direct_x, "lane {lane}");
assert_eq!((direct_label.as_str(), direct_x.translation.x), (label, 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); assert_eq!(batch.len(), 5);
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap(); let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
for lane in 0..5 { for lane in 0..5 {
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); let single = text_of(&record.element::<Graphic>()).to_string();
let single = text_of(unsafe { record::borrow_element::<Graphic>(rec) }).to_string();
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(batch.get(lane).rec()) }), single, "lane {lane}"); assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(batch.get(lane).rec()) }), single, "lane {lane}");
let batched: DAffine2 = unsafe { batch.get(lane).rec().read(offset) }; let batched: DAffine2 = unsafe { batch.get(lane).rec().read(offset) };
let direct: DAffine2 = unsafe { rec.read(offset) }; let direct: DAffine2 = record.attr::<Transform>();
assert_eq!(batched, direct, "lane {lane}"); 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))); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)));
let head = ctx.index_head(); let head = ctx.index_head();
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
// Lane 2 is the unexpanded subgroup H, riding as a leaf at G's depth. // 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)]; let expected: [(&str, f64); 2] = [("a", 1.), ("b", 20.5)];
for (lane, &(label, x)) in expected.iter().enumerate() { for (lane, &(label, x)) in expected.iter().enumerate() {
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(rec) }), label, "lane {lane}"); let transform: DAffine2 = record.attr::<Transform>();
let transform: DAffine2 = unsafe { rec.read(offset) };
assert_eq!(transform.translation.x, x, "lane {lane}"); assert_eq!(transform.translation.x, x, "lane {lane}");
unsafe { stack::rewind(mark) };
} }
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, 2)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, 2)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); let Graphic::Graphic(children) = record.element::<Graphic>() else {
let Graphic::Graphic(children) = (unsafe { record::borrow_element::<Graphic>(rec) }) else {
panic!("lane 2 keeps the subgroup element"); panic!("lane 2 keeps the subgroup element");
}; };
assert_eq!(children.len(), 1); assert_eq!(children.len(), 1);
@@ -647,9 +623,8 @@ mod tests {
300., 300.,
"embedded transforms ride untouched" "embedded transforms ride untouched"
); );
let transform: DAffine2 = unsafe { rec.read(offset) }; let transform: DAffine2 = record.attr::<Transform>();
assert_eq!(transform.translation.x, 4000.5); assert_eq!(transform.translation.x, 4000.5);
unsafe { stack::rewind(mark) };
} }
#[test] #[test]
@@ -777,10 +752,10 @@ mod tests {
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> { fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
let color = self.colors[input.innermost_index() as usize]; let color = self.colors[input.innermost_index() as usize];
let dst = stack::push(self.layout.frame_bytes()); let mut frame = record::FrameBuilder::new(&self.layout, input.arena());
unsafe { dst.cast::<Color>().write(color) }; frame.element(color);
stack::pop(dst); let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) })) GPoll::Final(value)
} }
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> { fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
@@ -802,10 +777,10 @@ mod tests {
let build = |colors: Vec<Color>| install_flip(ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout), &out); let build = |colors: Vec<Color>| install_flip(ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout), &out);
let stops_of = |colors: Vec<Color>| { let stops_of = |colors: Vec<Color>| {
let node = build(colors); 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"); panic!("expected a final record");
}; };
unsafe { record::borrow_element::<GradientStops>(out.rec(&value)) }.clone() record.element::<GradientStops>()
}; };
let three = stops_of(vec![Color::BLACK, Color::WHITE, Color::BLACK]); 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 // One row holding the wrapped group flattens back to the lanes, the
// group's identity transform composed onto each child's. // group's identity transform composed onto each child's.
let node = build!(layout, vec![(group, DAffine2::IDENTITY)], false); let node = build!(layout, vec![(group, DAffine2::IDENTITY)], false);
let out = Node::<ContextImpl>::layout(&node).clone();
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(2))); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(2)));
let head = ctx.index_head(); let head = ctx.index_head();
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
for (lane, &(label, x)) in [("a", 1.), ("b", 2.)].iter().enumerate() { for (lane, &(label, x)) in [("a", 1.), ("b", 2.)].iter().enumerate() {
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(rec) }), label, "lane {lane}"); let transform: DAffine2 = record.attr::<Transform>();
let transform: DAffine2 = unsafe { rec.read(offset) };
assert_eq!(transform.translation.x, x, "lane {lane}"); assert_eq!(transform.translation.x, x, "lane {lane}");
unsafe { stack::rewind(mark) };
} }
} }
@@ -909,22 +879,17 @@ mod tests {
rows.push((group(vec![]), translation(9.))); rows.push((group(vec![]), translation(9.)));
let layout = graphic_layout(); let layout = graphic_layout();
let node = build!(layout, rows, true); let node = build!(layout, rows, true);
let out = Node::<ContextImpl>::layout(&node).clone();
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves"); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves");
let head = ctx.index_head(); let head = ctx.index_head();
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
let expected: [(&str, f64); 3] = [("a", 1.), ("b", 20.5), ("c", 4300.5)]; let expected: [(&str, f64); 3] = [("a", 1.), ("b", 20.5), ("c", 4300.5)];
for (lane, &(label, x)) in expected.iter().enumerate() { for (lane, &(label, x)) in expected.iter().enumerate() {
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(rec) }), label, "lane {lane}"); let transform: DAffine2 = record.attr::<Transform>();
let transform: DAffine2 = unsafe { rec.read(offset) };
assert_eq!(transform.translation.x, x, "lane {lane}"); assert_eq!(transform.translation.x, x, "lane {lane}");
unsafe { stack::rewind(mark) };
} }
} }
@@ -949,17 +914,14 @@ mod tests {
assert_eq!(batch.len(), 3); assert_eq!(batch.len(), 3);
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap(); let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
for lane in 0..3 { for lane in 0..3 {
let mark = stack::sp(); let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane as u64)) else {
panic!("expected a final record"); panic!("expected a final record");
}; };
let rec = out.rec(&value); let single = text_of(&record.element::<Graphic>()).to_string();
let single = text_of(unsafe { record::borrow_element::<Graphic>(rec) }).to_string();
assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(batch.get(lane).rec()) }), single, "lane {lane}"); assert_eq!(text_of(unsafe { record::borrow_element::<Graphic>(batch.get(lane).rec()) }), single, "lane {lane}");
let batched: DAffine2 = unsafe { batch.get(lane).rec().read(offset) }; let batched: DAffine2 = unsafe { batch.get(lane).rec().read(offset) };
let direct: DAffine2 = unsafe { rec.read(offset) }; let direct: DAffine2 = record.attr::<Transform>();
assert_eq!(batched, direct, "lane {lane}"); assert_eq!(batched, direct, "lane {lane}");
unsafe { stack::rewind(mark) };
} }
} }
} }
+4 -7
View File
@@ -562,13 +562,10 @@ mod tests {
#[test] #[test]
fn the_native_flatten_reads_lanes_groups_and_reach() { fn the_native_flatten_reads_lanes_groups_and_reach() {
let inner_vector = square(DVec2::ZERO); let inner_vector = square(DVec2::ZERO);
let inner_layout = core_types::record::Layout::default().with_writes(0, core_types::record::element_write_hashed::<Vector>(), &[]); let arena = core_types::arena::Arena::new(1 << 16).unwrap();
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()]; let mut builder = core_types::record::RunBuilder::new(&arena, core_types::record::element_write_hashed::<Vector>(), &[], 1).unwrap();
// SAFETY: `inner_bytes` is one lane of `inner_layout`; a parked element builder.push(inner_vector.clone()).unwrap();
// stores its reference. let inner_item = builder.finish();
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 mut top = List::new(); let mut top = List::new();
top.push(Item::new_from_element(Graphic::Vector(square(DVec2::ZERO)))); top.push(Item::new_from_element(Graphic::Vector(square(DVec2::ZERO))));
+30 -57
View File
@@ -181,10 +181,9 @@ mod test {
use super::*; use super::*;
use core_types::SourceId; use core_types::SourceId;
use core_types::arena::Arena; use core_types::arena::Arena;
use core_types::attribute::Attribute as AttributeMarker;
use core_types::context::{ContextImpl, EvalScope}; use core_types::context::{ContextImpl, EvalScope};
use core_types::node::Node; 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; use vector_types::subpath::Subpath;
struct ValueNode<T>(T); struct ValueNode<T>(T);
@@ -206,14 +205,13 @@ mod test {
impl<'e> Node<ContextImpl<'e>> for TransformSource { impl<'e> Node<ContextImpl<'e>> for TransformSource {
type Output = RecordValue<'e>; type Output = RecordValue<'e>;
fn eval(&self, _input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> { fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
let dst = stack::push(self.layout.frame_bytes()); use core_types::context::ExtractArena;
unsafe { let mut frame = FrameBuilder::new(&self.layout, input.arena());
dst.cast::<f64>().write(self.element); frame.element(self.element);
dst.add(self.layout.offset_of(TransformAttr::NAME, 0).unwrap()).cast::<DAffine2>().write(self.transform); frame.attr::<TransformAttr>(self.transform);
} let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
stack::pop(dst); GPoll::Final(value)
GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }))
} }
} }
@@ -233,16 +231,11 @@ mod test {
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> { fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
use core_types::context::{ExtractArena, ExtractIndices}; use core_types::context::{ExtractArena, ExtractIndices};
let (vector, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()]; let (vector, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()];
let dst = stack::push(self.layout.frame_bytes()); let mut frame = FrameBuilder::new(&self.layout, input.arena());
// SAFETY: dst is the claimed frame of this layout; offsets are the layout's own. frame.element(vector.clone());
unsafe { frame.attr::<TransformAttr>(*transform);
if core_types::record::write_element(dst, vector.clone(), input.arena()).is_none() { let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
return GPoll::Error(Box::new(core_types::gpoll::GraphError::new("arena exhausted"))); GPoll::Final(value)
}
core_types::record::write_field(dst, self.layout.offset_of(<TransformAttr as AttributeMarker>::NAME, 0).unwrap(), *transform);
}
stack::pop(dst);
GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }))
} }
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> { fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
@@ -266,16 +259,13 @@ mod test {
type Output = RecordValue<'e>; type Output = RecordValue<'e>;
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> { fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
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 position = input.try_position().and_then(|mut positions| positions.next()).unwrap_or(DVec2::ZERO);
let dst = stack::push(self.layout.frame_bytes()); let mut frame = FrameBuilder::new(&self.layout, input.arena());
// SAFETY: dst is the claimed frame of this layout; offsets are the layout's own. frame.element(position.x);
unsafe { frame.attr::<TransformAttr>(DAffine2::IDENTITY);
dst.cast::<f64>().write(position.x); let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
core_types::record::write_field(dst, self.layout.offset_of(<TransformAttr as AttributeMarker>::NAME, 0).unwrap(), DAffine2::IDENTITY); GPoll::Final(value)
}
stack::pop(dst);
GPoll::Final(RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) }))
} }
fn layout(&self) -> &Layout { fn layout(&self) -> &Layout {
@@ -315,19 +305,15 @@ mod test {
let head = ctx.index_head(); let head = ctx.index_head();
for copy in 0..3u64 { for copy in 0..3u64 {
let mark = stack::sp();
let lane = ctx.promoted(&head, copy); 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"); panic!("expected a final record");
}; };
let rec = leveled.rec(&value); assert_eq!(record.element::<f64>(), 7.);
assert_eq!(unsafe { rec.element::<f64>() }, 7.);
// Zero angle, direction (10, 0), count 3: copy `j` steps j * (5, 0) // Zero angle, direction (10, 0), count 3: copy `j` steps j * (5, 0)
// past the row's own (5, 5) translation. // 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::<TransformAttr>();
assert_eq!(composed, DAffine2::from_translation(DVec2::new(5. + copy as f64 * 5., 5.))); 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); let mut node = RepeatRadialNode::new(RecordSource::new(content, &layout, &layout), ValueNode(90.0f64), ValueNode(2.0f64), ValueNode(4u32), &layout);
Node::<ContextImpl>::set_layout(&mut node, repeat_radial_layout_meta().resolve(&[Some(&layout)])); Node::<ContextImpl>::set_layout(&mut node, repeat_radial_layout_meta().resolve(&[Some(&layout)]));
let leveled = Node::<ContextImpl>::layout(&node).clone();
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(4))); assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(4)));
let head = ctx.index_head(); let head = ctx.index_head();
for copy in 0..4u64 { for copy in 0..4u64 {
let mark = stack::sp();
let lane = ctx.promoted(&head, copy); 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"); panic!("expected a final record");
}; };
let rec = leveled.rec(&value); assert_eq!(record.element::<f64>(), 7.);
assert_eq!(unsafe { rec.element::<f64>() }, 7.);
// The kernel's own formula, so the float operations match exactly. // 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 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 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::<TransformAttr>();
assert_eq!(composed, expected); 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(); let head = ctx.index_head();
for (flat, &point) in expected.iter().enumerate() { for (flat, &point) in expected.iter().enumerate() {
let mark = stack::sp();
let lane = ctx.promoted(&head, flat as u64); 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"); panic!("expected a final record");
}; };
let rec = leveled.rec(&value);
// The content saw the pushed position, and the output transform lands on it. // The content saw the pushed position, and the output transform lands on it.
assert_eq!(unsafe { rec.element::<f64>() }, point.x); assert_eq!(record.element::<f64>(), point.x);
let composed: DAffine2 = unsafe { rec.read(leveled.offset_of(<TransformAttr as AttributeMarker>::NAME, 0).unwrap()) }; let composed: DAffine2 = record.attr::<TransformAttr>();
assert_eq!(composed.translation, point); 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); let mut node = RepeatOnPointsNode::new(RecordSource::new(content, &content_layout, &content_layout), points, ValueNode(true), &content_layout);
Node::<ContextImpl>::set_layout(&mut node, repeat_on_points_layout_meta().resolve(&[Some(&content_layout)])); Node::<ContextImpl>::set_layout(&mut node, repeat_on_points_layout_meta().resolve(&[Some(&content_layout)]));
let leveled = Node::<ContextImpl>::layout(&node).clone();
let mut expected = positions.clone(); let mut expected = positions.clone();
expected.reverse(); expected.reverse();
let head = ctx.index_head(); let head = ctx.index_head();
for (flat, &point) in expected.iter().enumerate() { for (flat, &point) in expected.iter().enumerate() {
let mark = stack::sp();
let lane = ctx.promoted(&head, flat as u64); 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"); panic!("expected a final record");
}; };
let composed: DAffine2 = unsafe { leveled.rec(&value).read(leveled.offset_of(<TransformAttr as AttributeMarker>::NAME, 0).unwrap()) }; let composed: DAffine2 = record.attr::<TransformAttr>();
assert_eq!(composed.translation, point); 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) };
} }
} }
} }