mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Serve a declared name's census default on an absent read
An absent read of a name the census declares now serves that name's own default rather than its value type's, which is the rule for a declared name. The census stages it exactly as it fills any absent field, as the declared default's bytes, so the read reuses the census's own mechanism instead of a second one; the compiler takes them when the name folds and `set_layout` installs them beside the offset. A name the census does not declare keeps the value type's default, which is that case's own rule. One name carries one value type, checked when the name folds, so the row's value type and width agree with the read's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -392,6 +392,7 @@ impl ProtoNetwork {
|
||||
lane_invariant,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
layout,
|
||||
}),
|
||||
ConstructionArgs::Nodes(inputs) => node.resolved.layout_meta.as_ref().and_then(|meta| {
|
||||
|
||||
@@ -841,6 +841,13 @@ mod test {
|
||||
assert_eq!(read_back(read_attribute_network("opacity", "opacity", TaggedValue::F64(0.5))), 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_absent_census_named_read_serves_the_census_default() {
|
||||
// `opacity` is declared `f64` defaulting to 1, so its absence reads as
|
||||
// the census default rather than the value type's.
|
||||
assert_eq!(read_back(read_attribute_network("novel:count", "opacity", TaggedValue::F64(2.5))), 1.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_named_read_disagreeing_with_its_write_is_refused() {
|
||||
// The name is written at a path upstream and read at `f64` here, which
|
||||
|
||||
@@ -114,6 +114,29 @@ pub unsafe fn read_at<'e, A: attribute::Attribute>(rec: Rec<'_>, offset: Option<
|
||||
})
|
||||
}
|
||||
|
||||
/// [`read_at`] for a name-generic read, whose marker carries the value type
|
||||
/// but not the name and so cannot know the name's own default. `absent` is
|
||||
/// that default's bytes, which the compiler takes from the census once the
|
||||
/// name is folded; without one the value type's default applies, which is
|
||||
/// exactly the rule for a name the census does not declare.
|
||||
///
|
||||
/// # Safety
|
||||
/// As [`read_at`]. `absent`, where present, must hold the object
|
||||
/// representation of `A::Value`, which the compiler takes from the census row
|
||||
/// for the folded name after checking that row's value type against `A`'s. A
|
||||
/// reference-valued default addresses `'static` data, so the value it yields
|
||||
/// outlives any evaluation.
|
||||
pub unsafe fn read_at_defaulting<'e, A: attribute::Attribute>(rec: Rec<'_>, offset: Option<usize>, absent: Option<&[u8]>) -> attribute::Attr<'e, A> {
|
||||
attribute::Attr(match (offset, absent) {
|
||||
// SAFETY: the caller's contract.
|
||||
(Some(offset), _) => unsafe { rec.read::<A::Value<'e>>(offset) },
|
||||
// SAFETY: the caller's contract; the bytes are unaligned storage, so
|
||||
// the value is read out rather than referenced in place.
|
||||
(None, Some(bytes)) => unsafe { bytes.as_ptr().cast::<A::Value<'e>>().read_unaligned() },
|
||||
(None, None) => A::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The read-less [`DerivedLazyInput`] glue: the token alone.
|
||||
///
|
||||
/// # Safety
|
||||
|
||||
@@ -377,6 +377,11 @@ pub struct RecordLayout {
|
||||
/// 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>>,
|
||||
/// The census default's bytes for each absent name-from-input read whose
|
||||
/// name the census declares, in the same order. A name the census does not
|
||||
/// declare has `None` and reads as its value type's default, which is that
|
||||
/// case's own rule; a read that resolved to an offset needs no default.
|
||||
pub named_read_defaults: Vec<Option<Box<[u8]>>>,
|
||||
}
|
||||
|
||||
/// A write whose name comes from the graph rather than from a marker: the
|
||||
@@ -552,12 +557,31 @@ impl LayoutMeta {
|
||||
// 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
|
||||
let named_reads: Vec<Option<usize>> = 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();
|
||||
// An absent read serves the name's own default, which for a declared
|
||||
// name is the census's rather than the value type's. The census stages
|
||||
// it the same way it fills any absent field: as the declared default's
|
||||
// bytes. One name carries one value type, checked when the name folds,
|
||||
// so the size agreement below is a guard rather than a branch.
|
||||
let named_read_defaults = self
|
||||
.named_reads
|
||||
.iter()
|
||||
.zip(&self.folded_read_names)
|
||||
.zip(&named_reads)
|
||||
.map(|((read, name), offset)| {
|
||||
let row = offset.is_none().then(|| attribute::info(name))??;
|
||||
(row.value_type == read.template.type_id && row.size == read.template.size).then(|| {
|
||||
let mut bytes = vec![0u8; row.size];
|
||||
(row.write_default_bytes)(&mut bytes);
|
||||
bytes.into_boxed_slice()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
RecordLayout {
|
||||
layout,
|
||||
frame_bytes,
|
||||
@@ -565,6 +589,7 @@ impl LayoutMeta {
|
||||
lane_invariant: 0,
|
||||
named_writes: self.folded_names.clone(),
|
||||
named_reads,
|
||||
named_read_defaults,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ mod serve;
|
||||
mod test_support;
|
||||
mod testkit;
|
||||
|
||||
pub use access::{Rec, RecordValue, apply_plan, borrow_element, erase_static, read_at, read_element, token_only, write_element, write_element_sized, write_field};
|
||||
pub use access::{Rec, RecordValue, apply_plan, borrow_element, erase_static, read_at, read_at_defaulting, read_element, token_only, write_element, write_element_sized, write_field};
|
||||
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::{
|
||||
|
||||
@@ -280,6 +280,7 @@ mod tests {
|
||||
lane_invariant: u32::MAX,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
});
|
||||
RecordExtract::new(graph, &layout)
|
||||
}
|
||||
|
||||
@@ -240,6 +240,18 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let slot = format_ident!("__read_{index}");
|
||||
quote!(pub(super) #slot: Option<usize>)
|
||||
}));
|
||||
// A name-generic read carries the folded name's own default beside its
|
||||
// offset, since its marker names the value type but not the name.
|
||||
state.extend(
|
||||
field_reads(&struct_regular_fields)
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, read))| crate::parsing::named_marker(&read.marker).is_some())
|
||||
.map(|(index, _)| {
|
||||
let slot = format_ident!("__read_default_{index}");
|
||||
quote!(pub(super) #slot: Option<::std::boxed::Box<[u8]>>)
|
||||
}),
|
||||
);
|
||||
state.extend((0..node.output.shape.attrs.len()).map(|index| {
|
||||
let slot = format_ident!("__write_{index}");
|
||||
quote!(pub(super) #slot: usize)
|
||||
@@ -1244,7 +1256,15 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
let read_binding = |slot: usize, read: &AttributeRead, rec: TokenStream2| {
|
||||
let pat = &read.pat_ident;
|
||||
let marker = &read.marker;
|
||||
let default_slot = format_ident!("__read_default_{slot}");
|
||||
let slot = format_ident!("__read_{slot}");
|
||||
// A name-generic marker carries the value type but not the name, so it
|
||||
// cannot know the name's own default; the compiler supplies it.
|
||||
if crate::parsing::named_marker(marker).is_some() {
|
||||
return quote! {
|
||||
let #pat = unsafe { #core_types::record::read_at_defaulting::<#marker>(#rec, self.#slot, self.#default_slot.as_deref()) };
|
||||
};
|
||||
}
|
||||
quote! {
|
||||
let #pat = unsafe { #core_types::record::read_at::<#marker>(#rec, self.#slot) };
|
||||
}
|
||||
@@ -2554,10 +2574,14 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
.enumerate()
|
||||
.filter(|(_, (_, read))| crate::parsing::named_marker(&read.marker).is_some())
|
||||
.map(|(slot, _)| {
|
||||
let default_slot = format_ident!("__read_default_{slot}");
|
||||
let slot = format_ident!("__read_{slot}");
|
||||
let position = folded_read;
|
||||
folded_read += 1;
|
||||
quote!(self.#slot = __resolved.named_reads[#position];)
|
||||
quote! {
|
||||
self.#slot = __resolved.named_reads[#position];
|
||||
self.#default_slot = __resolved.named_read_defaults[#position].clone();
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let plan = (!skips_carrier || gather_carrier).then(|| quote!(self.__plan = __resolved.plan;));
|
||||
@@ -2711,6 +2735,16 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
});
|
||||
let plan_default = (!skips_carrier || gather_carrier).then(|| quote!(__plan: ::std::vec::Vec::new(),)).into_iter();
|
||||
let read_names = (0..flat_reads.len()).map(|index| format_ident!("__read_{index}")).map(|slot| quote!(#slot,));
|
||||
// The folded name's default arrives with the layout, so the constructor
|
||||
// leaves the slot empty and `set_layout` fills it.
|
||||
let read_default_inits = flat_reads
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, (_, read))| crate::parsing::named_marker(&read.marker).is_some())
|
||||
.map(|(index, _)| {
|
||||
let slot = format_ident!("__read_default_{index}");
|
||||
quote!(#slot: ::core::option::Option::None,)
|
||||
});
|
||||
let write_defaults = (0..write_markers.len()).map(|index| format_ident!("__write_{index}")).map(|slot| quote!(#slot: 0,));
|
||||
let mat_cache_defaults = materialized_indices(®ular_fields, &node).into_iter().map(|index| {
|
||||
let slot = format_ident!("__mat_cache_{index}");
|
||||
@@ -2757,6 +2791,7 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
||||
__frame_bytes: 0,
|
||||
__lane_invariant: 0,
|
||||
#(#read_names)*
|
||||
#(#read_default_inits)*
|
||||
#(#write_defaults)*
|
||||
#(#mat_cache_defaults)*
|
||||
#(#slot_default)*
|
||||
|
||||
@@ -664,6 +664,7 @@ mod tests {
|
||||
let resolved = core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
lane_invariant: u32::MAX,
|
||||
..meta.resolve(inputs)
|
||||
};
|
||||
@@ -675,6 +676,7 @@ mod tests {
|
||||
let bundle = core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -1581,6 +1583,7 @@ mod tests {
|
||||
let resolved = core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
lane_invariant: 0,
|
||||
..mirror_layout_meta().resolve(&[Some(&layout)])
|
||||
};
|
||||
|
||||
@@ -287,6 +287,7 @@ mod tests {
|
||||
let resolved = record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
lane_invariant: u32::MAX,
|
||||
..meta.resolve(inputs)
|
||||
};
|
||||
@@ -298,6 +299,7 @@ mod tests {
|
||||
let bundle = record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
|
||||
@@ -274,6 +274,7 @@ mod tests {
|
||||
lane_invariant: u32::MAX,
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
},
|
||||
);
|
||||
let GPoll::Final(result) = core_types::record::serve_input(&graph, &ctx, &frames) else {
|
||||
|
||||
@@ -1055,6 +1055,7 @@ mod graphene_test {
|
||||
node.set_layout(core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -1118,6 +1119,7 @@ mod graphene_test {
|
||||
wired.set_layout(core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
@@ -1166,6 +1168,7 @@ mod graphene_test {
|
||||
wired.set_layout(core_types::record::RecordLayout {
|
||||
named_writes: Vec::new(),
|
||||
named_reads: Vec::new(),
|
||||
named_read_defaults: Vec::new(),
|
||||
frame_bytes: layout.frame_bytes(),
|
||||
plan: Vec::new(),
|
||||
layout: layout.clone(),
|
||||
|
||||
Reference in New Issue
Block a user