mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Resolve a named read's offset when the graph compiles
The fold already holds the name and the read input's finished layout, so the offset falls out there rather than at construction: `RecordLayout` carries the resolved numbers and `set_layout` copies them into the read slots. Constructors are untouched, and census-marker reads keep their current installation. A read meets the value type the name was written at, so a disagreement between a read here and a write upstream is the same graph error as two writes disagreeing; the one-name-one-type check now spans reads and writes together. An absent attribute stays absent and the read serves the forced default rather than reporting it. `read_attribute` is the catalog's get half, typed and never `Option` at the kernel boundary, with the name declared exactly as the write side declares it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -251,7 +251,12 @@ impl Arena {
|
||||
// SAFETY: the caller's contract.
|
||||
unsafe { p.cast::<T>().drop_in_place() }
|
||||
}
|
||||
self.drops.lock().unwrap().push(DropEntry { offset, type_of, drop_fn: glue::<T>, retained });
|
||||
self.drops.lock().unwrap().push(DropEntry {
|
||||
offset,
|
||||
type_of,
|
||||
drop_fn: glue::<T>,
|
||||
retained,
|
||||
});
|
||||
self.retained_heap.fetch_add(retained, Ordering::Relaxed);
|
||||
}
|
||||
// SAFETY: initialized above; insert-only, so no `&mut` to it can exist.
|
||||
@@ -772,7 +777,10 @@ mod tests {
|
||||
|
||||
let (parked, _) = transient.alloc_sized_keyed(Owner(String::from("a keyed park")), 0).unwrap();
|
||||
let src = std::ptr::from_ref(parked).cast::<u8>();
|
||||
assert!(unsafe { transient.move_park::<Twin>(src, &persistent, 0) }.is_none(), "a park of another type of the same size is refused");
|
||||
assert!(
|
||||
unsafe { transient.move_park::<Twin>(src, &persistent, 0) }.is_none(),
|
||||
"a park of another type of the same size is refused"
|
||||
);
|
||||
unsafe { transient.move_park::<Owner>(src, &persistent, 0) }.unwrap();
|
||||
assert!(unsafe { transient.move_park::<Twin>(src, &persistent, 0) }.is_none(), "the forwarding refuses the same mistype");
|
||||
|
||||
|
||||
@@ -439,7 +439,6 @@ impl AttributeDyn {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.len() == 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Clone for AttributeDyn {
|
||||
|
||||
@@ -136,7 +136,6 @@ impl<'a> RecordBatchMut<'a> {
|
||||
// SAFETY: the constructor's contract; the exclusive borrow is consumed.
|
||||
unsafe { RecordBatch::new(self.scratch.as_ptr().cast(), self.len, self.layout) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// One lane's record: its pointer paired with the batch's layout.
|
||||
|
||||
@@ -371,6 +371,12 @@ pub struct RecordLayout {
|
||||
/// leaves it empty; `set_layout` resolves its offsets through these
|
||||
/// instead of through a marker's `NAME`.
|
||||
pub named_writes: Vec<&'static str>,
|
||||
/// The offsets the fold resolved for this node's name-from-input reads, in
|
||||
/// placeholder order. `None` is an absent attribute, which the read serves
|
||||
/// as the name's forced default. Resolved in the compiler, where the folded
|
||||
/// name and the read input's finished layout sit together, so `set_layout`
|
||||
/// only copies the numbers into the read slots.
|
||||
pub named_reads: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
/// A write whose name comes from the graph rather than from a marker: the
|
||||
@@ -399,6 +405,34 @@ impl NamedWrite {
|
||||
}
|
||||
}
|
||||
|
||||
/// A read whose name comes from the graph rather than from a marker. The
|
||||
/// compiler resolves it to an offset in the read input's own layout, where
|
||||
/// the folded name and that finished layout already sit together.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NamedRead {
|
||||
/// The proto input the attribute is read from.
|
||||
pub input: u8,
|
||||
/// The proto input holding the name's constant text.
|
||||
pub name_input: u8,
|
||||
/// The read's field form, every facet but the name minted from the
|
||||
/// concrete value type, as for a write.
|
||||
pub template: FieldWrite,
|
||||
}
|
||||
|
||||
impl NamedRead {
|
||||
/// The template for a name-generic marker's read at `level`.
|
||||
pub fn of<X: 'static, V: crate::attribute::AttrValue>(input: u8, name_input: u8, level: u8) -> Self
|
||||
where
|
||||
V::Value<'static>: graphene_hash::CacheHash + PartialEq + 'static,
|
||||
{
|
||||
Self {
|
||||
input,
|
||||
name_input,
|
||||
template: FieldWrite::of::<crate::attribute::Named<X, V>>(level),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Declarative record-io metadata for a node type, emitted by the macro into
|
||||
/// its registry entry so the compiler can fold each input's layout without
|
||||
/// running the node's constructor. [`fold`](LayoutMeta::fold) reproduces the
|
||||
@@ -426,6 +460,11 @@ pub struct LayoutMeta {
|
||||
/// The names the fold gave [`named_writes`](Self::named_writes), in
|
||||
/// placeholder order. Empty until the fold runs.
|
||||
pub folded_names: Vec<&'static str>,
|
||||
/// The attributes the node reads under a name taken from the graph.
|
||||
pub named_reads: Vec<NamedRead>,
|
||||
/// The names the fold gave [`named_reads`](Self::named_reads), in
|
||||
/// placeholder order. Empty until the fold runs.
|
||||
pub folded_read_names: Vec<&'static str>,
|
||||
/// The attributes removed from the base layout, as `(name, level)`.
|
||||
pub removes: Vec<(&'static str, u8)>,
|
||||
/// The depth change the node applies: `0` for elementwise and flip nodes,
|
||||
@@ -464,6 +503,8 @@ impl LayoutMeta {
|
||||
writes: Vec::new(),
|
||||
named_writes: Vec::new(),
|
||||
folded_names: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
folded_read_names: Vec::new(),
|
||||
removes: Vec::new(),
|
||||
level_delta: 0,
|
||||
folded: None,
|
||||
@@ -508,12 +549,22 @@ impl LayoutMeta {
|
||||
// A reducer collapses its carrier's levels, so it writes a fresh record rather than copying fields down.
|
||||
_ => Vec::new(),
|
||||
};
|
||||
// A named read resolves against the input it reads, whose layout is
|
||||
// finished by the time this node folds. An absent attribute stays
|
||||
// `None`, which the read serves as the name's forced default.
|
||||
let named_reads = self
|
||||
.named_reads
|
||||
.iter()
|
||||
.zip(&self.folded_read_names)
|
||||
.map(|(read, name)| inputs.get(read.input as usize).copied().flatten().and_then(|layout| layout.offset_of(name, read.template.level)))
|
||||
.collect();
|
||||
RecordLayout {
|
||||
layout,
|
||||
frame_bytes,
|
||||
plan,
|
||||
lane_invariant: 0,
|
||||
named_writes: self.folded_names.clone(),
|
||||
named_reads,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +582,13 @@ impl LayoutMeta {
|
||||
self.writes.push(write);
|
||||
self.folded_names.push(name);
|
||||
}
|
||||
|
||||
/// Records the name of the name-from-input read at `index`. The offset
|
||||
/// itself waits for [`resolve`](Self::resolve), which is where the read
|
||||
/// input's finished layout arrives.
|
||||
pub fn fold_read_name(&mut self, name: &'static str) {
|
||||
self.folded_read_names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Field-by-field carry from `from`'s layout into `to`'s, computed at
|
||||
|
||||
@@ -25,8 +25,8 @@ pub use access::{Rec, RecordValue, apply_plan, borrow_element, erase_static, rea
|
||||
pub use frames::{FrameArena, FrameScope, Frames};
|
||||
pub use input::{DerivedLazyInput, DerivedRecordInput, ElementInput, ElementLazyInput, LevelStatus, RecordExtract, RecordInput, RecordLazyInput, fill_frames, materialize_batch, materialize_level};
|
||||
pub use layout::{
|
||||
ElToken, ElementSpec, ElementWrite, ElementWritePick, ElementWritePickHashed, ElementWritePickPlain, FieldDesc, FieldOffset, FieldWrite, InputReads, Layout, LayoutMeta, NamedWrite, RecordLayout, copy_plan,
|
||||
element_dims, element_parked, element_write, element_write_hashed, empty_layout,
|
||||
ElToken, ElementSpec, ElementWrite, ElementWritePick, ElementWritePickHashed, ElementWritePickPlain, FieldDesc, FieldOffset, FieldWrite, InputReads, Layout, LayoutMeta, NamedRead, NamedWrite,
|
||||
RecordLayout, copy_plan, element_dims, element_parked, element_write, element_write_hashed, empty_layout,
|
||||
};
|
||||
pub use owned::{OwnedRecord, deepen_field_value, has_deep_element_glue, register_deep_element_clone, register_deep_field_value, replay_field_value};
|
||||
pub use promote::{Promotion, assert_promoted, register_element_promote, register_field_promote, register_retained_heap};
|
||||
|
||||
@@ -57,10 +57,7 @@ static DEEP_FIELD_VALUES: std::sync::LazyLock<std::sync::Mutex<std::collections:
|
||||
|
||||
/// 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: crate::list::FieldReplayFn,
|
||||
) {
|
||||
pub fn register_deep_field_value<T: 'static>(copy_out: fn(&dyn crate::list::AnyAttributeValue) -> Option<Box<dyn crate::list::AnyAttributeValue>>, replay: crate::list::FieldReplayFn) {
|
||||
DEEP_FIELD_VALUES.lock().unwrap().insert(std::any::TypeId::of::<T>(), DeepFieldGlue { copy_out, replay });
|
||||
}
|
||||
|
||||
|
||||
@@ -279,6 +279,7 @@ mod tests {
|
||||
layout: layout.clone(),
|
||||
lane_invariant: u32::MAX,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
});
|
||||
RecordExtract::new(graph, &layout)
|
||||
}
|
||||
|
||||
@@ -505,12 +505,7 @@ mod run_tests {
|
||||
}
|
||||
|
||||
/// The promoted paint of one lane, at the layout the promote published.
|
||||
fn promoted_paint<'p>(
|
||||
span: &core_types::record::MaterializedSpan,
|
||||
layout: &core_types::record::Layout,
|
||||
lane: usize,
|
||||
persistent: &'p core_types::arena::Arena,
|
||||
) -> &'p List<Graphic<'p>> {
|
||||
fn promoted_paint<'p>(span: &core_types::record::MaterializedSpan, layout: &core_types::record::Layout, lane: usize, persistent: &'p core_types::arena::Arena) -> &'p List<Graphic<'p>> {
|
||||
let offset = layout.offset_of(Fill::NAME, 0).unwrap();
|
||||
let batch = span.batch(persistent, layout).expect("the span resolves in its own region");
|
||||
// SAFETY: the promote wrote a record of `layout` into every lane.
|
||||
@@ -527,7 +522,9 @@ mod run_tests {
|
||||
// list the evaluation parked.
|
||||
let published = native_group_paint(&inner_vector, &persistent);
|
||||
let interior = {
|
||||
let Some(Graphic::Group(group)) = published.element(0) else { panic!("the paint carries a native group") };
|
||||
let Some(Graphic::Group(group)) = published.element(0) else {
|
||||
panic!("the paint carries a native group")
|
||||
};
|
||||
group.content.lanes().get(0).rec().ptr()
|
||||
};
|
||||
// SAFETY: the list serves only while `persistent` is live, and the
|
||||
@@ -538,7 +535,9 @@ mod run_tests {
|
||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
|
||||
let Some(Graphic::Group(group)) = served.element(0) else { panic!("the promote keeps the group form") };
|
||||
let Some(Graphic::Group(group)) = served.element(0) else {
|
||||
panic!("the promote keeps the group form")
|
||||
};
|
||||
assert_eq!(group.content.lanes().get(0).rec().ptr(), interior, "a persistent interior is shared pointer for pointer");
|
||||
assert!(
|
||||
persistent.occupancy() - occupied <= layout.frame_bytes() + size_of::<List<Graphic>>() + align_of::<List<Graphic>>(),
|
||||
@@ -560,8 +559,14 @@ mod run_tests {
|
||||
|
||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 2, &transient, &persistent);
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else { panic!("the promote keeps the vector") };
|
||||
assert_eq!(vector.point_domain.positions().as_ptr(), heap, "the promote moved the header, so the served paint names the pre-promote heap");
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else {
|
||||
panic!("the promote keeps the vector")
|
||||
};
|
||||
assert_eq!(
|
||||
vector.point_domain.positions().as_ptr(),
|
||||
heap,
|
||||
"the promote moved the header, so the served paint names the pre-promote heap"
|
||||
);
|
||||
assert!(std::ptr::eq(served, promoted_paint(&span, &layout, 1, &persistent)), "a paint two lanes share moves once");
|
||||
|
||||
transient.reset();
|
||||
@@ -593,7 +598,11 @@ mod run_tests {
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
let held = served.attribute::<Option<List<Graphic>>>(Stroke::NAME, 0).expect("the stroke attribute rides the promoted list");
|
||||
let held = held.as_ref().expect("the stroke is present");
|
||||
assert_eq!(map_groups_to_legacy(held.element(0).unwrap()), expected, "the attribute-held group serves from persistent storage after the reset");
|
||||
assert_eq!(
|
||||
map_groups_to_legacy(held.element(0).unwrap()),
|
||||
expected,
|
||||
"the attribute-held group serves from persistent storage after the reset"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -615,8 +624,14 @@ mod run_tests {
|
||||
|
||||
let (layout, span, _frames) = promote_paint_field(Some(paint), 1, &transient, &persistent);
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else { panic!("the promote keeps the vector") };
|
||||
assert_eq!(vector.point_domain.positions().as_ptr(), heap, "the promote moved the header, so the served paint names the pre-promote heap");
|
||||
let Some(Graphic::Vector(vector)) = served.element(0) else {
|
||||
panic!("the promote keeps the vector")
|
||||
};
|
||||
assert_eq!(
|
||||
vector.point_domain.positions().as_ptr(),
|
||||
heap,
|
||||
"the promote moved the header, so the served paint names the pre-promote heap"
|
||||
);
|
||||
|
||||
transient.reset();
|
||||
let served = promoted_paint(&span, &layout, 0, &persistent);
|
||||
|
||||
Reference in New Issue
Block a user