mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 13:28:11 +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_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 element slot a record wire of `T` carries, its erased glue bound at
|
||||||
/// the statically-known type.
|
/// the statically-known type.
|
||||||
pub fn element_write<T: Clone + Send + Sync + 'static>() -> ElementWrite {
|
pub fn element_write<T: Clone + Send + Sync + 'static>() -> ElementWrite {
|
||||||
@@ -1635,7 +1671,7 @@ impl OwnedRecord {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, field)| field.repark.is_some())
|
.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();
|
.collect();
|
||||||
OwnedRecord { bytes, element, fields }
|
OwnedRecord { bytes, element, fields }
|
||||||
}
|
}
|
||||||
@@ -1665,7 +1701,13 @@ impl OwnedRecord {
|
|||||||
for (index, value) in &self.fields {
|
for (index, value) in &self.fields {
|
||||||
let field = &layout.fields[*index];
|
let field = &layout.fields[*index];
|
||||||
let repark = field.repark.expect("copied fields carry re-park glue");
|
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(())
|
Some(())
|
||||||
}
|
}
|
||||||
@@ -1925,7 +1967,14 @@ impl GroupItem {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, field)| field.repark.is_some())
|
.filter(|(_, field)| field.repark.is_some())
|
||||||
.map(|(index, field)| {
|
.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)
|
(index, values)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -1957,8 +2006,15 @@ impl GroupItem {
|
|||||||
for (index, values) in &owned.fields {
|
for (index, values) in &owned.fields {
|
||||||
let field = &self.layout.fields[*index];
|
let field = &self.layout.fields[*index];
|
||||||
let repark = field.repark.expect("copied fields carry re-park glue");
|
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() {
|
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 {
|
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) }
|
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 _: () = {
|
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"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
#[core_types::ctor::ctor]
|
#[core_types::ctor::ctor]
|
||||||
fn register() {
|
fn register() {
|
||||||
core_types::record::register_deep_element_clone::<Graphic>(deep_clone_graphic, deep_repark_graphic);
|
register_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
#[unsafe(export_name = "__node_registry_deep_element_graphic")]
|
#[unsafe(export_name = "__node_registry_deep_element_graphic")]
|
||||||
extern "C" fn register() {
|
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);
|
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]
|
#[test]
|
||||||
fn a_run_and_its_legacy_list_agree_on_bounding_boxes() {
|
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.))];
|
let vectors = [unit_square_at(DVec2::ZERO), unit_square_at(DVec2::new(4., 4.))];
|
||||||
|
|||||||
Reference in New Issue
Block a user