mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 07:08:11 +08:00
Add the leveled value source and the dormant leveled edge form
This commit is contained in:
@@ -587,6 +587,38 @@ tagged_value! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TaggedValue {
|
impl TaggedValue {
|
||||||
|
/// The flip form of [`Self::to_edge`]: list-carrying variants serve their
|
||||||
|
/// payload as a level, one lane per item, instead of materializing a
|
||||||
|
/// legacy list value. Every other variant takes the [`Self::to_edge`]
|
||||||
|
/// path unchanged. Dormant until the flip retypes the list-typed inputs.
|
||||||
|
pub fn to_leveled_edge(self) -> Result<EdgeHandle, String> {
|
||||||
|
use core_types::value::leveled_record_value_edge;
|
||||||
|
match self {
|
||||||
|
Self::TypeDefault(td) => {
|
||||||
|
let name = td.name.as_ref();
|
||||||
|
macro_rules! check_level {
|
||||||
|
($list:ty, $element:ty) => {
|
||||||
|
if name == std::any::type_name::<$list>() {
|
||||||
|
return Ok(leveled_record_value_edge(Vec::<$element>::new()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
check_level!(List<Graphic>, Graphic);
|
||||||
|
check_level!(List<Artboard>, Artboard);
|
||||||
|
check_level!(List<Raster<CPU>>, Raster<CPU>);
|
||||||
|
check_level!(List<Vector>, Vector);
|
||||||
|
check_level!(List<String>, String);
|
||||||
|
Self::TypeDefault(td).to_edge()
|
||||||
|
}
|
||||||
|
Self::F64Array(values) => Ok(leveled_record_value_edge(values)),
|
||||||
|
Self::Color(color) => Ok(leveled_record_value_edge(color.into_iter().collect::<Vec<_>>())),
|
||||||
|
Self::Gradient(stops) => Ok(leveled_record_value_edge(vec![stops])),
|
||||||
|
Self::BrushStrokes(strokes) => Ok(leveled_record_value_edge(strokes)),
|
||||||
|
Self::NodeIdPath(path) => Ok(leveled_record_value_edge(path)),
|
||||||
|
other => other.to_edge(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_primitive_string(&self) -> String {
|
pub fn to_primitive_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
TaggedValue::None => "()".to_string(),
|
TaggedValue::None => "()".to_string(),
|
||||||
@@ -910,3 +942,32 @@ mod typedefault_dispatch {
|
|||||||
for_each_type_default!(check);
|
for_each_type_default!(check);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod leveled_edges {
|
||||||
|
use super::*;
|
||||||
|
use core_types::descriptor;
|
||||||
|
use core_types::registry::record_edge_type;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_variants_produce_leveled_edges_typed_by_element() {
|
||||||
|
let edge = TaggedValue::F64Array(vec![1., 2., 3.]).to_leveled_edge().unwrap();
|
||||||
|
assert_eq!(edge.ty(), &record_edge_type::<f64>());
|
||||||
|
assert_eq!(edge.layout().depth, 1);
|
||||||
|
|
||||||
|
let edge = TaggedValue::Color(Some(Color::default())).to_leveled_edge().unwrap();
|
||||||
|
assert_eq!(edge.ty(), &record_edge_type::<Color>());
|
||||||
|
assert_eq!(edge.layout().depth, 1);
|
||||||
|
|
||||||
|
let edge = TaggedValue::TypeDefault(descriptor!(List<Graphic>)).to_leveled_edge().unwrap();
|
||||||
|
assert_eq!(edge.ty(), &record_edge_type::<Graphic>());
|
||||||
|
assert_eq!(edge.layout().depth, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scalar_variants_keep_their_rank_zero_edges() {
|
||||||
|
let edge = TaggedValue::Bool(true).to_leveled_edge().unwrap();
|
||||||
|
assert_eq!(edge.ty(), &record_edge_type::<bool>());
|
||||||
|
assert_eq!(edge.layout().depth, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,53 @@ pub fn record_value_edge<T: Clone + Send + Sync + 'static>(value: T) -> crate::r
|
|||||||
crate::registry::EdgeHandle::new_record::<T>(std::sync::Arc::new(ValueSource::new(value)) as std::sync::Arc<crate::registry::ErasedRecordNode>)
|
crate::registry::EdgeHandle::new_record::<T>(std::sync::Arc::new(ValueSource::new(value)) as std::sync::Arc<crate::registry::ErasedRecordNode>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The node behind a leveled value edge: a constant list served as one level,
|
||||||
|
/// one lane per item, with the list's length as the exact extent.
|
||||||
|
pub struct LeveledValueSource<T> {
|
||||||
|
values: Vec<T>,
|
||||||
|
layout: crate::record::Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Send + Sync + 'static> LeveledValueSource<T> {
|
||||||
|
pub fn new(values: Vec<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
values,
|
||||||
|
layout: crate::record::Layout::default().with_writes(1, crate::record::element_write::<T>(), &[]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e, C, T> crate::node::Node<C> for LeveledValueSource<T>
|
||||||
|
where
|
||||||
|
C: crate::context::ExtractArena<ArenaRef = &'e crate::arena::Arena> + crate::context::ExtractIndex,
|
||||||
|
T: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
type Output = crate::record::RecordValue<'e>;
|
||||||
|
|
||||||
|
fn eval(&self, input: &C) -> crate::gpoll::GPoll<crate::record::RecordValue<'e>> {
|
||||||
|
let Some(value) = self.values.get(input.innermost_index() as usize) else {
|
||||||
|
return crate::gpoll::GPoll::error("value level addressed past its items");
|
||||||
|
};
|
||||||
|
crate::record::lift_poll(crate::gpoll::GPoll::Final(value.clone()), &self.layout, input.arena())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extent_at(&self, _input: &C, level: u8) -> crate::gpoll::GPoll<crate::gpoll::Extent> {
|
||||||
|
match level {
|
||||||
|
0 => crate::gpoll::GPoll::Final(crate::gpoll::Extent::Exactly(self.values.len())),
|
||||||
|
_ => crate::gpoll::GPoll::Final(crate::gpoll::Extent::Exactly(1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self) -> &crate::record::Layout {
|
||||||
|
&self.layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The native record edge of a constant level: the edge type is the element's.
|
||||||
|
pub fn leveled_record_value_edge<T: Clone + Send + Sync + 'static>(values: Vec<T>) -> crate::registry::EdgeHandle {
|
||||||
|
crate::registry::EdgeHandle::new_record::<T>(std::sync::Arc::new(LeveledValueSource::new(values)) as std::sync::Arc<crate::registry::ErasedRecordNode>)
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone> ClonedNode<T> {
|
impl<T: Clone> ClonedNode<T> {
|
||||||
pub const fn new(value: T) -> ClonedNode<T> {
|
pub const fn new(value: T) -> ClonedNode<T> {
|
||||||
ClonedNode(value)
|
ClonedNode(value)
|
||||||
|
|||||||
@@ -1661,6 +1661,25 @@ mod tests {
|
|||||||
assert_eq!(unsafe { out.rec(&value).element::<f64>() }, 20.);
|
assert_eq!(unsafe { out.rec(&value).element::<f64>() }, 20.);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_leveled_value_source_serves_its_items_as_lanes() {
|
||||||
|
let arena = Arena::new(1 << 16).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let source = core_types::value::LeveledValueSource::new(vec![1.5, 2.25, 3.75]);
|
||||||
|
let leveled = Node::<ContextImpl>::layout(&source).clone();
|
||||||
|
let out = f64_layout(&[]);
|
||||||
|
reserve_for(&[&leveled, &out]);
|
||||||
|
|
||||||
|
let node = install_flip(SumNode::new(source, &leveled), &out);
|
||||||
|
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
assert_eq!(unsafe { out.rec(&value).element::<f64>() }, 7.5);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reducer_drains_a_lower_bound_level() {
|
fn reducer_drains_a_lower_bound_level() {
|
||||||
let arena = Arena::new(1 << 16).unwrap();
|
let arena = Arena::new(1 << 16).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user