mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Carry group content across memo seams in an owned form
This commit is contained in:
@@ -1247,23 +1247,31 @@ pub fn element_dims<T>() -> (usize, usize) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Deep clone-out overrides for element types whose plain clone borrows the
|
||||
/// Deep-copy overrides for element types whose plain clone borrows the
|
||||
/// evaluation's arena (a `Graphic` holding a group interior). The generic
|
||||
/// element glue consults this registry, so every layout carrying such an
|
||||
/// element deep-copies at memo and capture seams regardless of which
|
||||
/// constructor built the glue. The override must produce a value of the
|
||||
/// element's own type that owns all of its content, so the generic re-park
|
||||
/// replays it unchanged.
|
||||
static DEEP_ELEMENT_CLONES: std::sync::LazyLock<std::sync::Mutex<std::collections::HashMap<std::any::TypeId, unsafe fn(*const u8) -> Box<dyn std::any::Any + Send + Sync>>>> =
|
||||
std::sync::LazyLock::new(Default::default);
|
||||
|
||||
/// Registers `clone_out` as the deep clone-out for elements of `T`. Called at
|
||||
/// startup from the crate that owns the type.
|
||||
pub fn register_deep_element_clone<T: 'static>(clone_out: unsafe fn(*const u8) -> Box<dyn std::any::Any + Send + Sync>) {
|
||||
DEEP_ELEMENT_CLONES.lock().unwrap().insert(std::any::TypeId::of::<T>(), clone_out);
|
||||
/// constructor built the glue. The clone-out must produce a value of the
|
||||
/// element's own type that owns all of its content; the re-park restores that
|
||||
/// value's arena-resident form before parking it.
|
||||
#[derive(Clone, Copy)]
|
||||
struct DeepElementGlue {
|
||||
clone_out: unsafe fn(*const u8) -> Box<dyn std::any::Any + Send + Sync>,
|
||||
repark: unsafe fn(&(dyn std::any::Any + Send + Sync), *mut u8, &crate::arena::Arena) -> Option<()>,
|
||||
}
|
||||
|
||||
fn deep_element_clone(type_id: std::any::TypeId) -> Option<unsafe fn(*const u8) -> Box<dyn std::any::Any + Send + Sync>> {
|
||||
static DEEP_ELEMENT_CLONES: std::sync::LazyLock<std::sync::Mutex<std::collections::HashMap<std::any::TypeId, DeepElementGlue>>> = std::sync::LazyLock::new(Default::default);
|
||||
|
||||
/// Registers the deep copy-out and re-park pair for elements of `T`. Called
|
||||
/// at startup from the crate that owns the type.
|
||||
pub fn register_deep_element_clone<T: 'static>(
|
||||
clone_out: unsafe fn(*const u8) -> Box<dyn std::any::Any + Send + Sync>,
|
||||
repark: unsafe fn(&(dyn std::any::Any + Send + Sync), *mut u8, &crate::arena::Arena) -> Option<()>,
|
||||
) {
|
||||
DEEP_ELEMENT_CLONES.lock().unwrap().insert(std::any::TypeId::of::<T>(), DeepElementGlue { clone_out, repark });
|
||||
}
|
||||
|
||||
fn deep_element_glue(type_id: std::any::TypeId) -> Option<DeepElementGlue> {
|
||||
DEEP_ELEMENT_CLONES.lock().unwrap().get(&type_id).copied()
|
||||
}
|
||||
|
||||
@@ -1271,12 +1279,15 @@ fn deep_element_clone(type_id: std::any::TypeId) -> Option<unsafe fn(*const u8)
|
||||
/// the statically-known type.
|
||||
pub fn element_write<T: Clone + Send + Sync + 'static>() -> ElementWrite {
|
||||
unsafe fn clone_out<T: Clone + Send + Sync + 'static>(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
if let Some(deep) = deep_element_clone(std::any::TypeId::of::<T>()) {
|
||||
return unsafe { deep(ptr) };
|
||||
if let Some(deep) = deep_element_glue(std::any::TypeId::of::<T>()) {
|
||||
return unsafe { (deep.clone_out)(ptr) };
|
||||
}
|
||||
Box::new(unsafe { read_element::<T>(Rec::new(ptr)) })
|
||||
}
|
||||
unsafe fn repark<T: Clone + Send + Sync + 'static>(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &crate::arena::Arena) -> Option<()> {
|
||||
if let Some(deep) = deep_element_glue(std::any::TypeId::of::<T>()) {
|
||||
return unsafe { (deep.repark)(value, dst, arena) };
|
||||
}
|
||||
let value = value.downcast_ref::<T>().expect("an element replays at its own type");
|
||||
unsafe { write_element(dst, value.clone(), arena) }
|
||||
}
|
||||
@@ -1778,14 +1789,40 @@ where
|
||||
|
||||
/// `len` records stored in the arena at `layout`'s stride. The layout is
|
||||
/// owned by the value and identifies the run's element type. The records are
|
||||
/// valid for the current evaluation, like every arena payload.
|
||||
/// valid for the current evaluation, like every arena payload. An owned item
|
||||
/// ([`Self::copy_out`]) survives the generation instead, and must
|
||||
/// [`Self::replay`] into a serving arena before any read.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GroupItem {
|
||||
layout: Layout,
|
||||
frames: *const u8,
|
||||
storage: ItemStorage,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum ItemStorage {
|
||||
Resident(*const u8),
|
||||
Owned(std::sync::Arc<OwnedLanes>),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ItemStorage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ItemStorage::Resident(frames) => write!(f, "Resident({frames:p})"),
|
||||
ItemStorage::Owned(_) => f.write_str("Owned(..)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The lanes deep-copied out of their evaluation: the packed bytes plus owned
|
||||
/// clones of every parked payload, per lane. The byte image's parked
|
||||
/// references are stale until [`GroupItem::replay`] re-parks the payloads.
|
||||
struct OwnedLanes {
|
||||
bytes: Box<[u8]>,
|
||||
elements: Option<Vec<Box<dyn std::any::Any + Send + Sync>>>,
|
||||
fields: Vec<(usize, Vec<Box<dyn crate::list::AnyAttributeValue>>)>,
|
||||
}
|
||||
|
||||
// SAFETY: the same argument as for `RecordValue`. The element bounds and the
|
||||
// parking discipline make the record bytes thread-safe, and their validity
|
||||
// is tied to the shared arena.
|
||||
@@ -1813,11 +1850,19 @@ impl GroupItem {
|
||||
}
|
||||
Some(Self {
|
||||
layout,
|
||||
frames: frames.cast_const(),
|
||||
storage: ItemStorage::Resident(frames.cast_const()),
|
||||
len: batch.len(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The resident frame base. An owned item has none until it replays.
|
||||
fn frames(&self) -> *const u8 {
|
||||
match &self.storage {
|
||||
ItemStorage::Resident(frames) => *frames,
|
||||
ItemStorage::Owned(_) => panic!("an owned item replays into an arena before it is read"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
@@ -1842,17 +1887,85 @@ impl GroupItem {
|
||||
assert!(field.repark.is_none() || field.content_hash.is_some(), "a parked field adopts only with content glue");
|
||||
}
|
||||
Self {
|
||||
frames: batch.frames_ptr(),
|
||||
storage: ItemStorage::Resident(batch.frames_ptr()),
|
||||
len: batch.len(),
|
||||
layout,
|
||||
}
|
||||
}
|
||||
|
||||
/// A batch view over the stored records.
|
||||
/// A batch view over the stored records. Panics on an owned item, which
|
||||
/// must [`Self::replay`] first.
|
||||
pub fn lanes(&self) -> crate::node::RecordBatch<'_> {
|
||||
// SAFETY: the constructors store `len` lanes of `layout` at the
|
||||
// layout's stride.
|
||||
unsafe { crate::node::RecordBatch::new(self.frames, self.len, &self.layout) }
|
||||
unsafe { crate::node::RecordBatch::new(self.frames(), self.len, &self.layout) }
|
||||
}
|
||||
|
||||
/// The item deep-copied out of its evaluation: lane bytes plus owned
|
||||
/// clones of every parked payload, for storage that outlives the arena
|
||||
/// generation. An owned item cannot be read until it replays.
|
||||
pub fn copy_out(&self) -> GroupItem {
|
||||
if let ItemStorage::Owned(_) = &self.storage {
|
||||
return self.clone();
|
||||
}
|
||||
let stride = self.layout.lane_stride();
|
||||
let frames = self.frames();
|
||||
// SAFETY: the constructors store `len` lanes of `layout` at the
|
||||
// layout's stride, and the erased glue reads each lane's own region.
|
||||
let bytes: Box<[u8]> = unsafe { std::slice::from_raw_parts(frames, self.len * stride) }.into();
|
||||
let elements = self
|
||||
.layout
|
||||
.element
|
||||
.parked
|
||||
.then(|| (0..self.len).map(|lane| unsafe { (self.layout.element.clone_out)(frames.add(lane * stride)) }).collect());
|
||||
let fields = self
|
||||
.layout
|
||||
.fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, field)| field.repark.is_some())
|
||||
.map(|(index, field)| {
|
||||
let values = (0..self.len).map(|lane| unsafe { (field.read_erased)(frames.add(lane * stride + field.offset)) }).collect();
|
||||
(index, values)
|
||||
})
|
||||
.collect();
|
||||
GroupItem {
|
||||
layout: self.layout.clone(),
|
||||
storage: ItemStorage::Owned(std::sync::Arc::new(OwnedLanes { bytes, elements, fields })),
|
||||
len: self.len,
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-parks an owned item's lanes into `arena`, restoring the resident
|
||||
/// form; `None` reports arena exhaustion. A resident item returns a plain
|
||||
/// clone.
|
||||
pub fn replay(&self, arena: &crate::arena::Arena) -> Option<GroupItem> {
|
||||
let ItemStorage::Owned(owned) = &self.storage else {
|
||||
return Some(self.clone());
|
||||
};
|
||||
let stride = self.layout.lane_stride();
|
||||
let scratch = arena.alloc_scratch::<u64>((self.len * stride).div_ceil(8))?;
|
||||
let frames = scratch.as_mut_ptr().cast::<u8>();
|
||||
// SAFETY: the scratch holds `len` lanes at the layout's stride, and the
|
||||
// re-park glue writes each lane's own region under that layout.
|
||||
unsafe { std::ptr::copy_nonoverlapping(owned.bytes.as_ptr(), frames, owned.bytes.len()) };
|
||||
if let Some(elements) = &owned.elements {
|
||||
for (lane, element) in elements.iter().enumerate() {
|
||||
unsafe { (self.layout.element.repark)(&**element, frames.add(lane * stride), arena) }?;
|
||||
}
|
||||
}
|
||||
for (index, values) in &owned.fields {
|
||||
let field = &self.layout.fields[*index];
|
||||
let repark = field.repark.expect("copied fields carry re-park glue");
|
||||
for (lane, value) in values.iter().enumerate() {
|
||||
unsafe { repark(&**value, frames.add(lane * stride + field.offset), arena) }?;
|
||||
}
|
||||
}
|
||||
Some(GroupItem {
|
||||
layout: self.layout.clone(),
|
||||
storage: ItemStorage::Resident(frames.cast_const()),
|
||||
len: self.len,
|
||||
})
|
||||
}
|
||||
|
||||
/// A typed view over the stored records, checked against the layout's
|
||||
@@ -1946,6 +2059,34 @@ pub struct Group {
|
||||
pub content: GroupContent,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
/// The group deep-copied out of its evaluation, every run in owned form.
|
||||
pub fn copy_out(&self) -> Group {
|
||||
let content = match &self.content {
|
||||
GroupContent::Run(item) => GroupContent::Run(item.copy_out()),
|
||||
GroupContent::Stack(children) => GroupContent::Stack(children.iter().map(Group::copy_out).collect()),
|
||||
};
|
||||
Group {
|
||||
row: self.row.as_ref().map(GroupItem::copy_out),
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-parks an owned group's runs into `arena`; `None` reports arena
|
||||
/// exhaustion.
|
||||
pub fn replay(&self, arena: &crate::arena::Arena) -> Option<Group> {
|
||||
let content = match &self.content {
|
||||
GroupContent::Run(item) => GroupContent::Run(item.replay(arena)?),
|
||||
GroupContent::Stack(children) => GroupContent::Stack(children.iter().map(|child| child.replay(arena)).collect::<Option<_>>()?),
|
||||
};
|
||||
let row = match &self.row {
|
||||
Some(row) => Some(row.replay(arena)?),
|
||||
None => None,
|
||||
};
|
||||
Some(Group { row, content })
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares one record region of `layout` by content. Regions without glue
|
||||
/// compare as bytes, which is the content for unparked values.
|
||||
unsafe fn record_content_eq(layout: &Layout, a: *const u8, b: *const u8) -> bool {
|
||||
@@ -1993,8 +2134,9 @@ impl PartialEq for GroupItem {
|
||||
return false;
|
||||
}
|
||||
let stride = self.layout.lane_stride();
|
||||
let (a, b) = (self.frames(), other.frames());
|
||||
// SAFETY: both sides hold `len` lanes of the shared layout.
|
||||
(0..self.len).all(|lane| unsafe { record_content_eq(&self.layout, self.frames.add(lane * stride), other.frames.add(lane * stride)) })
|
||||
(0..self.len).all(|lane| unsafe { record_content_eq(&self.layout, a.add(lane * stride), b.add(lane * stride)) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2003,9 +2145,10 @@ impl graphene_hash::CacheHash for GroupItem {
|
||||
layout_shape_hash(&self.layout, state);
|
||||
state.write_usize(self.len);
|
||||
let stride = self.layout.lane_stride();
|
||||
let frames = self.frames();
|
||||
for lane in 0..self.len {
|
||||
// SAFETY: `adopt` filled `len` lanes of `layout`.
|
||||
unsafe { record_content_hash(&self.layout, self.frames.add(lane * stride), state) };
|
||||
unsafe { record_content_hash(&self.layout, frames.add(lane * stride), state) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2304,6 +2447,46 @@ mod tests {
|
||||
assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, "field");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owned_items_replay_re_parked_payloads_after_the_source_dies() {
|
||||
let layout = Layout::default().with_writes(0, element_write_hashed::<String>(), &[FieldWrite::of::<crate::attribute::Name>(0)]);
|
||||
let stride = layout.lane_stride();
|
||||
let mut bytes = vec![0u8; stride * 2];
|
||||
|
||||
let owned = {
|
||||
let arena = crate::arena::Arena::new(1024).unwrap();
|
||||
for lane in 0..2 {
|
||||
let base = unsafe { bytes.as_mut_ptr().add(lane * stride) };
|
||||
unsafe { write_element(base, format!("element {lane}"), &arena) }.unwrap();
|
||||
let (name, _) = arena.alloc(format!("field {lane}")).unwrap();
|
||||
unsafe { write_field::<&str>(base, layout.offset_of("name", 0).unwrap(), name.as_str()) };
|
||||
}
|
||||
let item = unsafe { GroupItem::from_resident(crate::node::RecordBatch::new(bytes.as_ptr(), 2, &layout)) };
|
||||
item.copy_out()
|
||||
};
|
||||
bytes.fill(u8::MAX);
|
||||
|
||||
let arena = crate::arena::Arena::new(1024).unwrap();
|
||||
let replayed = owned.replay(&arena).unwrap();
|
||||
let lanes = replayed.lanes();
|
||||
for lane in 0..2 {
|
||||
let rec = lanes.get(lane).rec();
|
||||
assert_eq!(unsafe { read_element::<String>(rec) }, format!("element {lane}"));
|
||||
assert_eq!(unsafe { rec.read::<&str>(layout.offset_of("name", 0).unwrap()) }, format!("field {lane}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "an owned item replays")]
|
||||
fn an_owned_item_refuses_reads() {
|
||||
let layout = Layout::default().with_writes(0, element_write_hashed::<String>(), &[]);
|
||||
let mut bytes = vec![0u8; layout.lane_stride()];
|
||||
let arena = crate::arena::Arena::new(1024).unwrap();
|
||||
unsafe { write_element(bytes.as_mut_ptr(), String::from("parked"), &arena) }.unwrap();
|
||||
let item = unsafe { GroupItem::from_resident(crate::node::RecordBatch::new(bytes.as_ptr(), 1, &layout)) };
|
||||
item.copy_out().lanes();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_values_are_one_word() {
|
||||
assert_eq!(size_of::<RecordValue>(), 8);
|
||||
|
||||
@@ -42,28 +42,47 @@ impl Artboard {
|
||||
}
|
||||
}
|
||||
|
||||
/// The deep clone-out for `Artboard` elements: as for `Graphic`, a plain
|
||||
/// The deep copy-out for `Artboard` elements: as for `Graphic`, a plain
|
||||
/// clone of group content would carry frame pointers into the evaluation's
|
||||
/// arena, so memo and capture seams copy out the legacy-converted form.
|
||||
/// arena, so memo and capture seams copy out the owned-group form.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must point at a live parked `Artboard` element field.
|
||||
unsafe fn deep_clone_artboard(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
let artboard = unsafe { core_types::record::borrow_element::<Artboard>(core_types::record::Rec::new(ptr)) };
|
||||
Box::new(artboard.with_legacy_groups())
|
||||
let mut content = artboard.0.clone();
|
||||
for element in content.iter_element_values_mut() {
|
||||
*element = crate::graphic::map_groups_to_owned(element);
|
||||
}
|
||||
Box::new(Artboard(content))
|
||||
}
|
||||
|
||||
/// The deep re-park for `Artboard` elements: owned content groups replay into
|
||||
/// the serving arena before the artboard parks.
|
||||
///
|
||||
/// # Safety
|
||||
/// `value` must hold an `Artboard` and `dst` must be a live `Artboard`
|
||||
/// element field.
|
||||
unsafe fn deep_repark_artboard(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &core_types::arena::Arena) -> Option<()> {
|
||||
let artboard = value.downcast_ref::<Artboard>().expect("an element replays at its own type");
|
||||
let mut content = artboard.0.clone();
|
||||
for element in content.iter_element_values_mut() {
|
||||
*element = crate::graphic::map_groups_to_resident(element, arena)?;
|
||||
}
|
||||
unsafe { core_types::record::write_element(dst, Artboard(content), arena) }
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[core_types::ctor::ctor]
|
||||
fn register() {
|
||||
core_types::record::register_deep_element_clone::<Artboard>(deep_clone_artboard);
|
||||
core_types::record::register_deep_element_clone::<Artboard>(deep_clone_artboard, deep_repark_artboard);
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[unsafe(export_name = "__node_registry_deep_element_artboard")]
|
||||
extern "C" fn register() {
|
||||
core_types::record::register_deep_element_clone::<Artboard>(deep_clone_artboard);
|
||||
core_types::record::register_deep_element_clone::<Artboard>(deep_clone_artboard, deep_repark_artboard);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -53,6 +53,25 @@ where
|
||||
/// legacy vocabulary or a capture whose arena generation has passed.
|
||||
pub fn capture_to_legacy(capture: &RecordCapture, arena: &Arena) -> Option<Box<dyn std::any::Any + Send + Sync>> {
|
||||
if capture.layout().depth == 0 {
|
||||
// The group-carrying types legacy-convert while the capture is still
|
||||
// resident; the deep clone-out would hand back the unreadable owned
|
||||
// form.
|
||||
if capture.lanes() > 0 {
|
||||
let element = &capture.layout().element;
|
||||
if element.type_id == std::any::TypeId::of::<Graphic>() {
|
||||
let batch = capture.batch(arena)?;
|
||||
// SAFETY: the layout records the element type, and a parked
|
||||
// element stores its reference at offset 0.
|
||||
let graphic = unsafe { core_types::record::borrow_element::<Graphic>(batch.get(0).rec()) };
|
||||
return Some(Box::new(crate::graphic::map_groups_to_legacy(graphic)));
|
||||
}
|
||||
if element.type_id == std::any::TypeId::of::<Artboard>() {
|
||||
let batch = capture.batch(arena)?;
|
||||
// SAFETY: as for the graphic arm.
|
||||
let artboard = unsafe { core_types::record::borrow_element::<Artboard>(batch.get(0).rec()) };
|
||||
return Some(Box::new(artboard.with_legacy_groups()));
|
||||
}
|
||||
}
|
||||
return capture.materialize_element(arena);
|
||||
}
|
||||
let batch = capture.batch(arena)?;
|
||||
|
||||
@@ -854,29 +854,73 @@ fn push_lane_paint_into_interiors(list: &mut List<Graphic>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The deep clone-out for `Graphic` elements: a plain clone of a group
|
||||
/// The graphic with every `Group` deep-copied to its owned form, which
|
||||
/// survives the arena generation but cannot be read until
|
||||
/// [`map_groups_to_resident`] re-parks it into a serving arena.
|
||||
pub fn map_groups_to_owned(graphic: &Graphic) -> Graphic {
|
||||
match graphic {
|
||||
Graphic::Group(group) => Graphic::Group(group.copy_out()),
|
||||
Graphic::Graphic(children) => {
|
||||
let mut children = children.clone();
|
||||
for child in children.iter_element_values_mut() {
|
||||
*child = map_groups_to_owned(child);
|
||||
}
|
||||
Graphic::Graphic(children)
|
||||
}
|
||||
other => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The graphic with every owned `Group` re-parked into `arena`; `None`
|
||||
/// reports arena exhaustion.
|
||||
pub fn map_groups_to_resident(graphic: &Graphic, arena: &core_types::arena::Arena) -> Option<Graphic> {
|
||||
match graphic {
|
||||
Graphic::Group(group) => group.replay(arena).map(Graphic::Group),
|
||||
Graphic::Graphic(children) => {
|
||||
let mut children = children.clone();
|
||||
for child in children.iter_element_values_mut() {
|
||||
*child = map_groups_to_resident(child, arena)?;
|
||||
}
|
||||
Some(Graphic::Graphic(children))
|
||||
}
|
||||
other => Some(other.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The deep copy-out for `Graphic` elements: a plain clone of a group
|
||||
/// interior would carry frame pointers into the evaluation's arena, so memo
|
||||
/// and capture seams copy out the legacy-converted form, which owns all of
|
||||
/// its content. The generic re-park replays it as an ordinary `Graphic`.
|
||||
/// and capture seams copy out the owned-group form.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must point at a live parked `Graphic` element field.
|
||||
unsafe fn deep_clone_graphic(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
let graphic = unsafe { core_types::record::borrow_element::<Graphic>(core_types::record::Rec::new(ptr)) };
|
||||
Box::new(map_groups_to_legacy(graphic))
|
||||
Box::new(map_groups_to_owned(graphic))
|
||||
}
|
||||
|
||||
/// The deep re-park for `Graphic` elements: owned groups replay into the
|
||||
/// serving arena before the graphic parks.
|
||||
///
|
||||
/// # Safety
|
||||
/// `value` must hold a `Graphic` and `dst` must be a live `Graphic` element
|
||||
/// field.
|
||||
unsafe fn deep_repark_graphic(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &core_types::arena::Arena) -> Option<()> {
|
||||
let graphic = value.downcast_ref::<Graphic>().expect("an element replays at its own type");
|
||||
let resident = map_groups_to_resident(graphic, arena)?;
|
||||
unsafe { core_types::record::write_element(dst, resident, arena) }
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[core_types::ctor::ctor]
|
||||
fn register() {
|
||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic);
|
||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic, deep_repark_graphic);
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[unsafe(export_name = "__node_registry_deep_element_graphic")]
|
||||
extern "C" fn register() {
|
||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic);
|
||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic, deep_repark_graphic);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1201,6 +1245,77 @@ mod run_tests {
|
||||
assert_eq!(paint_graphics::<Fill, _>(&legacy, 0), paint_graphics::<Fill, _>(&run, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_owned_group_replays_content_equal_after_the_source_dies() {
|
||||
let paint = List::new_from_element(Graphic::Color(List::new_from_element(Color::BLACK)));
|
||||
let vector = unit_square_at(DVec2::ZERO);
|
||||
|
||||
let layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[FieldWrite::of::<Fill>(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::<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: core_types::record::GroupContent::Run(item),
|
||||
};
|
||||
let expected = group_to_legacy_list(&group);
|
||||
(map_groups_to_owned(&Graphic::Group(group)), expected)
|
||||
};
|
||||
bytes.fill(u8::MAX);
|
||||
|
||||
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 Graphic::Group(group) = &resident else { panic!("the replay keeps the group form") };
|
||||
assert_eq!(group_to_legacy_list(group), expected);
|
||||
}
|
||||
|
||||
#[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::<Vector>(), &[]);
|
||||
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 nested = Graphic::Group(core_types::record::Group {
|
||||
row: None,
|
||||
content: core_types::record::GroupContent::Run(inner_item),
|
||||
});
|
||||
|
||||
let outer_layout = Layout::default().with_writes(0, element_write_hashed::<Graphic>(), &[]);
|
||||
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: core_types::record::GroupContent::Run(outer_item),
|
||||
};
|
||||
let expected = group_to_legacy_list(&group);
|
||||
(map_groups_to_owned(&Graphic::Group(group)), expected)
|
||||
};
|
||||
outer_bytes.fill(u8::MAX);
|
||||
inner_bytes.fill(u8::MAX);
|
||||
|
||||
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 Graphic::Group(group) = &resident else { panic!("the replay keeps the group form") };
|
||||
assert_eq!(group_to_legacy_list(group), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_run_and_its_legacy_list_agree_on_bounding_boxes() {
|
||||
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(4., 4.))];
|
||||
|
||||
@@ -359,7 +359,11 @@ mod tests {
|
||||
let value = unsafe { core_types::record::borrow_element::<Payload>(core_types::record::Rec::new(ptr)) };
|
||||
Box::new(Payload(value.0.clone(), value.1 + 1))
|
||||
}
|
||||
core_types::record::register_deep_element_clone::<Payload>(deep);
|
||||
unsafe fn deep_repark(value: &(dyn std::any::Any + Send + Sync), dst: *mut u8, arena: &Arena) -> Option<()> {
|
||||
let value = value.downcast_ref::<Payload>().expect("an element replays at its own type");
|
||||
unsafe { core_types::record::write_element(dst, Payload(value.0.clone(), value.1 + 1), arena) }
|
||||
}
|
||||
core_types::record::register_deep_element_clone::<Payload>(deep, deep_repark);
|
||||
|
||||
let arena = Arena::new(4096).unwrap();
|
||||
let generations = [];
|
||||
@@ -371,7 +375,7 @@ mod tests {
|
||||
let memoized = core_types::record::RecordExtract::<Payload, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 0)), "the miss serves the live value");
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 1)), "the hit replays the deep copy");
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 2)), "the hit replays through both halves of the deep glue");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -743,7 +743,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_group_element_deep_copies_to_its_legacy_form() {
|
||||
fn a_group_element_deep_copies_to_its_owned_form_and_replays() {
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -763,12 +763,25 @@ mod tests {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let copy = unsafe { (out.element.clone_out)(out.rec(&value).ptr()) };
|
||||
let Graphic::Graphic(list) = *copy.downcast::<Graphic>().expect("the deep copy replays at the element's own type") else {
|
||||
panic!("expected the legacy-converted form");
|
||||
|
||||
let replay_arena = Arena::new(1 << 16).unwrap();
|
||||
let mut slot = [0u8; 8];
|
||||
unsafe { (out.element.repark)(&*copy, slot.as_mut_ptr(), &replay_arena) }.expect("the arena holds the replay");
|
||||
// SAFETY: the re-park wrote a parked `Graphic` element into `slot`.
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(record::Rec::new(slot.as_ptr())) }) else {
|
||||
panic!("the replay restores the group element");
|
||||
};
|
||||
assert_eq!(list.len(), 2);
|
||||
assert_eq!(text_of(list.element(0).unwrap()), "a");
|
||||
assert_eq!(text_of(list.element(1).unwrap()), "b");
|
||||
let record::GroupContent::Run(item) = &group.content else {
|
||||
panic!("expected a single run");
|
||||
};
|
||||
assert_eq!(item.len(), 2);
|
||||
let lanes = item.typed_lanes::<Graphic>().expect("the run holds the adopted graphic lanes");
|
||||
let offset = item.layout().offset_of(ATTR_TRANSFORM, 0).unwrap();
|
||||
for (lane, (label, x)) in [("a", 1.), ("b", 2.)].into_iter().enumerate() {
|
||||
assert_eq!(text_of(lanes.element_ref(lane)), label, "lane {lane}");
|
||||
let transform: DAffine2 = unsafe { item.lanes().get(lane).rec().read(offset) };
|
||||
assert_eq!(transform.translation.x, x, "lane {lane}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user