mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 04:18:11 +08:00
Add a generic lazy repeat that evaluates its content at each copy index
This commit is contained in:
@@ -438,6 +438,7 @@ impl<'e> RecordValue<'e> {
|
|||||||
/// unconstrained ones.
|
/// unconstrained ones.
|
||||||
pub trait DerivedRecordEdge<'derived, C> {
|
pub trait DerivedRecordEdge<'derived, C> {
|
||||||
fn eval_derived(&self, cell: &crate::node::StatusCell, input_index: usize, ctx: &C) -> Result<RecordValue<'derived>, crate::gpoll::Interrupt>;
|
fn eval_derived(&self, cell: &crate::node::StatusCell, input_index: usize, ctx: &C) -> Result<RecordValue<'derived>, crate::gpoll::Interrupt>;
|
||||||
|
fn extent_at_derived(&self, ctx: &C, level: u8) -> GPoll<crate::gpoll::Extent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'derived, C, N> DerivedRecordEdge<'derived, C> for N
|
impl<'derived, C, N> DerivedRecordEdge<'derived, C> for N
|
||||||
@@ -447,6 +448,10 @@ where
|
|||||||
fn eval_derived(&self, cell: &crate::node::StatusCell, input_index: usize, ctx: &C) -> Result<RecordValue<'derived>, crate::gpoll::Interrupt> {
|
fn eval_derived(&self, cell: &crate::node::StatusCell, input_index: usize, ctx: &C) -> Result<RecordValue<'derived>, crate::gpoll::Interrupt> {
|
||||||
cell.eval_input(input_index, self, ctx)
|
cell.eval_input(input_index, self, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extent_at_derived(&self, ctx: &C, level: u8) -> GPoll<crate::gpoll::Extent> {
|
||||||
|
self.extent_at(ctx, level)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A record edge at a caller-chosen lifetime; the lifetime is a trait
|
/// A record edge at a caller-chosen lifetime; the lifetime is a trait
|
||||||
|
|||||||
@@ -1712,6 +1712,10 @@ pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
|
|||||||
#plan
|
#plan
|
||||||
self.__layout = __resolved.layout;
|
self.__layout = __resolved.layout;
|
||||||
})
|
})
|
||||||
|
} else if routing_generic.is_some() {
|
||||||
|
Some(quote! {
|
||||||
|
self.__layout = __resolved.layout;
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
//! wiring is by hand until the compiler pass constructs layouts.
|
//! wiring is by hand until the compiler pass constructs layouts.
|
||||||
|
|
||||||
use core_types::attribute::{Attr, Opacity, RemoveAttr};
|
use core_types::attribute::{Attr, Opacity, RemoveAttr};
|
||||||
use core_types::context::{ExtractArena, ExtractIndex, InjectIndex};
|
use core_types::context::{DeriveCtx, ExtractArena, ExtractIndex, InjectIndex};
|
||||||
use core_types::gpoll::{ErrorKind, GraphError, Interrupt};
|
use core_types::gpoll::{ErrorKind, GraphError, Interrupt};
|
||||||
use core_types::{Context, Ctx};
|
use core_types::{Context, Ctx};
|
||||||
|
|
||||||
@@ -76,6 +76,35 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test-only generic structure creator: evaluates the lazy content once per copy
|
||||||
|
/// with the copy's index pushed in, producing a rank level of `count` copies.
|
||||||
|
#[node_macro::node(category("Test"), extent(repeat_extent))]
|
||||||
|
fn repeat<T>(ctx: impl Ctx + DeriveCtx + ExtractIndex, content: impl Node<Context<'_>, Output = T>, count: u32) -> Result<IList<T>, Interrupt> {
|
||||||
|
let spilled = ctx.index_head();
|
||||||
|
let copy = ctx.innermost_index() % count as u64;
|
||||||
|
content.eval(&ctx.promoted(&spilled, copy))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pushed level's extent is the copy count; inner levels forward to the
|
||||||
|
/// content, whose extent is taken uniform across copies (queried at copy 0).
|
||||||
|
fn repeat_extent<'r, C, In0, In1>(node: &RepeatNode<In0, In1>, ctx: &C, level: u8) -> core_types::gpoll::GPoll<core_types::gpoll::Extent>
|
||||||
|
where
|
||||||
|
C: core_types::context::DeriveCtx + core_types::context::ExtractIndex,
|
||||||
|
In0: for<'d> core_types::record::DerivedRecordEdge<'d, core_types::context::Derived<'d, C>>,
|
||||||
|
In1: core_types::node::Node<C, Output = core_types::record::RecordValue<'r>>,
|
||||||
|
{
|
||||||
|
use core_types::node::Node;
|
||||||
|
if level + 1 == node.__layout.depth {
|
||||||
|
node.count.eval(ctx).map(|value| {
|
||||||
|
let count: u32 = unsafe { core_types::record::read_element(node.__in_1.rec(&value)) };
|
||||||
|
core_types::gpoll::Extent::Exactly(count as usize)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let spilled = ctx.index_head();
|
||||||
|
node.content.extent_at_derived(&ctx.promoted(&spilled, 0), level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Test"))]
|
#[node_macro::node(category("Test"))]
|
||||||
fn source_opacity(_: impl Ctx, _: (), element: f64, opacity: f64) -> (f64, Attr<Opacity>) {
|
fn source_opacity(_: impl Ctx, _: (), element: f64, opacity: f64) -> (f64, Attr<Opacity>) {
|
||||||
(element, Attr(opacity))
|
(element, Attr(opacity))
|
||||||
@@ -208,6 +237,29 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct IndexSourceNode {
|
||||||
|
layout: Layout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'e> Node<ContextImpl<'e>> for IndexSourceNode {
|
||||||
|
type Output = RecordValue<'e>;
|
||||||
|
|
||||||
|
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||||
|
let element = input.innermost_index() as f64;
|
||||||
|
let mut value = RecordValue::zeroed();
|
||||||
|
let dst = match self.layout.frame_bytes() {
|
||||||
|
0 => value.as_mut_ptr(),
|
||||||
|
bytes => stack::push(bytes),
|
||||||
|
};
|
||||||
|
unsafe { dst.cast::<f64>().write(element) };
|
||||||
|
if self.layout.frame_bytes() != 0 {
|
||||||
|
stack::pop(dst);
|
||||||
|
value = RecordValue::spilled(unsafe { Rec::new(dst.cast_const()) });
|
||||||
|
}
|
||||||
|
GPoll::Final(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||||
stack::reserve(1 << 16);
|
stack::reserve(1 << 16);
|
||||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||||
@@ -308,6 +360,80 @@ mod tests {
|
|||||||
assert_eq!(node.extent_at(&ctx, 0), core_types::gpoll::GPoll::Final(core_types::gpoll::Extent::Exactly(3)));
|
assert_eq!(node.extent_at(&ctx, 0), core_types::gpoll::GPoll::Final(core_types::gpoll::Extent::Exactly(3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generic_repeat_pushes_a_level_and_forwards_the_element() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let base = f64_layout(&[]);
|
||||||
|
let (count_edge, count_layout) = lifted_value(3u32);
|
||||||
|
reserve_for(&[&base, &count_layout]);
|
||||||
|
|
||||||
|
let meta = core_types::record::LayoutMeta {
|
||||||
|
sources: vec![0],
|
||||||
|
reads: vec![],
|
||||||
|
element: core_types::record::ElementSpec::Carried,
|
||||||
|
writes: vec![],
|
||||||
|
removes: vec![],
|
||||||
|
level_delta: 1,
|
||||||
|
};
|
||||||
|
let node = install(
|
||||||
|
RepeatNode::new(RecordSource::new(bare_source(&base, 7.), &base, &base), count_edge, &base, &count_layout),
|
||||||
|
meta,
|
||||||
|
&[Some(&base)],
|
||||||
|
);
|
||||||
|
let leveled = Node::<ContextImpl>::layout(&node).clone();
|
||||||
|
assert_eq!(leveled.depth, 1, "the IList return pushed one rank level above the depth-0 content");
|
||||||
|
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(core_types::gpoll::Extent::Exactly(3)));
|
||||||
|
|
||||||
|
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
assert_eq!(unsafe { leveled.rec(&value).element::<f64>() }, 7., "the opaque generic element forwarded unchanged");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repeat_evaluates_content_at_each_copy_index() {
|
||||||
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
let generations = [];
|
||||||
|
let scope = scope_fixture(&generations, &arena);
|
||||||
|
let ctx = ContextImpl::root(&scope);
|
||||||
|
|
||||||
|
let base = f64_layout(&[]);
|
||||||
|
let (count_edge, count_layout) = lifted_value(4u32);
|
||||||
|
reserve_for(&[&base, &count_layout]);
|
||||||
|
|
||||||
|
let meta = core_types::record::LayoutMeta {
|
||||||
|
sources: vec![0],
|
||||||
|
reads: vec![],
|
||||||
|
element: core_types::record::ElementSpec::Carried,
|
||||||
|
writes: vec![],
|
||||||
|
removes: vec![],
|
||||||
|
level_delta: 1,
|
||||||
|
};
|
||||||
|
let repeat = install(
|
||||||
|
RepeatNode::new(RecordSource::new(IndexSourceNode { layout: base.clone() }, &base, &base), count_edge, &base, &count_layout),
|
||||||
|
meta,
|
||||||
|
&[Some(&base)],
|
||||||
|
);
|
||||||
|
let leveled = Node::<ContextImpl>::layout(&repeat).clone();
|
||||||
|
|
||||||
|
let head = ctx.index_head();
|
||||||
|
for copy in 0..4 {
|
||||||
|
let mark = stack::sp();
|
||||||
|
let lane = ctx.promoted(&head, copy);
|
||||||
|
let GPoll::Final(value) = repeat.eval(&lane) else {
|
||||||
|
panic!("expected a final record");
|
||||||
|
};
|
||||||
|
// The copy evaluated its content at its own pushed index.
|
||||||
|
assert_eq!(unsafe { leveled.rec(&value).element::<f64>() }, copy as f64);
|
||||||
|
// SAFETY: the copy's element was read out above, so no borrow into its frame remains.
|
||||||
|
unsafe { stack::rewind(mark) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reducer_folds_a_repeated_level() {
|
fn reducer_folds_a_repeated_level() {
|
||||||
let arena = Arena::new(1024).unwrap();
|
let arena = Arena::new(1024).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user