mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Mint attribute reads through a layout-checked field token
This commit is contained in:
@@ -26,7 +26,9 @@ pub trait Attribute: 'static {
|
||||
const NAME: &'static str;
|
||||
/// The value type every read and write of this name shares. The lifetime
|
||||
/// is the evaluation the value flows in; non-reference values ignore it.
|
||||
type Value<'e>: Copy + Default + std::fmt::Debug;
|
||||
/// The value outlives that evaluation, so its `'static` instantiation is
|
||||
/// the one the census registers and layouts stamp their type id from.
|
||||
type Value<'e>: Copy + Default + std::fmt::Debug + 'e;
|
||||
/// The name-specific default, filled where an item lacks the attribute.
|
||||
/// Producing a value for any `'e` from no inputs, reference defaults can
|
||||
/// only point at `'static` data, which is what lets the census fill them
|
||||
|
||||
@@ -174,13 +174,27 @@ impl<'a> RecordLane<'a> {
|
||||
unsafe { self.rec.element::<U>() }
|
||||
}
|
||||
|
||||
/// Attribute `A` through a token, `None` where the token was minted against
|
||||
/// another layout than this lane's.
|
||||
pub fn try_attr_at<A: crate::attribute::Attribute>(&self, field: crate::record::FieldOffset<A>) -> Option<A::Value<'a>> {
|
||||
let offset = field.resolve(self.layout)?;
|
||||
// SAFETY: resolving against this lane's own layout pins the offset and
|
||||
// the field's value type, and the batch's contract makes the lane a live
|
||||
// record of that layout.
|
||||
Some(unsafe { self.rec.read::<A::Value<'a>>(offset) })
|
||||
}
|
||||
|
||||
/// Attribute `A` through a token, or its census default where the token is
|
||||
/// absent or names another layout.
|
||||
pub fn attr_at<A: crate::attribute::Attribute>(&self, field: Option<crate::record::FieldOffset<A>>) -> A::Value<'a> {
|
||||
field.and_then(|field| self.try_attr_at(field)).unwrap_or_else(A::default)
|
||||
}
|
||||
|
||||
/// Attribute `A` at the record's top level, or its census default when the
|
||||
/// layout does not carry it.
|
||||
/// layout does not carry it. Mints a token per call; lane loops hoist the
|
||||
/// mint instead.
|
||||
pub fn attr<A: crate::attribute::Attribute>(&self) -> A::Value<'a> {
|
||||
match self.layout.offset_of(A::NAME, 0) {
|
||||
Some(offset) => unsafe { self.rec.read::<A::Value<'a>>(offset) },
|
||||
None => A::default(),
|
||||
}
|
||||
self.attr_at(crate::record::FieldOffset::<A>::of(self.layout, 0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -263,6 +263,61 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
/// A field handle minted once against a layout: the marker's (name, level)
|
||||
/// resolved to a field index and offset, with the marker-to-type proof taken
|
||||
/// there. Resolving it against a layout re-checks that one index instead of
|
||||
/// scanning names, so a token paired with any other layout resolves to
|
||||
/// nothing rather than to the wrong bytes.
|
||||
pub struct FieldOffset<A: attribute::Attribute> {
|
||||
index: usize,
|
||||
offset: usize,
|
||||
level: u8,
|
||||
marker: std::marker::PhantomData<fn() -> A>,
|
||||
}
|
||||
|
||||
impl<A: attribute::Attribute> Clone for FieldOffset<A> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: attribute::Attribute> Copy for FieldOffset<A> {}
|
||||
|
||||
impl<A: attribute::Attribute> std::fmt::Debug for FieldOffset<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FieldOffset").field("name", &A::NAME).field("offset", &self.offset).field("level", &self.level).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: attribute::Attribute> FieldOffset<A> {
|
||||
/// The marker's field at `level`, `None` where the layout does not carry
|
||||
/// it. Panics where the layout declares the name at another value type,
|
||||
/// which the census forbids.
|
||||
pub fn of(layout: &Layout, level: u8) -> Option<Self> {
|
||||
let (index, field) = layout.fields.iter().enumerate().find(|(_, field)| field.name == A::NAME && field.level == level)?;
|
||||
assert_eq!(field.type_id, std::any::TypeId::of::<A::Value<'static>>(), "attribute `{}` is declared at another value type", A::NAME);
|
||||
Some(Self {
|
||||
index,
|
||||
offset: field.offset,
|
||||
level,
|
||||
marker: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn offset(self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
|
||||
/// The offset this token names in `layout`, `None` unless `layout` is the
|
||||
/// one it was minted against. The field's value type is re-checked, so a
|
||||
/// resolved offset carries the marker-to-type proof into `layout`.
|
||||
pub fn resolve(self, layout: &Layout) -> Option<usize> {
|
||||
let field = layout.fields.get(self.index)?;
|
||||
let same = field.offset == self.offset && field.level == self.level && field.name == A::NAME && field.type_id == std::any::TypeId::of::<A::Value<'static>>();
|
||||
same.then_some(self.offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// The stand-in fed to a kernel's unbounded `element: T` parameter. The type
|
||||
/// system forces the kernel to route it to the element position of its return
|
||||
/// tuple, so the passthrough is explicit in the signature while the lowering
|
||||
@@ -2411,8 +2466,7 @@ impl<'a, T: dyn_any::StaticTypeSized> RunView<'a, T> {
|
||||
/// A marker's field on a run, its offset resolved once.
|
||||
pub struct RunColumn<'a, A: crate::attribute::Attribute> {
|
||||
item: &'a GroupItem<'a>,
|
||||
offset: Option<usize>,
|
||||
marker: std::marker::PhantomData<A>,
|
||||
field: Option<FieldOffset<A>>,
|
||||
}
|
||||
|
||||
impl<'a, A: crate::attribute::Attribute> RunColumn<'a, A> {
|
||||
@@ -2420,17 +2474,14 @@ impl<'a, A: crate::attribute::Attribute> RunColumn<'a, A> {
|
||||
pub fn of(item: &'a GroupItem<'a>) -> Self {
|
||||
Self {
|
||||
item,
|
||||
offset: item.layout().offset_of(A::NAME, 0),
|
||||
marker: std::marker::PhantomData,
|
||||
field: FieldOffset::of(item.layout(), 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: crate::attribute::Attribute> crate::lane::LaneColumn<'a, A> for RunColumn<'a, A> {
|
||||
fn try_get(&self, lane: usize) -> Option<A::Value<'a>> {
|
||||
// SAFETY: the offset comes from the item's own layout, whose field at
|
||||
// this name holds this marker's value type by census registration.
|
||||
self.offset.map(|offset| unsafe { self.item.lanes().get(lane).rec().ptr().add(offset).cast::<A::Value<'a>>().read() })
|
||||
self.item.lanes().get(lane).try_attr_at(self.field?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2680,6 +2731,19 @@ mod tests {
|
||||
layout.with_writes(0, element_write::<f64>(), &[conflicting]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_token_resolves_only_in_the_layout_it_was_minted_against() {
|
||||
use crate::attribute::{Attribute, Opacity, Transform};
|
||||
|
||||
let layout = Layout::default().with_writes(0, element_write::<f64>(), &[FieldWrite::of::<Opacity>(0)]);
|
||||
let token = FieldOffset::<Opacity>::of(&layout, 0).expect("the layout carries the marker");
|
||||
assert_eq!(token.resolve(&layout), layout.offset_of(Opacity::NAME, 0));
|
||||
|
||||
let shifted = Layout::default().with_writes(0, element_write::<f64>(), &[FieldWrite::of::<Transform>(0), FieldWrite::of::<Opacity>(0)]);
|
||||
assert_eq!(FieldOffset::<Opacity>::of(&shifted, 0).and_then(|token| token.resolve(&layout)), None);
|
||||
assert!(FieldOffset::<Opacity>::of(&Layout::default(), 0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_is_order_independent() {
|
||||
let a = Layout::default().with_writes(0, element_write::<f64>(), &[f64_field("opacity")]);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::markers::{ATTR_FILL, ATTR_STROKE, Fill, Stroke};
|
||||
use core_types::attribute::{Attribute, EditorLayerPath, Opacity, OpacityFill, Transform};
|
||||
use core_types::attribute::{Attribute, ClippingMask, EditorLayerPath, Opacity, OpacityFill, Transform};
|
||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::lane::{LaneColumn, LaneSource};
|
||||
use core_types::list::{AttributeValueDyn, Item, ItemAttributeValues, List};
|
||||
use core_types::ops::{FromAnchorPosition, ListConvert};
|
||||
use core_types::record::FieldOffset;
|
||||
use core_types::render_complexity::RenderComplexity;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_TRANSFORM, Color};
|
||||
@@ -654,29 +655,28 @@ impl<'e> Graphic<'e> {
|
||||
}
|
||||
}
|
||||
|
||||
/// One run's attribute offsets, resolved once so the lane loops read raw.
|
||||
/// One run's attribute tokens, minted once so the lane loops read at an offset.
|
||||
struct RunAttrs {
|
||||
transform: Option<usize>,
|
||||
opacity: Option<usize>,
|
||||
opacity_fill: Option<usize>,
|
||||
clipping_mask: Option<usize>,
|
||||
transform: Option<FieldOffset<Transform>>,
|
||||
opacity: Option<FieldOffset<Opacity>>,
|
||||
opacity_fill: Option<FieldOffset<OpacityFill>>,
|
||||
clipping_mask: Option<FieldOffset<ClippingMask>>,
|
||||
}
|
||||
|
||||
impl RunAttrs {
|
||||
fn of(item: &core_types::record::GroupItem) -> Self {
|
||||
let layout = item.layout();
|
||||
Self {
|
||||
transform: layout.offset_of(ATTR_TRANSFORM, 0),
|
||||
opacity: layout.offset_of(ATTR_OPACITY, 0),
|
||||
opacity_fill: layout.offset_of(ATTR_OPACITY_FILL, 0),
|
||||
clipping_mask: layout.offset_of(ATTR_CLIPPING_MASK, 0),
|
||||
transform: FieldOffset::of(layout, 0),
|
||||
opacity: FieldOffset::of(layout, 0),
|
||||
opacity_fill: FieldOffset::of(layout, 0),
|
||||
clipping_mask: FieldOffset::of(layout, 0),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_or<T: Copy>(item: &core_types::record::GroupItem, offset: Option<usize>, lane: usize, default: T) -> T {
|
||||
match offset {
|
||||
// SAFETY: the offset comes from the item's own layout.
|
||||
Some(offset) => unsafe { item.lanes().get(lane).rec().read(offset) },
|
||||
fn read_or<'i, A: Attribute>(item: &'i core_types::record::GroupItem, field: Option<FieldOffset<A>>, lane: usize, default: A::Value<'i>) -> A::Value<'i> {
|
||||
match field.and_then(|field| item.lanes().get(lane).try_attr_at(field)) {
|
||||
Some(value) => value,
|
||||
None => default,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user