mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 22:38:11 +08:00
Carry a name-generic attribute's value type as its own row
An attribute's name and its value type separate: `AttrValue` declares everything `Attribute` does except the name and its name-specific default, and `Named<X, V>` pairs one with a name the graph supplies. The value type stays concrete in the signature while the name varies per instance, so a computed name has nowhere to come from. `LayoutMeta` gains the name-from-input variant the fold consumes: a `FieldWrite` template with every facet minted from the concrete type and only the name left as a placeholder, plus the input position the name's constant sits at. `fold_name` resolves one, preferring the census row's descriptor where the name is declared so a known name keeps its census default. `RecordLayout` carries the folded names so `set_layout` can resolve offsets without a marker's `NAME`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8266a61c52
commit
4f12de68a7
@@ -155,6 +155,198 @@ impl<A: Attribute> Default for RemoveAttr<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The value-type half of an attribute: everything [`Attribute`] declares
|
||||||
|
/// except the name and its name-specific default. A name-generic write pairs
|
||||||
|
/// one of these with a name taken from the graph, so the value type stays
|
||||||
|
/// concrete in the signature while the name varies per instance.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The obligations are [`Attribute`]'s, at this trait's value type:
|
||||||
|
/// [`REPARK`](Self::REPARK) must be `Some` for every row whose
|
||||||
|
/// [`Value<'e>`](Self::Value) can carry a borrow shorter than `'static`, and
|
||||||
|
/// [`from_stored`](Self::from_stored) and [`read_erased`](Self::read_erased)
|
||||||
|
/// must be each other's inverse.
|
||||||
|
pub unsafe trait AttrValue: 'static {
|
||||||
|
/// The value type every write of this row shares, as [`Attribute::Value`].
|
||||||
|
type Value<'e>: Copy + Default + std::fmt::Debug + Send + Sync + 'e;
|
||||||
|
|
||||||
|
/// Borrows the value out of legacy list storage, as [`Attribute::from_stored`].
|
||||||
|
fn from_stored<'a>(stored: &'a dyn std::any::Any) -> Option<Self::Value<'a>>;
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// `ptr` must point at a live field of this row's value type.
|
||||||
|
unsafe fn read_erased(ptr: *const u8) -> Box<dyn AnyAttributeValue>;
|
||||||
|
|
||||||
|
/// Re-parks an owned clone into fresh field storage, as [`Attribute::REPARK`].
|
||||||
|
const REPARK: Option<crate::list::ReparkFn> = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`Attribute::NAME`] a [`Named`] marker carries before the compiler
|
||||||
|
/// fills it. A layout never holds it: the fold replaces it with the
|
||||||
|
/// instance's constant, and a node whose name input is not constant is
|
||||||
|
/// refused at that same point.
|
||||||
|
pub const NAMED_PLACEHOLDER: &str = "";
|
||||||
|
|
||||||
|
/// A name-generic attribute write. `X` is a placeholder that distinguishes
|
||||||
|
/// name-generic attributes within one signature, so a node writing two of
|
||||||
|
/// them takes two names; `V` fixes the value type. The name is absent by
|
||||||
|
/// construction: it comes from the instance's constant text input, folded
|
||||||
|
/// into the layout at graph compile time, so a computed name cannot exist.
|
||||||
|
pub struct Named<X, V>(PhantomData<fn() -> (X, V)>);
|
||||||
|
|
||||||
|
// SAFETY: every obligation is discharged by `V`, which carries the same
|
||||||
|
// contract at the same value type; only the name differs, and the compiler
|
||||||
|
// fold replaces the placeholder before a layout sees it.
|
||||||
|
unsafe impl<X: 'static, V: AttrValue> Attribute for Named<X, V> {
|
||||||
|
const NAME: &'static str = NAMED_PLACEHOLDER;
|
||||||
|
type Value<'e> = V::Value<'e>;
|
||||||
|
|
||||||
|
fn from_stored<'a>(stored: &'a dyn std::any::Any) -> Option<Self::Value<'a>> {
|
||||||
|
V::from_stored(stored)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_erased(ptr: *const u8) -> Box<dyn AnyAttributeValue> {
|
||||||
|
// SAFETY: the caller's contract, at `V`'s own value type.
|
||||||
|
unsafe { V::read_erased(ptr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const REPARK: Option<crate::list::ReparkFn> = V::REPARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interns a folded attribute name for the `&'static str` a layout field
|
||||||
|
/// holds. Census names never reach here; a document's novel names are finite
|
||||||
|
/// and repeat across instances, so the leak is one allocation per name.
|
||||||
|
pub fn intern_name(name: &str) -> &'static str {
|
||||||
|
static NAMES: LazyLock<Mutex<std::collections::HashSet<&'static str>>> = LazyLock::new(|| Mutex::new(std::collections::HashSet::new()));
|
||||||
|
let mut names = NAMES.lock().unwrap();
|
||||||
|
if let Some(interned) = names.get(name) {
|
||||||
|
return interned;
|
||||||
|
}
|
||||||
|
let interned: &'static str = Box::leak(name.to_owned().into_boxed_str());
|
||||||
|
names.insert(interned);
|
||||||
|
interned
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declares [`AttrValue`] rows, the value types a name-generic attribute can
|
||||||
|
/// be written at. `for T` implements the row on an existing plain value type;
|
||||||
|
/// `Row: &T` and `Row: Option<&T>` declare a token naming a reference value,
|
||||||
|
/// whose payload the writing kernel parks in the arena.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// core_types::named_value! {
|
||||||
|
/// /// Plain rows, implemented on the value type itself.
|
||||||
|
/// for f64;
|
||||||
|
/// /// A reference row, whose token names the borrowed value.
|
||||||
|
/// pub Text: &str;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! named_value {
|
||||||
|
() => {};
|
||||||
|
($(#[$meta:meta])* for $value:ty; $($rest:tt)*) => {
|
||||||
|
// SAFETY: a plain value type cannot name `'e`, so no re-park is owed,
|
||||||
|
// and the two glue fns below are each other's inverse at `$value`.
|
||||||
|
unsafe impl $crate::attribute::AttrValue for $value {
|
||||||
|
type Value<'e> = $value;
|
||||||
|
|
||||||
|
fn from_stored<'a>(stored: &'a dyn ::std::any::Any) -> ::core::option::Option<Self::Value<'a>> {
|
||||||
|
stored.downcast_ref::<$value>().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_erased(ptr: *const u8) -> ::std::boxed::Box<dyn $crate::list::AnyAttributeValue> {
|
||||||
|
::std::boxed::Box::new(unsafe { ptr.cast::<$value>().read() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$crate::named_value!($($rest)*);
|
||||||
|
};
|
||||||
|
($(#[$meta:meta])* $vis:vis $row:ident: Option<&$value:ty>; $($rest:tt)*) => {
|
||||||
|
$(#[$meta])*
|
||||||
|
$vis struct $row;
|
||||||
|
|
||||||
|
// SAFETY: the reference arm emits `REPARK`, and the two glue fns below
|
||||||
|
// are each other's inverse at `Option<&$value>`.
|
||||||
|
unsafe impl $crate::attribute::AttrValue for $row {
|
||||||
|
type Value<'e> = ::core::option::Option<&'e $value>;
|
||||||
|
|
||||||
|
fn from_stored<'a>(stored: &'a dyn ::std::any::Any) -> ::core::option::Option<Self::Value<'a>> {
|
||||||
|
stored
|
||||||
|
.downcast_ref::<::core::option::Option<<$value as ::std::borrow::ToOwned>::Owned>>()
|
||||||
|
.map(|owned| owned.as_ref().map(::std::borrow::Borrow::borrow))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_erased(ptr: *const u8) -> ::std::boxed::Box<dyn $crate::list::AnyAttributeValue> {
|
||||||
|
::std::boxed::Box::new(unsafe { ptr.cast::<::core::option::Option<&$value>>().read() }.map(|value| <$value as ::std::borrow::ToOwned>::to_owned(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const REPARK: ::core::option::Option<$crate::list::ReparkFn> = {
|
||||||
|
unsafe fn repark(value: &dyn $crate::list::AnyAttributeValue, dst: *mut u8, arena: &$crate::arena::Arena) -> ::core::option::Option<()> {
|
||||||
|
let owned: &::core::option::Option<<$value as ::std::borrow::ToOwned>::Owned> =
|
||||||
|
value.as_any().downcast_ref().expect("an optional reference row replays its owned clone");
|
||||||
|
let parked = match owned {
|
||||||
|
::core::option::Option::Some(owned) => {
|
||||||
|
let (parked, _) = arena.alloc(<$value as ::std::borrow::ToOwned>::to_owned(::std::borrow::Borrow::borrow(owned)))?;
|
||||||
|
::core::option::Option::Some(::std::borrow::Borrow::borrow(parked))
|
||||||
|
}
|
||||||
|
::core::option::Option::None => ::core::option::Option::None,
|
||||||
|
};
|
||||||
|
unsafe { dst.cast::<::core::option::Option<&$value>>().write(parked) };
|
||||||
|
::core::option::Option::Some(())
|
||||||
|
}
|
||||||
|
::core::option::Option::Some(repark)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$crate::named_value!($($rest)*);
|
||||||
|
};
|
||||||
|
($(#[$meta:meta])* $vis:vis $row:ident: &$value:ty; $($rest:tt)*) => {
|
||||||
|
$(#[$meta])*
|
||||||
|
$vis struct $row;
|
||||||
|
|
||||||
|
// SAFETY: the reference arm emits `REPARK`, and the two glue fns below
|
||||||
|
// are each other's inverse at `&$value`.
|
||||||
|
unsafe impl $crate::attribute::AttrValue for $row {
|
||||||
|
type Value<'e> = &'e $value;
|
||||||
|
|
||||||
|
fn from_stored<'a>(stored: &'a dyn ::std::any::Any) -> ::core::option::Option<Self::Value<'a>> {
|
||||||
|
stored.downcast_ref::<<$value as ::std::borrow::ToOwned>::Owned>().map(::std::borrow::Borrow::borrow)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn read_erased(ptr: *const u8) -> ::std::boxed::Box<dyn $crate::list::AnyAttributeValue> {
|
||||||
|
::std::boxed::Box::new(unsafe { ptr.cast::<&$value>().read() }.to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
const REPARK: ::core::option::Option<$crate::list::ReparkFn> = {
|
||||||
|
unsafe fn repark(value: &dyn $crate::list::AnyAttributeValue, dst: *mut u8, arena: &$crate::arena::Arena) -> ::core::option::Option<()> {
|
||||||
|
let owned: &<$value as ::std::borrow::ToOwned>::Owned = value.as_any().downcast_ref().expect("a reference row replays its owned clone");
|
||||||
|
let (parked, _) = arena.alloc(<$value as ::std::borrow::ToOwned>::to_owned(::std::borrow::Borrow::borrow(owned)))?;
|
||||||
|
unsafe { dst.cast::<&$value>().write(::std::borrow::Borrow::borrow(parked)) };
|
||||||
|
::core::option::Option::Some(())
|
||||||
|
}
|
||||||
|
::core::option::Option::Some(repark)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$crate::named_value!($($rest)*);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
named_value! {
|
||||||
|
for f64;
|
||||||
|
for u32;
|
||||||
|
for u64;
|
||||||
|
for bool;
|
||||||
|
for DVec2;
|
||||||
|
for DAffine2;
|
||||||
|
for crate::Color;
|
||||||
|
for crate::blending::BlendMode;
|
||||||
|
/// A document node path, the value type of `editor:layer_path`.
|
||||||
|
pub NodeIdPath: &[crate::uuid::NodeId];
|
||||||
|
/// Free text, parked in the arena by the writing kernel.
|
||||||
|
pub Text: &str;
|
||||||
|
}
|
||||||
|
|
||||||
/// A census row: what is known about one declared attribute name.
|
/// A census row: what is known about one declared attribute name.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct AttributeInfo {
|
pub struct AttributeInfo {
|
||||||
|
|||||||
@@ -366,6 +366,37 @@ pub struct RecordLayout {
|
|||||||
/// over input positions. Empty is the safe default: an uninstalled layout
|
/// over input positions. Empty is the safe default: an uninstalled layout
|
||||||
/// rebinds every input per lane.
|
/// rebinds every input per lane.
|
||||||
pub lane_invariant: u32,
|
pub lane_invariant: u32,
|
||||||
|
/// The names the fold gave this node's name-from-input writes, in the
|
||||||
|
/// order the signature declares its placeholders. A marker-only node
|
||||||
|
/// leaves it empty; `set_layout` resolves its offsets through these
|
||||||
|
/// instead of through a marker's `NAME`.
|
||||||
|
pub named_writes: Vec<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A write whose name comes from the graph rather than from a marker: the
|
||||||
|
/// input carrying the name, and the field descriptor with every facet but the
|
||||||
|
/// name already minted from the concrete value type.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct NamedWrite {
|
||||||
|
/// The proto input position holding the name's constant text.
|
||||||
|
pub name_input: u8,
|
||||||
|
/// The write this becomes once the fold supplies the name.
|
||||||
|
pub template: FieldWrite,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NamedWrite {
|
||||||
|
/// The template for a name-generic marker's write at `level`. Every facet
|
||||||
|
/// but the name is the concrete value type's, exactly as for a census
|
||||||
|
/// marker; [`FieldWrite::name`] holds the placeholder until the fold runs.
|
||||||
|
pub fn of<X: 'static, V: crate::attribute::AttrValue>(name_input: u8, level: u8) -> Self
|
||||||
|
where
|
||||||
|
V::Value<'static>: graphene_hash::CacheHash + PartialEq + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
name_input,
|
||||||
|
template: FieldWrite::of::<crate::attribute::Named<X, V>>(level),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Declarative record-io metadata for a node type, emitted by the macro into
|
/// Declarative record-io metadata for a node type, emitted by the macro into
|
||||||
@@ -387,6 +418,14 @@ pub struct LayoutMeta {
|
|||||||
pub element: ElementSpec,
|
pub element: ElementSpec,
|
||||||
/// The attributes the node writes at its acting level.
|
/// The attributes the node writes at its acting level.
|
||||||
pub writes: Vec<FieldWrite>,
|
pub writes: Vec<FieldWrite>,
|
||||||
|
/// The attributes the node writes under a name taken from the graph. The
|
||||||
|
/// compiler fold turns each into a [`writes`](Self::writes) entry and
|
||||||
|
/// empties this, so a folded meta is indistinguishable from a marker
|
||||||
|
/// node's; one still carrying an entry has not been folded.
|
||||||
|
pub named_writes: Vec<NamedWrite>,
|
||||||
|
/// 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 removed from the base layout, as `(name, level)`.
|
/// The attributes removed from the base layout, as `(name, level)`.
|
||||||
pub removes: Vec<(&'static str, u8)>,
|
pub removes: Vec<(&'static str, u8)>,
|
||||||
/// The depth change the node applies: `0` for elementwise and flip nodes,
|
/// The depth change the node applies: `0` for elementwise and flip nodes,
|
||||||
@@ -423,6 +462,8 @@ impl LayoutMeta {
|
|||||||
reads: Vec::new(),
|
reads: Vec::new(),
|
||||||
element: ElementSpec::Concrete(element),
|
element: ElementSpec::Concrete(element),
|
||||||
writes: Vec::new(),
|
writes: Vec::new(),
|
||||||
|
named_writes: Vec::new(),
|
||||||
|
folded_names: Vec::new(),
|
||||||
removes: Vec::new(),
|
removes: Vec::new(),
|
||||||
level_delta: 0,
|
level_delta: 0,
|
||||||
folded: None,
|
folded: None,
|
||||||
@@ -472,8 +513,24 @@ impl LayoutMeta {
|
|||||||
frame_bytes,
|
frame_bytes,
|
||||||
plan,
|
plan,
|
||||||
lane_invariant: 0,
|
lane_invariant: 0,
|
||||||
|
named_writes: self.folded_names.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Folds `name` into the name-from-input write at `index`, taking the
|
||||||
|
/// census row's descriptor where the name is declared so a known name
|
||||||
|
/// keeps its census default, and the template's otherwise. The write
|
||||||
|
/// joins [`writes`](Self::writes) in placeholder order.
|
||||||
|
pub fn fold_name(&mut self, index: usize, name: &'static str) {
|
||||||
|
let named = self.named_writes[index];
|
||||||
|
let census = attribute::info(name).map(|row| (row.field_write_at)(named.template.level));
|
||||||
|
let write = match census {
|
||||||
|
Some(write) if write.type_id == named.template.type_id => write,
|
||||||
|
_ => FieldWrite { name, ..named.template },
|
||||||
|
};
|
||||||
|
self.writes.push(write);
|
||||||
|
self.folded_names.push(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Field-by-field carry from `from`'s layout into `to`'s, computed at
|
/// Field-by-field carry from `from`'s layout into `to`'s, computed at
|
||||||
|
|||||||
Reference in New Issue
Block a user