mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Deepen graphic-list field values at the persistence seams
This commit is contained in:
@@ -1275,6 +1275,42 @@ fn deep_element_glue(type_id: std::any::TypeId) -> Option<DeepElementGlue> {
|
||||
DEEP_ELEMENT_CLONES.lock().unwrap().get(&type_id).copied()
|
||||
}
|
||||
|
||||
/// Deep-copy overrides for field values whose content borrows the
|
||||
/// evaluation's arena (a graphic list holding native groups), keyed by the
|
||||
/// field's owned value form. Consulted at the persistence seams only:
|
||||
/// `read_erased` itself stays shallow, since introspection reads captures in
|
||||
/// generation. Both halves decline when the value already owns all of its
|
||||
/// content, so group-free values pay no extra clone: `copy_out` returns
|
||||
/// `None` for unchanged, `replay` returns `Some(None)` for unchanged and
|
||||
/// `None` for arena exhaustion.
|
||||
#[derive(Clone, Copy)]
|
||||
struct DeepFieldGlue {
|
||||
copy_out: fn(&dyn crate::list::AnyAttributeValue) -> Option<Box<dyn crate::list::AnyAttributeValue>>,
|
||||
replay: fn(&dyn crate::list::AnyAttributeValue, &crate::arena::Arena) -> Option<Option<Box<dyn crate::list::AnyAttributeValue>>>,
|
||||
}
|
||||
|
||||
static DEEP_FIELD_VALUES: std::sync::LazyLock<std::sync::Mutex<std::collections::HashMap<std::any::TypeId, DeepFieldGlue>>> = std::sync::LazyLock::new(Default::default);
|
||||
|
||||
/// Registers the deep copy-out and replay pair for field values of `T`.
|
||||
/// Called at startup from the crate that owns the type.
|
||||
pub fn register_deep_field_value<T: 'static>(
|
||||
copy_out: fn(&dyn crate::list::AnyAttributeValue) -> Option<Box<dyn crate::list::AnyAttributeValue>>,
|
||||
replay: fn(&dyn crate::list::AnyAttributeValue, &crate::arena::Arena) -> Option<Option<Box<dyn crate::list::AnyAttributeValue>>>,
|
||||
) {
|
||||
DEEP_FIELD_VALUES.lock().unwrap().insert(std::any::TypeId::of::<T>(), DeepFieldGlue { copy_out, replay });
|
||||
}
|
||||
|
||||
fn deep_field_glue(type_id: std::any::TypeId) -> Option<DeepFieldGlue> {
|
||||
DEEP_FIELD_VALUES.lock().unwrap().get(&type_id).copied()
|
||||
}
|
||||
|
||||
fn deepen_field_value(value: Box<dyn crate::list::AnyAttributeValue>) -> Box<dyn crate::list::AnyAttributeValue> {
|
||||
match deep_field_glue(value.as_any().type_id()) {
|
||||
Some(glue) => (glue.copy_out)(&*value).unwrap_or(value),
|
||||
None => value,
|
||||
}
|
||||
}
|
||||
|
||||
/// The element slot a record wire of `T` carries, its erased glue bound at
|
||||
/// the statically-known type.
|
||||
pub fn element_write<T: Clone + Send + Sync + 'static>() -> ElementWrite {
|
||||
@@ -1635,7 +1671,7 @@ impl OwnedRecord {
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, field)| field.repark.is_some())
|
||||
.map(|(index, field)| (index, unsafe { (field.read_erased)(rec.ptr().add(field.offset)) }))
|
||||
.map(|(index, field)| (index, deepen_field_value(unsafe { (field.read_erased)(rec.ptr().add(field.offset)) })))
|
||||
.collect();
|
||||
OwnedRecord { bytes, element, fields }
|
||||
}
|
||||
@@ -1665,7 +1701,13 @@ impl OwnedRecord {
|
||||
for (index, value) in &self.fields {
|
||||
let field = &layout.fields[*index];
|
||||
let repark = field.repark.expect("copied fields carry re-park glue");
|
||||
unsafe { repark(&**value, dst.add(field.offset), arena) }?;
|
||||
match deep_field_glue(value.as_any().type_id()) {
|
||||
Some(glue) => match (glue.replay)(&**value, arena)? {
|
||||
Some(resident) => unsafe { repark(&*resident, dst.add(field.offset), arena) }?,
|
||||
None => unsafe { repark(&**value, dst.add(field.offset), arena) }?,
|
||||
},
|
||||
None => unsafe { repark(&**value, dst.add(field.offset), arena) }?,
|
||||
}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
@@ -1925,7 +1967,14 @@ impl GroupItem {
|
||||
.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();
|
||||
let mut values: Vec<_> = (0..self.len).map(|lane| unsafe { (field.read_erased)(frames.add(lane * stride + field.offset)) }).collect();
|
||||
if let Some(glue) = values.first().and_then(|value| deep_field_glue(value.as_any().type_id())) {
|
||||
for value in &mut values {
|
||||
if let Some(deepened) = (glue.copy_out)(&**value) {
|
||||
*value = deepened;
|
||||
}
|
||||
}
|
||||
}
|
||||
(index, values)
|
||||
})
|
||||
.collect();
|
||||
@@ -1957,8 +2006,15 @@ impl GroupItem {
|
||||
for (index, values) in &owned.fields {
|
||||
let field = &self.layout.fields[*index];
|
||||
let repark = field.repark.expect("copied fields carry re-park glue");
|
||||
let glue = values.first().and_then(|value| deep_field_glue(value.as_any().type_id()));
|
||||
for (lane, value) in values.iter().enumerate() {
|
||||
unsafe { repark(&**value, frames.add(lane * stride + field.offset), arena) }?;
|
||||
match glue {
|
||||
Some(glue) => match (glue.replay)(&**value, arena)? {
|
||||
Some(resident) => unsafe { repark(&*resident, frames.add(lane * stride + field.offset), arena) }?,
|
||||
None => unsafe { repark(&**value, frames.add(lane * stride + field.offset), arena) }?,
|
||||
},
|
||||
None => unsafe { repark(&**value, frames.add(lane * stride + field.offset), arena) }?,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(GroupItem {
|
||||
|
||||
@@ -910,17 +910,62 @@ unsafe fn deep_repark_graphic(value: &(dyn std::any::Any + Send + Sync), dst: *m
|
||||
unsafe { core_types::record::write_element(dst, resident, arena) }
|
||||
}
|
||||
|
||||
fn graphic_contains_groups(graphic: &Graphic) -> bool {
|
||||
match graphic {
|
||||
Graphic::Group(_) => true,
|
||||
Graphic::Graphic(children) => list_contains_groups(children),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn list_contains_groups(list: &List<Graphic>) -> bool {
|
||||
(0..list.len()).any(|index| list.element(index).is_some_and(graphic_contains_groups))
|
||||
}
|
||||
|
||||
/// The deep copy-out for graphic-list field values (the paint markers' owned
|
||||
/// form): content groups leave in their owned form. Declines (`None`) for
|
||||
/// group-free content, which already owns everything.
|
||||
fn deep_clone_graphic_list(value: &dyn core_types::list::AnyAttributeValue) -> Option<Box<dyn core_types::list::AnyAttributeValue>> {
|
||||
let list = value.as_any().downcast_ref::<Option<List<Graphic>>>().expect("a graphic list field deep-copies at its own type");
|
||||
let list = list.as_ref().filter(|list| list_contains_groups(list))?;
|
||||
let mut list = list.clone();
|
||||
for element in list.iter_element_values_mut() {
|
||||
*element = map_groups_to_owned(element);
|
||||
}
|
||||
Some(Box::new(Some(list)))
|
||||
}
|
||||
|
||||
/// The deep replay for graphic-list field values: owned content groups replay
|
||||
/// into the serving arena before the field re-parks. `Some(None)` declines
|
||||
/// for group-free content; `None` reports arena exhaustion.
|
||||
fn deep_repark_graphic_list(value: &dyn core_types::list::AnyAttributeValue, arena: &core_types::arena::Arena) -> Option<Option<Box<dyn core_types::list::AnyAttributeValue>>> {
|
||||
let list = value.as_any().downcast_ref::<Option<List<Graphic>>>().expect("a graphic list field replays at its own type");
|
||||
let Some(list) = list.as_ref().filter(|list| list_contains_groups(list)) else {
|
||||
return Some(None);
|
||||
};
|
||||
let mut list = list.clone();
|
||||
for element in list.iter_element_values_mut() {
|
||||
*element = map_groups_to_resident(element, arena)?;
|
||||
}
|
||||
Some(Some(Box::new(Some(list))))
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
fn register_all() {
|
||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic, deep_repark_graphic);
|
||||
core_types::record::register_deep_field_value::<Option<List<Graphic>>>(deep_clone_graphic_list, deep_repark_graphic_list);
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[core_types::ctor::ctor]
|
||||
fn register() {
|
||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic, deep_repark_graphic);
|
||||
register_all();
|
||||
}
|
||||
|
||||
#[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, deep_repark_graphic);
|
||||
register_all();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1316,6 +1361,81 @@ mod run_tests {
|
||||
assert_eq!(group_to_legacy_list(group), expected);
|
||||
}
|
||||
|
||||
fn native_group_paint(vector: &Vector, inner_layout: &Layout, inner_bytes: &mut Vec<u8>) -> List<Graphic> {
|
||||
// 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)) };
|
||||
List::new_from_element(Graphic::Group(core_types::record::Group {
|
||||
row: None,
|
||||
content: core_types::record::GroupContent::Run(inner_item),
|
||||
}))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_owned_run_deep_copies_graphic_list_fields() {
|
||||
let inner_vector = unit_square_at(DVec2::ZERO);
|
||||
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]);
|
||||
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()];
|
||||
let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes);
|
||||
|
||||
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 bytes = vec![0u8; layout.lane_stride()];
|
||||
// SAFETY: `bytes` is one lane of `layout`; a parked element stores its
|
||||
// reference, and the fill field stores the marker's value form.
|
||||
unsafe {
|
||||
bytes.as_mut_ptr().cast::<&Vector>().write(&vector);
|
||||
bytes.as_mut_ptr().add(layout.offset_of(Fill::NAME, 0).unwrap()).cast::<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)) };
|
||||
(item.copy_out(), map_groups_to_legacy(paint.element(0).unwrap()))
|
||||
};
|
||||
bytes.fill(u8::MAX);
|
||||
inner_bytes.fill(u8::MAX);
|
||||
drop(paint);
|
||||
|
||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
let replayed = owned.replay(&arena).expect("the arena holds the replay");
|
||||
let run = RunView::<Vector>::new(&replayed).expect("the run holds vector elements");
|
||||
let served = run.attr::<Fill>(0).expect("the fill replays present");
|
||||
assert_eq!(map_groups_to_legacy(served.element(0).unwrap()), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_owned_record_deep_copies_graphic_list_fields() {
|
||||
let inner_vector = unit_square_at(DVec2::ZERO);
|
||||
let inner_layout = Layout::default().with_writes(0, element_write_hashed::<Vector>(), &[]);
|
||||
let mut inner_bytes = vec![0u8; inner_layout.lane_stride()];
|
||||
let paint = native_group_paint(&inner_vector, &inner_layout, &mut inner_bytes);
|
||||
|
||||
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 offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
||||
let mut bytes = vec![0u8; layout.lane_stride()];
|
||||
// SAFETY: as in the run test above.
|
||||
unsafe {
|
||||
bytes.as_mut_ptr().cast::<&Vector>().write(&vector);
|
||||
bytes.as_mut_ptr().add(offset).cast::<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());
|
||||
bytes.fill(u8::MAX);
|
||||
inner_bytes.fill(u8::MAX);
|
||||
drop(paint);
|
||||
|
||||
let arena = core_types::arena::Arena::new(1 << 16).unwrap();
|
||||
core_types::record::stack::reserve(layout.frame_bytes());
|
||||
let value = owned.replay(&layout, &arena).expect("the arena holds the replay");
|
||||
// SAFETY: the replay wrote a record of `layout`.
|
||||
let served = unsafe { layout.rec(&value).read::<Option<&List<Graphic>>>(offset) }.expect("the fill replays present");
|
||||
assert_eq!(map_groups_to_legacy(served.element(0).unwrap()), 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.))];
|
||||
|
||||
Reference in New Issue
Block a user