mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Flip the Node trait from eval onto the frame claim's serve
This commit is contained in:
@@ -3,7 +3,7 @@ use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll, ModifyIndex};
|
||||
use core_types::frame_table::{FrameTable, Lookup};
|
||||
use core_types::gpoll::{Finality, GPoll};
|
||||
use core_types::graphene_hash::CacheHash;
|
||||
use core_types::record::{LevelStatus, OwnedRecord, RecordValue, claim_frame, copy_record_bytes, serve_frame};
|
||||
use core_types::record::{FrameClaim, LevelStatus, OwnedRecord, Served, copy_record_bytes};
|
||||
use core_types::registry::cache_key;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
@@ -29,12 +29,12 @@ pub struct MemoLevel {
|
||||
/// key normalizes the addressed lane away, so per-lane pulls share one
|
||||
/// materialization of the content instead of re-evaluating it per lane.
|
||||
#[node_macro::node(category("General"), path(graphene_core::memo))]
|
||||
fn memoize<'e>(
|
||||
fn memoize<'e, 'l>(
|
||||
ctx: impl Ctx + CacheHash + DeriveCtx + ExtractArena<'e> + ModifyIndex + Copy,
|
||||
#[data] cache: Arc<Mutex<Option<MemoLevel>>>,
|
||||
content: impl Node<Context<'_>, Output = RecordValue<'e>>,
|
||||
) -> GPoll<RecordValue<'e>> {
|
||||
let entry_sp = core_types::record::stack::sp();
|
||||
content: impl Node<Context<'_>>,
|
||||
slot: FrameClaim<'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
// A scalar wire's value may depend on the consuming lane (index readers),
|
||||
// so only a leveled wire, whose level covers every lane by construction,
|
||||
// keys with the lane normalized away.
|
||||
@@ -51,32 +51,34 @@ fn memoize<'e>(
|
||||
}
|
||||
false => cache_key(&ctx),
|
||||
};
|
||||
let finalized = |value: RecordValue<'e>, finality: &Finality| match finality {
|
||||
let finalized = |value: Served<'e>, finality: &Finality| match finality {
|
||||
Finality::AllFinal => GPoll::Final(value),
|
||||
Finality::Partial => GPoll::Partial(value),
|
||||
};
|
||||
let serve = |entry: &MemoLevel| {
|
||||
// The claim is this node's output frame: a hit fills it from the cached
|
||||
// bytes, and every valueless exit drops it with the frame still claimed.
|
||||
let serve = |entry: &MemoLevel, mut slot: FrameClaim<'l>| {
|
||||
if lane >= entry.lanes.len() {
|
||||
// The cached level ends here; the past-end signal serves drains.
|
||||
// The frame stays claimed on every exit, valueless ones included.
|
||||
claim_frame(content.layout());
|
||||
return GPoll::Error(Box::new(core_types::gpoll::GraphError::past_end()));
|
||||
}
|
||||
if entry.generation == ctx.arena().generation() {
|
||||
// SAFETY: within the generation the materialized batch stays live,
|
||||
// immutable, and laid out at the recorded stride.
|
||||
let value = unsafe { serve_frame(content.layout(), (entry.frames + lane * entry.stride) as *const u8) };
|
||||
return finalized(value, &entry.finality);
|
||||
unsafe { slot.fill_copy((entry.frames + lane * entry.stride) as *const u8) };
|
||||
// SAFETY: the copy images a complete record of this layout.
|
||||
return finalized(unsafe { slot.finish_served() }, &entry.finality);
|
||||
}
|
||||
match entry.lanes[lane].replay(content.layout(), ctx.arena()) {
|
||||
Some(value) => finalized(value, &entry.finality),
|
||||
match entry.lanes[lane].replay_into(&mut slot, ctx.arena()) {
|
||||
// SAFETY: the replay completes the record in the frame.
|
||||
Some(()) => finalized(unsafe { slot.finish_served() }, &entry.finality),
|
||||
None => GPoll::arena_exhausted(),
|
||||
}
|
||||
};
|
||||
if let Some(entry) = cache.lock().unwrap().as_ref()
|
||||
&& entry.key == key
|
||||
{
|
||||
return serve(entry);
|
||||
return serve(entry, slot);
|
||||
}
|
||||
if leveled {
|
||||
return match content.materialize_level(&ctx, ctx.arena()) {
|
||||
@@ -95,28 +97,19 @@ fn memoize<'e>(
|
||||
lanes,
|
||||
finality,
|
||||
};
|
||||
let result = serve(&entry);
|
||||
let result = serve(&entry, slot);
|
||||
*cache.lock().unwrap() = Some(entry);
|
||||
result
|
||||
}
|
||||
// A valueless materialization caches nothing, so the frames it left
|
||||
// behind have no reader and must not be counted against this node.
|
||||
LevelStatus::Pending => {
|
||||
// SAFETY: nothing borrows the frames above the entry mark.
|
||||
unsafe { core_types::record::interrupt_frame(entry_sp, content.layout()) };
|
||||
GPoll::Pending
|
||||
}
|
||||
LevelStatus::Error(error) => {
|
||||
// SAFETY: nothing borrows the frames above the entry mark.
|
||||
unsafe { core_types::record::interrupt_frame(entry_sp, content.layout()) };
|
||||
GPoll::Error(Box::new(error))
|
||||
}
|
||||
LevelStatus::Pending => GPoll::Pending,
|
||||
LevelStatus::Error(error) => GPoll::Error(Box::new(error)),
|
||||
};
|
||||
}
|
||||
let result = content.eval(&ctx);
|
||||
// The output layout is the content's, so the claim is the content's frame.
|
||||
let result = content.serve(&ctx, slot);
|
||||
let publishable = match &result {
|
||||
GPoll::Final(value) => Some((value, Finality::AllFinal)),
|
||||
GPoll::Partial(value) => Some((value, Finality::Partial)),
|
||||
GPoll::Final(served) => Some((served.record(), Finality::AllFinal)),
|
||||
GPoll::Partial(served) => Some((served.record(), Finality::Partial)),
|
||||
GPoll::Pending | GPoll::Fallback(_) | GPoll::Error(_) => None,
|
||||
};
|
||||
if let Some((value, finality)) = publishable {
|
||||
@@ -124,7 +117,7 @@ fn memoize<'e>(
|
||||
let copy = unsafe { OwnedRecord::copy_out(content.layout(), content.layout().rec(value)) };
|
||||
*cache.lock().unwrap() = Some(MemoLevel {
|
||||
key,
|
||||
// A scalar record replays from the deep copy; the value the eval
|
||||
// A scalar record replays from the deep copy; the value the serve
|
||||
// returned already lives in this frame.
|
||||
generation: u64::MAX,
|
||||
frames: 0,
|
||||
@@ -137,11 +130,12 @@ fn memoize<'e>(
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), path(graphene_core::memo))]
|
||||
fn frame_memo<'e>(
|
||||
fn frame_memo<'e, 'l>(
|
||||
ctx: impl Ctx + CacheHash + ExtractArena<'e>,
|
||||
#[data] cell: ArenaCell<FrameTable<Box<[u8]>, 32>>,
|
||||
content: impl Node<Context<'_>, Output = RecordValue<'e>>,
|
||||
) -> GPoll<RecordValue<'e>> {
|
||||
content: impl Node<Context<'_>>,
|
||||
frame: FrameClaim<'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
let arena = ctx.arena();
|
||||
let table = match cell.load(arena) {
|
||||
Some(table) => table,
|
||||
@@ -150,35 +144,39 @@ fn frame_memo<'e>(
|
||||
cell.store(weak);
|
||||
table
|
||||
}
|
||||
None => return content.eval(&ctx),
|
||||
None => return content.serve(&ctx, frame),
|
||||
},
|
||||
};
|
||||
// SAFETY: published bytes are same-frame copies of this edge's records,
|
||||
// so they carry the edge's layout with live parked references.
|
||||
let revive = |bytes: &'e Box<[u8]>| unsafe { serve_frame(content.layout(), bytes.as_ptr()) };
|
||||
// so they carry the edge's layout with live parked references, and the
|
||||
// claim is that layout's frame.
|
||||
let revive = |mut frame: FrameClaim<'l>, bytes: &Box<[u8]>| unsafe {
|
||||
frame.fill_copy(bytes.as_ptr());
|
||||
frame.finish_served()
|
||||
};
|
||||
match table.lookup(cache_key(ctx)) {
|
||||
Lookup::Hit(Finality::AllFinal, bytes) => GPoll::Final(revive(bytes)),
|
||||
Lookup::Hit(Finality::Partial, bytes) => GPoll::Partial(revive(bytes)),
|
||||
Lookup::Vacant(slot) => match content.eval(&ctx) {
|
||||
GPoll::Final(value) => {
|
||||
Lookup::Hit(Finality::AllFinal, bytes) => GPoll::Final(revive(frame, bytes)),
|
||||
Lookup::Hit(Finality::Partial, bytes) => GPoll::Partial(revive(frame, bytes)),
|
||||
Lookup::Vacant(slot) => match content.serve(&ctx, frame) {
|
||||
GPoll::Final(served) => {
|
||||
// SAFETY: the value came from this edge, so it carries the edge's layout.
|
||||
// The eval's own frame serves this pull; the publish feeds later ones.
|
||||
let bytes = unsafe { copy_record_bytes(content.layout(), content.layout().rec(&value)) };
|
||||
// The serve's own frame answers this pull; the publish feeds later ones.
|
||||
let bytes = unsafe { copy_record_bytes(content.layout(), content.layout().rec(served.record())) };
|
||||
slot.publish(bytes, Finality::AllFinal);
|
||||
GPoll::Final(value)
|
||||
GPoll::Final(served)
|
||||
}
|
||||
GPoll::Partial(value) => {
|
||||
GPoll::Partial(served) => {
|
||||
// SAFETY: as above.
|
||||
let bytes = unsafe { copy_record_bytes(content.layout(), content.layout().rec(&value)) };
|
||||
let bytes = unsafe { copy_record_bytes(content.layout(), content.layout().rec(served.record())) };
|
||||
slot.publish(bytes, Finality::Partial);
|
||||
GPoll::Partial(value)
|
||||
GPoll::Partial(served)
|
||||
}
|
||||
unpublishable => {
|
||||
slot.release();
|
||||
unpublishable
|
||||
}
|
||||
},
|
||||
Lookup::Full => content.eval(&ctx),
|
||||
Lookup::Full => content.serve(&ctx, frame),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,15 +187,16 @@ type MonitorValue = Arc<Mutex<Option<CtxSnapshot>>>;
|
||||
/// (context, source generations), so introspection recreates it by
|
||||
/// re-evaluating this edge with the rehydrated snapshot.
|
||||
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"))]
|
||||
fn monitor<'e>(
|
||||
fn monitor<'e, 'l>(
|
||||
ctx: impl Ctx + DeriveCtx + ExtractAll + ExtractArena<'e> + ModifyIndex + Copy,
|
||||
#[data] io: MonitorValue,
|
||||
content: impl Node<Context<'_>, Output = RecordValue<'e>>,
|
||||
) -> GPoll<RecordValue<'e>> {
|
||||
content: impl Node<Context<'_>>,
|
||||
slot: FrameClaim<'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
if ctx.index() == 0 {
|
||||
*io.lock().unwrap() = Some(CtxSnapshot::capture(ctx));
|
||||
}
|
||||
content.eval(&ctx)
|
||||
content.serve(&ctx, slot)
|
||||
}
|
||||
|
||||
fn serialize_monitor(io: &MonitorValue) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
|
||||
@@ -212,42 +211,33 @@ mod tests {
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::LiftedSource;
|
||||
use core_types::registry::{EdgeHandle, ErasedRecordNode};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
struct CountingNode(AtomicU32);
|
||||
|
||||
impl<Input> Node<Input> for CountingNode {
|
||||
type Output = u32;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<u32> {
|
||||
GPoll::Final(self.0.fetch_add(1, Ordering::Relaxed) + 1)
|
||||
}
|
||||
fn lifted<T: Clone + Send + Sync + core_types::StaticTypeSized>(value: T) -> LiftedSource<T, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<T>>
|
||||
where
|
||||
T::Static: Clone + Send + Sync,
|
||||
{
|
||||
LiftedSource::new(move |_: &ContextImpl<'_>| GPoll::Final(value.clone()))
|
||||
}
|
||||
|
||||
struct PartialCountingNode(AtomicU32);
|
||||
|
||||
impl<Input> Node<Input> for PartialCountingNode {
|
||||
type Output = u32;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<u32> {
|
||||
GPoll::Partial(self.0.fetch_add(1, Ordering::Relaxed) + 1)
|
||||
}
|
||||
fn counting() -> LiftedSource<u32, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<u32>> {
|
||||
let count = AtomicU32::new(0);
|
||||
LiftedSource::new(move |_: &ContextImpl<'_>| GPoll::Final(count.fetch_add(1, Ordering::Relaxed) + 1))
|
||||
}
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
GPoll::Final(self.0.clone())
|
||||
}
|
||||
fn partial_counting() -> LiftedSource<u32, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<u32>> {
|
||||
let count = AtomicU32::new(0);
|
||||
LiftedSource::new(move |_: &ContextImpl<'_>| GPoll::Partial(count.fetch_add(1, Ordering::Relaxed) + 1))
|
||||
}
|
||||
|
||||
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { core_types::record::stack::reserve(1 << 16); } EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(1 << 16);
|
||||
}
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
fn element_layout<T: Clone + Send + Sync + core_types::StaticTypeSized>() -> core_types::record::Layout
|
||||
@@ -265,12 +255,12 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = element_layout::<u32>();
|
||||
let monitor = MonitorNode::new(core_types::record::RecordLift::<u32, _>::new(ValueNode(11u32)), &layout);
|
||||
let monitor = MonitorNode::new(lifted::<u32>(11u32), &layout);
|
||||
let handle = EdgeHandle::new_record::<u32>(Arc::new(monitor) as Arc<ErasedRecordNode>);
|
||||
assert!(handle.serialize().is_none(), "no snapshot before the first eval");
|
||||
|
||||
let edge = handle.duplicate().downcast_record::<u32>().unwrap();
|
||||
let GPoll::Final(_) = edge.eval(&ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
|
||||
@@ -296,7 +286,7 @@ mod tests {
|
||||
let handle = EdgeHandle::new_record::<u32>(Arc::new(monitor) as Arc<ErasedRecordNode>);
|
||||
|
||||
let edge = handle.duplicate().downcast_record::<u32>().unwrap();
|
||||
let GPoll::Final(_) = edge.eval(&ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
|
||||
@@ -333,7 +323,7 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = element_layout::<Payload>();
|
||||
let memoized = MemoizeNode::new(core_types::record::RecordLift::<Payload, _>::new(ValueNode(Payload("deep".to_string(), 0))), &layout);
|
||||
let memoized = MemoizeNode::new(lifted::<Payload>(Payload("deep".to_string(), 0)), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<Payload, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 0)), "the miss serves the live value");
|
||||
@@ -348,7 +338,7 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = element_layout::<u32>();
|
||||
let memoized = MemoizeNode::new(core_types::record::RecordLift::<u32, _>::new(CountingNode(AtomicU32::new(0))), &layout);
|
||||
let memoized = MemoizeNode::new(counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||
@@ -365,7 +355,7 @@ mod tests {
|
||||
let scope_after = scope_fixture(&after, &arena);
|
||||
|
||||
let layout = element_layout::<u32>();
|
||||
let memoized = MemoizeNode::new(core_types::record::RecordLift::<u32, _>::new(CountingNode(AtomicU32::new(0))), &layout);
|
||||
let memoized = MemoizeNode::new(counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
||||
@@ -381,7 +371,7 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = element_layout::<u32>();
|
||||
let memoized = MemoizeNode::new(core_types::record::RecordLift::<u32, _>::new(PartialCountingNode(AtomicU32::new(0))), &layout);
|
||||
let memoized = MemoizeNode::new(partial_counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
||||
@@ -396,7 +386,7 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = element_layout::<u32>();
|
||||
let edge = EdgeHandle::new_record::<u32>(Arc::new(core_types::record::RecordLift::<u32, _>::new(CountingNode(AtomicU32::new(0)))) as Arc<ErasedRecordNode>);
|
||||
let edge = EdgeHandle::new_record::<u32>(Arc::new(counting()) as Arc<ErasedRecordNode>);
|
||||
let memoized = EdgeHandle::new_record::<u32>(Arc::new(MemoizeNode::new(edge.downcast_record::<u32>().unwrap(), &layout)) as Arc<ErasedRecordNode>);
|
||||
let stacked = MemoizeNode::new(memoized.downcast_record::<u32>().unwrap(), &layout);
|
||||
let stacked = core_types::record::RecordExtract::<u32, _>::new(stacked, &layout);
|
||||
@@ -413,12 +403,12 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = element_layout::<String>();
|
||||
let memo = FrameMemoNode::new(core_types::record::RecordLift::<String, _>::new(ValueNode("lent out".to_string())), &layout);
|
||||
let memo = FrameMemoNode::new(lifted::<String>("lent out".to_string()), &layout);
|
||||
|
||||
let GPoll::Final(first) = memo.eval(&ctx) else {
|
||||
let GPoll::Final(first) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
panic!("the miss must fill the frame table");
|
||||
};
|
||||
let GPoll::Final(second) = memo.eval(&ctx) else {
|
||||
let GPoll::Final(second) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
panic!("the hit must revive the published record");
|
||||
};
|
||||
let first: &String = unsafe { core_types::record::borrow_element(layout.rec(&first)) };
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
use core_types::Ctx;
|
||||
use core_types::attribute::{Attr, EditorLayerPath, Opacity, RemoveAttr, Transform};
|
||||
use core_types::context::{DeriveCtx, ExtractIndex, ExtractIndices, IndexLink, InjectIndex, ModifyIndex};
|
||||
use core_types::context::{DeriveCtx, ExtractIndex, IndexLink, InjectIndex, ModifyIndex};
|
||||
use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn};
|
||||
use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt, Level};
|
||||
use core_types::node::Lane;
|
||||
@@ -398,17 +398,8 @@ mod tests {
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{Layout, Rec, RecordSource, RecordValue, stack};
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
GPoll::Final(self.0.clone())
|
||||
}
|
||||
}
|
||||
use core_types::record::{FrameClaim, Layout, LiftedSource, Rec, RecordSource, Served, stack};
|
||||
use core_types::value::ValueSource;
|
||||
|
||||
struct RecordSourceNode<E> {
|
||||
layout: Layout,
|
||||
@@ -417,21 +408,28 @@ mod tests {
|
||||
partial: bool,
|
||||
}
|
||||
|
||||
impl<'e, E: Copy + Send + Sync + 'static> Node<ContextImpl<'e>> for RecordSourceNode<E> {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena());
|
||||
impl<C, E: Copy + Send + Sync + 'static> Node<C> for RecordSourceNode<E> {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(self.element);
|
||||
for (name, field) in &self.fields {
|
||||
frame.field::<f64>(name, 0, *field);
|
||||
}
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
let served = unsafe { slot.forward(&value) };
|
||||
match self.partial {
|
||||
true => GPoll::Partial(value),
|
||||
false => GPoll::Final(value),
|
||||
true => GPoll::Partial(served),
|
||||
false => GPoll::Final(served),
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
&self.layout
|
||||
}
|
||||
}
|
||||
|
||||
struct LeveledSourceNode {
|
||||
@@ -440,21 +438,26 @@ mod tests {
|
||||
field: Option<(&'static str, f64)>,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for LeveledSourceNode {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex> Node<C> for LeveledSourceNode {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let element = self.elements[input.innermost_index() as usize % self.elements.len()];
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(element);
|
||||
if let Some((name, value)) = self.field {
|
||||
frame.field::<f64>(name, 0, value);
|
||||
}
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
GPoll::Final(Extent::Exactly(self.elements.len()))
|
||||
}
|
||||
|
||||
@@ -468,19 +471,24 @@ mod tests {
|
||||
rows: Vec<(f64, DAffine2)>,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for LeveledTransformSource {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex> Node<C> for LeveledTransformSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let (element, transform) = self.rows[input.innermost_index() as usize % self.rows.len()];
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(element);
|
||||
frame.attr::<Transform>(transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
GPoll::Final(Extent::Exactly(self.rows.len()))
|
||||
}
|
||||
|
||||
@@ -496,21 +504,26 @@ mod tests {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for DrainSourceNode {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex> Node<C> for DrainSourceNode {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let lane = input.innermost_index();
|
||||
if lane >= self.count as u64 {
|
||||
return GPoll::past_end();
|
||||
}
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(lane as f64);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
GPoll::Final(Extent::AtLeast(0))
|
||||
}
|
||||
|
||||
@@ -523,23 +536,32 @@ mod tests {
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for IndexSourceNode {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex + core_types::context::ExtractIndices> Node<C> for IndexSourceNode {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
// Depth-0 content varying per copy: the enclosing (pushed) level's
|
||||
// index sits one link above the content's own innermost lane.
|
||||
let element = input.try_index().and_then(|mut indices| indices.nth(1)).unwrap_or(0) as f64;
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(element);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
&self.layout
|
||||
}
|
||||
}
|
||||
|
||||
fn scope_fixture<'a>(generations: &'a [(SourceId, u64)], arena: &'a Arena) -> EvalScope<'a> {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { stack::reserve(1 << 16); } EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
unsafe {
|
||||
stack::reserve(1 << 16);
|
||||
}
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
fn f64_layout(names: &[&'static str]) -> Layout {
|
||||
@@ -604,11 +626,11 @@ mod tests {
|
||||
node
|
||||
}
|
||||
|
||||
fn lifted_value<T: Clone + Send + Sync + core_types::StaticTypeSized + 'static>(value: T) -> (core_types::record::RecordLift<T, ValueNode<T>>, Layout)
|
||||
fn lifted_value<T: Clone + Send + Sync + core_types::StaticTypeSized + 'static>(value: T) -> (ValueSource<T>, Layout)
|
||||
where
|
||||
T::Static: Clone + Send + Sync,
|
||||
{
|
||||
let lift = core_types::record::RecordLift::<T, _>::new(ValueNode(value));
|
||||
let lift = ValueSource::new(value);
|
||||
let layout = Node::<ContextImpl>::layout(&lift).clone();
|
||||
(lift, layout)
|
||||
}
|
||||
@@ -643,8 +665,8 @@ mod tests {
|
||||
let leveled = repeat_opacity_layout(&base);
|
||||
reserve_for(&[&base, &leveled]);
|
||||
|
||||
let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(8u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
assert_eq!(node.layout(), &leveled);
|
||||
let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueSource::new(8u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
assert_eq!(Node::<ContextImpl>::layout(&node), &leveled);
|
||||
let GPoll::Final(served) = core_types::record::capture(&node, &indexed) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
@@ -662,7 +684,7 @@ mod tests {
|
||||
let base = f64_layout(&[]);
|
||||
reserve_for(&[&base]);
|
||||
|
||||
let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let node = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueSource::new(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
// The pushed level (0, the only level) reports the copy count.
|
||||
assert_eq!(node.extent_at(&ctx, 0), core_types::gpoll::GPoll::Final(core_types::gpoll::Extent::Exactly(3)));
|
||||
}
|
||||
@@ -804,7 +826,7 @@ mod tests {
|
||||
let (reverse_edge, reverse_layout) = lifted_value(false);
|
||||
reserve_for(&[&base, &leveled_content, &count_layout, &reverse_layout]);
|
||||
|
||||
let content = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let content = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueSource::new(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
@@ -861,7 +883,7 @@ mod tests {
|
||||
reserve_for(&[&base]);
|
||||
|
||||
let node = install(
|
||||
RepeatFadedNode::new(RecordSource::new(content, &base, &base), ValueNode(4u32), &base),
|
||||
RepeatFadedNode::new(RecordSource::new(content, &base, &base), ValueSource::new(4u32), &base),
|
||||
repeat_faded_layout_meta(),
|
||||
&[Some(&base)],
|
||||
);
|
||||
@@ -1299,7 +1321,7 @@ mod tests {
|
||||
let rows = [(1., 10.), (2., 30.), (3., 20.)];
|
||||
let build = |keep: bool| {
|
||||
install(
|
||||
MirrorNode::new(RecordSource::new(content(&rows), &layout, &layout), ValueNode(keep)),
|
||||
MirrorNode::new(RecordSource::new(content(&rows), &layout, &layout), ValueSource::new(keep)),
|
||||
mirror_layout_meta(),
|
||||
&[Some(&layout)],
|
||||
)
|
||||
@@ -1347,7 +1369,7 @@ mod tests {
|
||||
rows: rows.iter().map(|&(element, x)| (element, DAffine2::from_translation(glam::DVec2::new(x, 0.)))).collect(),
|
||||
};
|
||||
let node = install(
|
||||
ReverseLanesNode::new(RecordSource::new(content, &layout, &layout), ValueNode(0.25)),
|
||||
ReverseLanesNode::new(RecordSource::new(content, &layout, &layout), ValueSource::new(0.25)),
|
||||
reverse_lanes_layout_meta(),
|
||||
&[Some(&layout)],
|
||||
);
|
||||
@@ -1386,7 +1408,7 @@ mod tests {
|
||||
.map(|&(element, x)| (element, DAffine2::from_translation(glam::DVec2::new(x, 0.))))
|
||||
.collect(),
|
||||
};
|
||||
let node = install(MirrorNode::new(RecordSource::new(content, &layout, &layout), ValueNode(true)), mirror_layout_meta(), &[Some(&layout)]);
|
||||
let node = install(MirrorNode::new(RecordSource::new(content, &layout, &layout), ValueSource::new(true)), mirror_layout_meta(), &[Some(&layout)]);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
let head = ctx.index_head();
|
||||
let scoped = ctx.promoted(&head, 0);
|
||||
@@ -1415,15 +1437,11 @@ mod tests {
|
||||
/// a lane-varying value serves the range's first lane to all of them.
|
||||
#[test]
|
||||
fn batch_rebinds_an_eager_input_the_compiler_cannot_prove_invariant() {
|
||||
struct CountingValue<'a>(bool, &'a std::cell::Cell<u32>);
|
||||
|
||||
impl<Input> Node<Input> for CountingValue<'_> {
|
||||
type Output = bool;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<bool> {
|
||||
self.1.set(self.1.get() + 1);
|
||||
GPoll::Final(self.0)
|
||||
}
|
||||
fn counting_value(value: bool, evals: &std::cell::Cell<u32>) -> LiftedSource<bool, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<bool> + '_> {
|
||||
LiftedSource::new(move |_: &ContextImpl<'_>| {
|
||||
evals.set(evals.get() + 1);
|
||||
GPoll::Final(value)
|
||||
})
|
||||
}
|
||||
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
@@ -1441,7 +1459,7 @@ mod tests {
|
||||
.collect(),
|
||||
};
|
||||
let evals = std::cell::Cell::new(0u32);
|
||||
let mut node = MirrorNode::new(RecordSource::new(content, &layout, &layout), CountingValue(true, &evals));
|
||||
let mut node = MirrorNode::new(RecordSource::new(content, &layout, &layout), counting_value(true, &evals));
|
||||
let resolved = core_types::record::RecordLayout {
|
||||
lane_invariant: 0,
|
||||
..mirror_layout_meta().resolve(&[Some(&layout)])
|
||||
@@ -1462,15 +1480,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_binds_eager_inputs_once() {
|
||||
struct CountingValue<'a>(bool, &'a std::cell::Cell<u32>);
|
||||
|
||||
impl<Input> Node<Input> for CountingValue<'_> {
|
||||
type Output = bool;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<bool> {
|
||||
self.1.set(self.1.get() + 1);
|
||||
GPoll::Final(self.0)
|
||||
}
|
||||
fn counting_value(value: bool, evals: &std::cell::Cell<u32>) -> LiftedSource<bool, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<bool> + '_> {
|
||||
LiftedSource::new(move |_: &ContextImpl<'_>| {
|
||||
evals.set(evals.get() + 1);
|
||||
GPoll::Final(value)
|
||||
})
|
||||
}
|
||||
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
@@ -1489,7 +1503,7 @@ mod tests {
|
||||
};
|
||||
let evals = std::cell::Cell::new(0u32);
|
||||
let node = install(
|
||||
MirrorNode::new(RecordSource::new(content, &layout, &layout), CountingValue(true, &evals)),
|
||||
MirrorNode::new(RecordSource::new(content, &layout, &layout), counting_value(true, &evals)),
|
||||
mirror_layout_meta(),
|
||||
&[Some(&layout)],
|
||||
);
|
||||
@@ -1520,7 +1534,7 @@ mod tests {
|
||||
|
||||
// Element = the outer copy, so the total fold sums across both copies.
|
||||
let content = install(
|
||||
RepeatOpacityNode::new(IndexSourceNode { layout: base.clone() }, ValueNode(3u32), &base),
|
||||
RepeatOpacityNode::new(IndexSourceNode { layout: base.clone() }, ValueSource::new(3u32), &base),
|
||||
repeat_opacity_layout_meta(),
|
||||
&[Some(&base)],
|
||||
);
|
||||
@@ -1572,7 +1586,7 @@ mod tests {
|
||||
let out = f64_layout(&[]);
|
||||
reserve_for(&[&base, &leveled_content, &count_layout, &reverse_layout, &out]);
|
||||
|
||||
let content = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let content = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueSource::new(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let meta = core_types::record::LayoutMeta {
|
||||
sources: vec![0],
|
||||
reads: vec![],
|
||||
@@ -1651,7 +1665,7 @@ mod tests {
|
||||
};
|
||||
let path = vec![NodeId(7), NodeId(8)];
|
||||
let node = install(
|
||||
StampLayerPathNode::new(RecordSource::new(source, &source_layout, &source_layout), ValueNode(path.clone()), &source_layout),
|
||||
StampLayerPathNode::new(RecordSource::new(source, &source_layout, &source_layout), ValueSource::new(path.clone()), &source_layout),
|
||||
stamp_layer_path_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
);
|
||||
@@ -1661,7 +1675,7 @@ mod tests {
|
||||
let head = ctx.index_head();
|
||||
for (lane, element) in [(0u64, 10.), (1, 11.)] {
|
||||
let _lane_scope = unsafe { stack::ScopeGuard::enter() };
|
||||
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, lane)) else {
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&node, &ctx.promoted(&head, lane)) else {
|
||||
panic!("expected a final record at lane {lane}");
|
||||
};
|
||||
let rec = out.rec(&value);
|
||||
@@ -1780,9 +1794,9 @@ mod tests {
|
||||
let out = f64_layout(&[]);
|
||||
reserve_for(&[&base, &leveled, &out]);
|
||||
|
||||
let repeat = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueNode(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let repeat = install(RepeatOpacityNode::new(bare_source(&base, 7.), ValueSource::new(3u32), &base), repeat_opacity_layout_meta(), &[Some(&base)]);
|
||||
let node = install_flip(SumNode::new(repeat, &leveled), &out);
|
||||
assert_eq!(node.layout().depth, 0, "the reducer collapsed the rank level");
|
||||
assert_eq!(Node::<ContextImpl>::layout(&node).depth, 0, "the reducer collapsed the rank level");
|
||||
|
||||
let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
@@ -1865,17 +1879,17 @@ mod tests {
|
||||
let chain = install(
|
||||
MultiplyOpacityNode::new(
|
||||
install(
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout),
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueSource::new(0.5), &source_layout),
|
||||
multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
),
|
||||
ValueNode(0.5),
|
||||
ValueSource::new(0.5),
|
||||
&modified,
|
||||
),
|
||||
multiply_opacity_layout_meta(),
|
||||
&[Some(&modified)],
|
||||
);
|
||||
assert_eq!(chain.layout(), &stacked);
|
||||
assert_eq!(Node::<ContextImpl>::layout(&chain), &stacked);
|
||||
let GPoll::Final(served) = core_types::record::capture(&chain, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
@@ -1917,7 +1931,7 @@ mod tests {
|
||||
let chain = install(
|
||||
MeasureNode::new(
|
||||
install(
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, -2.), ValueNode(0.5), &source_layout),
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, -2.), ValueSource::new(0.5), &source_layout),
|
||||
multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
),
|
||||
@@ -1954,7 +1968,7 @@ mod tests {
|
||||
let chain = install(
|
||||
ShadeNode::new(
|
||||
install(
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout),
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueSource::new(0.5), &source_layout),
|
||||
multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
),
|
||||
@@ -1983,7 +1997,7 @@ mod tests {
|
||||
let u32_faded = fade_layout(&u32_source);
|
||||
reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]);
|
||||
|
||||
let wide = install(FadeNode::new(bare_source(&f64_source, 8.), ValueNode(0.5), &f64_source), fade_layout_meta(), &[Some(&f64_source)]);
|
||||
let wide = install(FadeNode::new(bare_source(&f64_source, 8.), ValueSource::new(0.5), &f64_source), fade_layout_meta(), &[Some(&f64_source)]);
|
||||
let GPoll::Final(served) = core_types::record::capture(&wide, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
@@ -1998,7 +2012,7 @@ mod tests {
|
||||
fields: vec![],
|
||||
partial: false,
|
||||
},
|
||||
ValueNode(0.25),
|
||||
ValueSource::new(0.25),
|
||||
&u32_source,
|
||||
),
|
||||
fade_layout_meta(),
|
||||
@@ -2021,7 +2035,7 @@ mod tests {
|
||||
let layout = source_opacity_layout();
|
||||
reserve_for(&[&layout]);
|
||||
|
||||
let node = install(SourceOpacityNode::new(ValueNode(()), ValueNode(3.), ValueNode(0.25)), source_opacity_layout_meta(), &[]);
|
||||
let node = install(SourceOpacityNode::new(ValueSource::new(()), ValueSource::new(3.), ValueSource::new(0.25)), source_opacity_layout_meta(), &[]);
|
||||
assert_eq!(Node::<ContextImpl>::layout(&node), &layout);
|
||||
let GPoll::Final(served) = core_types::record::capture(&node, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
@@ -2049,7 +2063,7 @@ mod tests {
|
||||
fields: vec![],
|
||||
partial: true,
|
||||
},
|
||||
ValueNode(0.5),
|
||||
ValueSource::new(0.5),
|
||||
&source_layout,
|
||||
),
|
||||
multiply_opacity_layout_meta(),
|
||||
@@ -2073,7 +2087,7 @@ mod tests {
|
||||
reserve_for(&[&source_layout, &modified]);
|
||||
|
||||
let ok = install(
|
||||
CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(0.5), &source_layout),
|
||||
CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueSource::new(0.5), &source_layout),
|
||||
checked_multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
);
|
||||
@@ -2083,11 +2097,11 @@ mod tests {
|
||||
assert_eq!(served.attr::<Opacity>(), 0.5);
|
||||
|
||||
let failing = install(
|
||||
CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout),
|
||||
CheckedMultiplyOpacityNode::new(bare_source(&source_layout, 1.), ValueSource::new(-1.), &source_layout),
|
||||
checked_multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
);
|
||||
let GPoll::Error(error) = failing.eval(&ctx) else {
|
||||
let GPoll::Error(error) = core_types::record::serve_edge(&failing, &ctx) else {
|
||||
panic!("expected an error");
|
||||
};
|
||||
assert!(error.kind == "negative factor");
|
||||
@@ -2108,11 +2122,11 @@ mod tests {
|
||||
let chain = install(
|
||||
ScaleNode::new(
|
||||
install(
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout),
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 2.), ValueSource::new(0.5), &source_layout),
|
||||
multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
),
|
||||
ValueNode(3.),
|
||||
ValueSource::new(3.),
|
||||
&modified,
|
||||
),
|
||||
scale_layout_meta(),
|
||||
@@ -2178,7 +2192,7 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let source_layout = f64_layout(&["opacity"]);
|
||||
let factor = core_types::record::RecordLift::<f64, _>::new(ValueNode(3.));
|
||||
let factor = ValueSource::new(3.);
|
||||
let factor_layout = Node::<ContextImpl>::layout(&factor).clone();
|
||||
reserve_for(&[&source_layout]);
|
||||
|
||||
@@ -2307,14 +2321,14 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let unit = core_types::record::RecordLift::<(), _>::new(ValueNode(()));
|
||||
let unit = ValueSource::new(());
|
||||
let unit_layout = Node::<ContextImpl>::layout(&unit).clone();
|
||||
let content_layout = f64_layout(&["opacity"]);
|
||||
reserve_for(&[&content_layout]);
|
||||
|
||||
let run = |opacity: Option<f64>| {
|
||||
let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
||||
let alternate = core_types::record::RecordLift::<f64, _>::new(CountingValue(evals.clone()));
|
||||
let alternate = counting_source(evals.clone());
|
||||
let alternate_layout = Node::<ContextImpl>::layout(&alternate).clone();
|
||||
let (content_layout, fields) = match opacity {
|
||||
Some(value) => (content_layout.clone(), vec![("opacity", value)]),
|
||||
@@ -2322,7 +2336,7 @@ mod tests {
|
||||
};
|
||||
let node = install_flip(
|
||||
FallbackNode::new(
|
||||
core_types::record::RecordLift::<(), _>::new(ValueNode(())),
|
||||
ValueSource::new(()),
|
||||
f64_record_source(&content_layout, 7., fields),
|
||||
alternate,
|
||||
&unit_layout,
|
||||
@@ -2331,7 +2345,7 @@ mod tests {
|
||||
),
|
||||
&f64_layout(&[]),
|
||||
);
|
||||
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&node, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let element = unsafe { Node::<ContextImpl>::layout(&node).rec(&value).element::<f64>() };
|
||||
@@ -2362,7 +2376,7 @@ mod tests {
|
||||
install(
|
||||
StripOpacityNode::new(
|
||||
install(
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout),
|
||||
MultiplyOpacityNode::new(bare_source(&source_layout, 4.), ValueSource::new(0.5), &source_layout),
|
||||
multiply_opacity_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
),
|
||||
@@ -2421,17 +2435,17 @@ mod tests {
|
||||
let chain = install(
|
||||
LabelNode::new(
|
||||
install(
|
||||
LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout),
|
||||
LabelNode::new(bare_source(&source_layout, 1.), ValueSource::new(String::from("a")), &source_layout),
|
||||
label_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
),
|
||||
ValueNode(String::from("b")),
|
||||
ValueSource::new(String::from("b")),
|
||||
&labeled,
|
||||
),
|
||||
label_layout_meta(),
|
||||
&[Some(&labeled)],
|
||||
);
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&chain, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = relabeled.rec(&value);
|
||||
@@ -2560,18 +2574,24 @@ mod tests {
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for RealTimeProbe {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex + core_types::ExtractRealTime> Node<C> for RealTimeProbe {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let element: f64 = match core_types::context::ExtractRealTime::try_real_time(input) {
|
||||
Some(_) => 1.,
|
||||
None => 0.,
|
||||
};
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = core_types::record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(element);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
&self.layout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2640,7 +2660,7 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let lift = core_types::record::RecordLift::<String, _>::new(ValueNode(String::from("parked")));
|
||||
let lift = ValueSource::new(String::from("parked"));
|
||||
let layout = Node::<ContextImpl>::layout(&lift).clone();
|
||||
let chain = core_types::record::RecordExtract::<String, _>::new(lift, &layout);
|
||||
|
||||
@@ -2664,7 +2684,7 @@ mod tests {
|
||||
|
||||
let chain = ForwardRecordNode::new(RecordSource::new(f64_record_source(&layout, 4., vec![("opacity", 0.25)]), &layout, &layout.clone()), &layout);
|
||||
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&chain, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = layout.rec(&value);
|
||||
@@ -2703,15 +2723,11 @@ mod tests {
|
||||
assert_eq!(served.element::<f64>(), 4.);
|
||||
}
|
||||
|
||||
struct CountingValue(std::sync::Arc<std::sync::atomic::AtomicU32>);
|
||||
|
||||
impl<Input> Node<Input> for CountingValue {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
fn counting_source(evals: std::sync::Arc<std::sync::atomic::AtomicU32>) -> LiftedSource<f64, impl for<'c> Fn(&ContextImpl<'c>) -> GPoll<f64>> {
|
||||
LiftedSource::new(move |_: &ContextImpl<'_>| {
|
||||
evals.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
GPoll::Final(21.)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2722,7 +2738,7 @@ mod tests {
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let evals = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
|
||||
let lift = core_types::record::RecordLift::<f64, _>::new(CountingValue(evals.clone()));
|
||||
let lift = counting_source(evals.clone());
|
||||
let layout = Node::<ContextImpl>::layout(&lift).clone();
|
||||
let memo = crate::memo::MemoizeNode::new(lift, &layout);
|
||||
|
||||
@@ -2755,7 +2771,7 @@ mod tests {
|
||||
};
|
||||
let memo = crate::memo::MemoizeNode::new(source, &layout);
|
||||
|
||||
let GPoll::Partial(_) = memo.eval(&ctx) else {
|
||||
let GPoll::Partial(_) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
let GPoll::Partial(served) = core_types::record::capture(&memo, &ctx) else {
|
||||
@@ -2773,7 +2789,7 @@ mod tests {
|
||||
reserve_for(&[&labeled, &labeled]);
|
||||
|
||||
let chain = install(
|
||||
LabelNode::new(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout),
|
||||
LabelNode::new(bare_source(&source_layout, 1.), ValueSource::new(String::from("a")), &source_layout),
|
||||
label_layout_meta(),
|
||||
&[Some(&source_layout)],
|
||||
);
|
||||
@@ -2783,7 +2799,7 @@ mod tests {
|
||||
{
|
||||
let scope = scope_fixture(&generations, &first_arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
let GPoll::Final(_) = memo.eval(&ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
}
|
||||
@@ -2791,7 +2807,7 @@ mod tests {
|
||||
let replay_arena = Arena::new(1024).unwrap();
|
||||
let scope = scope_fixture(&generations, &replay_arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
let GPoll::Final(value) = memo.eval(&ctx) else {
|
||||
let GPoll::Final(value) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = labeled.rec(&value);
|
||||
|
||||
@@ -222,39 +222,35 @@ mod tests {
|
||||
use core_types::SourceId;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::attribute::Attribute as AttributeMarker;
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena, ExtractIndices};
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{self, Layout, RecordSource, RecordValue, stack};
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
GPoll::Final(self.0.clone())
|
||||
}
|
||||
}
|
||||
use core_types::record::{self, FrameClaim, Layout, RecordSource, Served, stack};
|
||||
use core_types::value::ValueSource;
|
||||
|
||||
struct GraphicSource {
|
||||
layout: Layout,
|
||||
rows: Vec<(Graphic<'static>, DAffine2)>,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for GraphicSource {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex> Node<C> for GraphicSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let (graphic, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()];
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(graphic.clone());
|
||||
frame.attr::<Transform>(*transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
GPoll::Final(Extent::Exactly(self.rows.len()))
|
||||
}
|
||||
|
||||
@@ -342,7 +338,7 @@ mod tests {
|
||||
&$layout,
|
||||
&$layout,
|
||||
),
|
||||
ValueNode($fully),
|
||||
ValueSource::new($fully),
|
||||
),
|
||||
flatten_layout_meta(),
|
||||
&[Some(&$layout)],
|
||||
@@ -358,31 +354,36 @@ mod tests {
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
fn vararg_text(input: &ContextImpl<'_>) -> Option<String> {
|
||||
fn vararg_text<C: core_types::ExtractVarArgs>(input: &C) -> Option<String> {
|
||||
let arg = core_types::ExtractVarArgs::vararg(input, 0).ok()?;
|
||||
let list = arg.downcast_ref::<core_types::list::List<Graphic>>()?;
|
||||
let Graphic::Text(text) = list.element(0)? else { return None };
|
||||
Some(text.clone())
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for PerRowSource {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex + core_types::ExtractVarArgs> Node<C> for PerRowSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let Some(label) = vararg_text(input) else {
|
||||
return GPoll::error("the subgraph fixture expects a text vararg");
|
||||
};
|
||||
let lane = input.innermost_index();
|
||||
let graphic = text(&format!("{label}{lane}"));
|
||||
let translated = DAffine2::from_translation(glam::DVec2::new(lane as f64, 0.));
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(graphic);
|
||||
frame.attr::<Transform>(translated);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
match vararg_text(input) {
|
||||
Some(label) => GPoll::Final(Extent::Exactly(label.len())),
|
||||
None => GPoll::error("the subgraph fixture expects a text vararg"),
|
||||
@@ -681,7 +682,7 @@ mod tests {
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(1)), "the group is the level's single lane");
|
||||
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(out.rec(&value)) }) else {
|
||||
@@ -716,7 +717,7 @@ mod tests {
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let copy = unsafe { (out.element.clone_out)(out.rec(&value).ptr()) };
|
||||
@@ -746,18 +747,23 @@ mod tests {
|
||||
colors: Vec<Color>,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for ColorSource {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
impl<C: ExtractIndex> Node<C> for ColorSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let color = self.colors[input.innermost_index() as usize];
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = record::FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(color);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
GPoll::Final(Extent::Exactly(self.colors.len()))
|
||||
}
|
||||
|
||||
@@ -809,7 +815,7 @@ mod tests {
|
||||
);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(out.rec(&value)) }) else {
|
||||
@@ -844,7 +850,7 @@ mod tests {
|
||||
// SAFETY: the element is cloned out inside the scope, so no borrow
|
||||
// into the frame escapes it.
|
||||
let _scope = unsafe { stack::ScopeGuard::enter() };
|
||||
let GPoll::Final(value) = wrapped.eval(&ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&wrapped, &ctx.promoted(&head, 0)) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let group = unsafe { record::borrow_element::<Graphic>(wrap_out.rec(&value)) }.clone();
|
||||
|
||||
@@ -220,28 +220,22 @@ mod tests {
|
||||
use core_types::{ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime};
|
||||
use graphene_application_io::TimingInformation;
|
||||
|
||||
struct ProbeNode;
|
||||
|
||||
impl<'a> Node<ContextImpl<'a>> for ProbeNode {
|
||||
type Output = RenderOutput;
|
||||
|
||||
fn eval(&self, ctx: &ContextImpl<'a>) -> GPoll<RenderOutput> {
|
||||
let render_params = ctx.vararg(0).unwrap().downcast_ref::<RenderParams>().expect("the vararg chain must start with RenderParams");
|
||||
assert_eq!(render_params.scale, 2.0);
|
||||
assert!(matches!(ctx.vararg(1), Err(VarArgsResult::IndexOutOfBounds)), "the RenderConfig must not leak downstream");
|
||||
assert_eq!(ctx.footprint().transform, glam::DAffine2::from_scale(glam::DVec2::splat(2.0)) * Footprint::DEFAULT.transform);
|
||||
assert_eq!(ctx.try_real_time(), Some(1.5));
|
||||
assert_eq!(ctx.try_animation_time(), Some(2.0));
|
||||
assert_eq!(ctx.try_pointer_position(), Some(glam::DVec2::new(3.0, 4.0)));
|
||||
GPoll::Final(RenderOutput {
|
||||
data: RenderOutputType::Buffer {
|
||||
data: Vec::new(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
metadata: RenderMetadata::default(),
|
||||
})
|
||||
}
|
||||
fn probe(ctx: &ContextImpl) -> GPoll<RenderOutput> {
|
||||
let render_params = ctx.vararg(0).unwrap().downcast_ref::<RenderParams>().expect("the vararg chain must start with RenderParams");
|
||||
assert_eq!(render_params.scale, 2.0);
|
||||
assert!(matches!(ctx.vararg(1), Err(VarArgsResult::IndexOutOfBounds)), "the RenderConfig must not leak downstream");
|
||||
assert_eq!(ctx.footprint().transform, glam::DAffine2::from_scale(glam::DVec2::splat(2.0)) * Footprint::DEFAULT.transform);
|
||||
assert_eq!(ctx.try_real_time(), Some(1.5));
|
||||
assert_eq!(ctx.try_animation_time(), Some(2.0));
|
||||
assert_eq!(ctx.try_pointer_position(), Some(glam::DVec2::new(3.0, 4.0)));
|
||||
GPoll::Final(RenderOutput {
|
||||
data: RenderOutputType::Buffer {
|
||||
data: Vec::new(),
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
metadata: RenderMetadata::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -265,10 +259,13 @@ mod tests {
|
||||
};
|
||||
let ctx = root.with_varargs(&varargs);
|
||||
|
||||
let probe = core_types::record::RecordLift::<RenderOutput, _>::new(ProbeNode);
|
||||
let probe = core_types::record::LiftedSource::<RenderOutput, _>::new(probe);
|
||||
let layout = Node::<ContextImpl>::layout(&probe).clone();
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { core_types::record::stack::reserve(layout.frame_bytes().max(1 << 12)); } let mut graph = CreateContextNode::new(probe, &layout);
|
||||
unsafe {
|
||||
core_types::record::stack::reserve(layout.frame_bytes().max(1 << 12));
|
||||
}
|
||||
let mut graph = CreateContextNode::new(probe, &layout);
|
||||
// The executor resolves and installs the node's own layout at wiring;
|
||||
// without it the flip tail writes through the default empty layout.
|
||||
Node::<ContextImpl>::set_layout(
|
||||
@@ -280,7 +277,7 @@ mod tests {
|
||||
lane_invariant: u32::MAX,
|
||||
},
|
||||
);
|
||||
let GPoll::Final(result) = Node::<ContextImpl>::eval(&graph, &ctx) else {
|
||||
let GPoll::Final(result) = core_types::record::serve_edge(&graph, &ctx) else {
|
||||
panic!("create_context must complete synchronously");
|
||||
};
|
||||
let output: &RenderOutput = unsafe { core_types::record::borrow_element(layout.rec(&result)) };
|
||||
|
||||
@@ -1011,34 +1011,14 @@ mod test {
|
||||
mod graphene_test {
|
||||
use super::*;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractIndex};
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::gpoll::{Finality, GPoll};
|
||||
use core_types::node::{BatchStatus, Node};
|
||||
use core_types::record::{Layout, RecordLift, RecordValue, stack};
|
||||
use core_types::record::{Layout, LiftedSource, RecordValue, serve_edge, stack};
|
||||
use core_types::registry::{ErasedRecordNode, construct};
|
||||
use core_types::value::record_value_edge;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
struct SourceNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> Node<Input> for SourceNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
GPoll::Final(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct IndexNode;
|
||||
|
||||
impl<Input: ExtractIndex> Node<Input> for IndexNode {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, input: &Input) -> GPoll<f64> {
|
||||
GPoll::Final(input.index() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
fn scope_fixture(arena: &Arena) -> EvalScope<'_> {
|
||||
EvalScope::new(None, None, None, &[], arena)
|
||||
}
|
||||
@@ -1049,13 +1029,13 @@ mod graphene_test {
|
||||
|
||||
/// Lifts a plain-element test source onto a record wire, returned beside its
|
||||
/// element-only layout for the generated node's constructor.
|
||||
fn lifted<T, N>(node: N) -> (RecordLift<T, N>, Layout)
|
||||
fn lifted<T, F>(kernel: F) -> (LiftedSource<T, F>, Layout)
|
||||
where
|
||||
T: Clone + Send + Sync + core_types::StaticTypeSized + 'static,
|
||||
<T as core_types::StaticTypeSized>::Static: Clone + Send + Sync,
|
||||
N: for<'c> Node<ContextImpl<'c>, Output = T>,
|
||||
F: for<'c> Fn(&ContextImpl<'c>) -> GPoll<T>,
|
||||
{
|
||||
let lift = RecordLift::<T, _>::new(node);
|
||||
let lift = LiftedSource::<T, _>::new(kernel);
|
||||
let layout = Node::<ContextImpl>::layout(&lift).clone();
|
||||
(lift, layout)
|
||||
}
|
||||
@@ -1087,13 +1067,13 @@ mod graphene_test {
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let (a, la) = lifted(SourceNode(1.0f64));
|
||||
let (b, lb) = lifted(SourceNode(2.0f64));
|
||||
let (a, la) = lifted(|_: &ContextImpl| GPoll::Final(1.0f64));
|
||||
let (b, lb) = lifted(|_: &ContextImpl| GPoll::Final(2.0f64));
|
||||
let out = out_layout::<f64>();
|
||||
let graph = installed(AddNode::<_, _, f64, f64>::new(a, b, &la, &lb), &out);
|
||||
reserve_for(&[&la, &lb, &out]);
|
||||
|
||||
let GPoll::Final(value) = Node::eval(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 3.0);
|
||||
@@ -1105,8 +1085,8 @@ mod graphene_test {
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let (index, li) = lifted(IndexNode);
|
||||
let (src, ls) = lifted(SourceNode(10.0f64));
|
||||
let (index, li) = lifted(|input: &ContextImpl| GPoll::Final(core_types::ExtractIndex::<0>::index(input) as f64));
|
||||
let (src, ls) = lifted(|_: &ContextImpl| GPoll::Final(10.0f64));
|
||||
let out = out_layout::<f64>();
|
||||
let node = installed(AddNode::<_, _, f64, f64>::new(index, src, &li, &ls), &out);
|
||||
reserve_for(&[&li, &ls, &out]);
|
||||
@@ -1142,7 +1122,7 @@ mod graphene_test {
|
||||
let edge = wired.downcast_record::<bool>().unwrap();
|
||||
reserve_for(&[&layout]);
|
||||
|
||||
let GPoll::Final(value) = edge.eval(&ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert!(element::<bool>(&layout, &value));
|
||||
@@ -1188,7 +1168,7 @@ mod graphene_test {
|
||||
let edge = wired.downcast_record::<f64>().unwrap();
|
||||
reserve_for(&[&layout]);
|
||||
|
||||
let GPoll::Final(value) = edge.eval(&ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&layout, &value), 4.0);
|
||||
@@ -1209,32 +1189,33 @@ mod graphene_test {
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
struct CountingSource(Arc<AtomicU32>, f64);
|
||||
|
||||
impl<Input> Node<Input> for CountingSource {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
self.0.fetch_add(1, Ordering::Relaxed);
|
||||
GPoll::Final(self.1)
|
||||
}
|
||||
}
|
||||
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let taken = Arc::new(AtomicU32::new(0));
|
||||
let untaken = Arc::new(AtomicU32::new(0));
|
||||
let (cond, lc) = lifted(SourceNode(true));
|
||||
let (if_true, lt) = lifted(CountingSource(taken.clone(), 1.0));
|
||||
let (if_false, lf) = lifted(CountingSource(untaken.clone(), 2.0));
|
||||
let (cond, lc) = lifted(|_: &ContextImpl| GPoll::Final(true));
|
||||
let (if_true, lt) = lifted({
|
||||
let runs = taken.clone();
|
||||
move |_: &ContextImpl| {
|
||||
runs.fetch_add(1, Ordering::Relaxed);
|
||||
GPoll::Final(1.0)
|
||||
}
|
||||
});
|
||||
let (if_false, lf) = lifted({
|
||||
let runs = untaken.clone();
|
||||
move |_: &ContextImpl| {
|
||||
runs.fetch_add(1, Ordering::Relaxed);
|
||||
GPoll::Final(2.0)
|
||||
}
|
||||
});
|
||||
let union = core_types::record::Layout::union(&[<, &lf]);
|
||||
let graph = SwitchNode::new(cond, if_true, if_false, &union, &lc);
|
||||
let out = Node::<ContextImpl>::layout(&graph).clone();
|
||||
reserve_for(&[&lc, <, &lf, &out]);
|
||||
|
||||
let GPoll::Final(value) = Node::eval(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 1.0);
|
||||
@@ -1244,44 +1225,24 @@ mod graphene_test {
|
||||
|
||||
#[test]
|
||||
fn converted_switch_passes_branch_status_through() {
|
||||
struct PendingSource;
|
||||
|
||||
impl<Input> Node<Input> for PendingSource {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
GPoll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
struct PartialSource;
|
||||
|
||||
impl<Input> Node<Input> for PartialSource {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
GPoll::Partial(7.0)
|
||||
}
|
||||
}
|
||||
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let (c1, lc1) = lifted(SourceNode(true));
|
||||
let (p1, lp1) = lifted(PendingSource);
|
||||
let (pa1, lpa1) = lifted(PartialSource);
|
||||
let (c1, lc1) = lifted(|_: &ContextImpl| GPoll::Final(true));
|
||||
let (p1, lp1) = lifted(|_: &ContextImpl| GPoll::<f64>::Pending);
|
||||
let (pa1, lpa1) = lifted(|_: &ContextImpl| GPoll::Partial(7.0f64));
|
||||
let pending = SwitchNode::new(c1, p1, pa1, &core_types::record::Layout::union(&[&lp1, &lpa1]), &lc1);
|
||||
|
||||
let (c2, lc2) = lifted(SourceNode(false));
|
||||
let (p2, lp2) = lifted(PendingSource);
|
||||
let (pa2, lpa2) = lifted(PartialSource);
|
||||
let (c2, lc2) = lifted(|_: &ContextImpl| GPoll::Final(false));
|
||||
let (p2, lp2) = lifted(|_: &ContextImpl| GPoll::<f64>::Pending);
|
||||
let (pa2, lpa2) = lifted(|_: &ContextImpl| GPoll::Partial(7.0f64));
|
||||
let partial = SwitchNode::new(c2, p2, pa2, &core_types::record::Layout::union(&[&lp2, &lpa2]), &lc2);
|
||||
let out = Node::<ContextImpl>::layout(&partial).clone();
|
||||
reserve_for(&[&lc1, &lp1, &lpa1, &lc2, &lp2, &lpa2, &out]);
|
||||
|
||||
assert!(matches!(Node::eval(&pending, &ctx), GPoll::Pending));
|
||||
let GPoll::Partial(value) = Node::eval(&partial, &ctx) else {
|
||||
assert!(matches!(serve_edge(&pending, &ctx), GPoll::Pending));
|
||||
let GPoll::Partial(value) = serve_edge(&partial, &ctx) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 7.0);
|
||||
@@ -1289,29 +1250,19 @@ mod graphene_test {
|
||||
|
||||
#[test]
|
||||
fn converted_switch_merges_condition_status_into_the_branch_result() {
|
||||
struct PartialCondition;
|
||||
|
||||
impl<Input> Node<Input> for PartialCondition {
|
||||
type Output = bool;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<bool> {
|
||||
GPoll::Partial(true)
|
||||
}
|
||||
}
|
||||
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let (cond, lc) = lifted(PartialCondition);
|
||||
let (if_true, lt) = lifted(SourceNode(1.0f64));
|
||||
let (if_false, lf) = lifted(SourceNode(2.0f64));
|
||||
let (cond, lc) = lifted(|_: &ContextImpl| GPoll::Partial(true));
|
||||
let (if_true, lt) = lifted(|_: &ContextImpl| GPoll::Final(1.0f64));
|
||||
let (if_false, lf) = lifted(|_: &ContextImpl| GPoll::Final(2.0f64));
|
||||
let union = core_types::record::Layout::union(&[<, &lf]);
|
||||
let graph = SwitchNode::new(cond, if_true, if_false, &union, &lc);
|
||||
let out = Node::<ContextImpl>::layout(&graph).clone();
|
||||
reserve_for(&[&lc, <, &lf, &out]);
|
||||
|
||||
let GPoll::Partial(value) = Node::eval(&graph, &ctx) else {
|
||||
let GPoll::Partial(value) = serve_edge(&graph, &ctx) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 1.0);
|
||||
@@ -1319,27 +1270,17 @@ mod graphene_test {
|
||||
|
||||
#[test]
|
||||
fn generated_eval_computes_on_stand_in_and_traces_fallback() {
|
||||
struct FallbackNode;
|
||||
|
||||
impl<Input> Node<Input> for FallbackNode {
|
||||
type Output = f64;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<f64> {
|
||||
GPoll::fallback(0.0, "upstream failed")
|
||||
}
|
||||
}
|
||||
|
||||
let arena = Arena::new(64).unwrap();
|
||||
let scope = scope_fixture(&arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let (fallback, lfb) = lifted(FallbackNode);
|
||||
let (src, ls) = lifted(SourceNode(5.0f64));
|
||||
let (fallback, lfb) = lifted(|_: &ContextImpl| GPoll::fallback(0.0f64, "upstream failed"));
|
||||
let (src, ls) = lifted(|_: &ContextImpl| GPoll::Final(5.0f64));
|
||||
let out = out_layout::<f64>();
|
||||
let graph = installed(AddNode::<_, _, f64, f64>::new(fallback, src, &lfb, &ls), &out);
|
||||
reserve_for(&[&lfb, &ls, &out]);
|
||||
|
||||
let GPoll::Fallback(boxed) = Node::eval(&graph, &ctx) else {
|
||||
let GPoll::Fallback(boxed) = serve_edge(&graph, &ctx) else {
|
||||
panic!("fallback must propagate with the computed stand-in");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &boxed.0), 5.0);
|
||||
|
||||
@@ -181,37 +181,33 @@ mod test {
|
||||
use super::*;
|
||||
use core_types::SourceId;
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{FieldWrite, FrameBuilder, Layout, RecordSource, RecordValue, capture, element_write, stack};
|
||||
use core_types::record::{FieldWrite, FrameBuilder, FrameClaim, Layout, RecordSource, Served, capture, element_write, stack};
|
||||
use core_types::value::ValueSource;
|
||||
use vector_types::subpath::Subpath;
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
|
||||
type Output = T;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<T> {
|
||||
GPoll::Final(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
struct TransformSource {
|
||||
layout: Layout,
|
||||
element: f64,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for TransformSource {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
use core_types::context::ExtractArena;
|
||||
let mut frame = FrameBuilder::new(&self.layout, input.arena());
|
||||
impl<C: ExtractIndex> Node<C> for TransformSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let mut frame = FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(self.element);
|
||||
frame.attr::<TransformAttr>(self.transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
&self.layout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,20 +221,24 @@ mod test {
|
||||
rows: Vec<(Vector, DAffine2)>,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for VectorRows {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
use core_types::context::{ExtractArena, ExtractIndices};
|
||||
impl<C: ExtractIndex> Node<C> for VectorRows {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let (vector, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()];
|
||||
let mut frame = FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(vector.clone());
|
||||
frame.attr::<TransformAttr>(*transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn extent_at(&self, _input: &ContextImpl<'e>, _level: u8) -> GPoll<Extent> {
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
GPoll::Final(Extent::Exactly(self.rows.len()))
|
||||
}
|
||||
|
||||
@@ -255,17 +255,18 @@ mod test {
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for PositionProbe {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
use core_types::context::{ExtractArena, ExtractPosition};
|
||||
impl<C: ExtractIndex + core_types::context::ExtractPosition> Node<C> for PositionProbe {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
let position = input.try_position().and_then(|mut positions| positions.next()).unwrap_or(DVec2::ZERO);
|
||||
let mut frame = FrameBuilder::new(&self.layout, input.arena());
|
||||
let mut frame = FrameBuilder::new(&self.layout, ExtractArena::arena(input));
|
||||
frame.element(position.x);
|
||||
frame.attr::<TransformAttr>(DAffine2::IDENTITY);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
GPoll::Final(value)
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
@@ -293,9 +294,9 @@ mod test {
|
||||
|
||||
let mut node = RepeatArrayNode::new(
|
||||
RecordSource::new(content, &layout, &layout),
|
||||
ValueNode(DVec2::new(10., 0.)),
|
||||
ValueNode(0.0f64),
|
||||
ValueNode(3u32),
|
||||
ValueSource::new(DVec2::new(10., 0.)),
|
||||
ValueSource::new(0.0f64),
|
||||
ValueSource::new(3u32),
|
||||
&layout,
|
||||
);
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_array_layout_meta().resolve(&[Some(&layout)]));
|
||||
@@ -332,7 +333,7 @@ mod test {
|
||||
transform: local,
|
||||
};
|
||||
|
||||
let mut node = RepeatRadialNode::new(RecordSource::new(content, &layout, &layout), ValueNode(90.0f64), ValueNode(2.0f64), ValueNode(4u32), &layout);
|
||||
let mut node = RepeatRadialNode::new(RecordSource::new(content, &layout, &layout), ValueSource::new(90.0f64), ValueSource::new(2.0f64), ValueSource::new(4u32), &layout);
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_radial_layout_meta().resolve(&[Some(&layout)]));
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(4)));
|
||||
|
||||
@@ -371,7 +372,7 @@ mod test {
|
||||
let content_layout = transform_layout();
|
||||
let content = PositionProbe { layout: content_layout.clone() };
|
||||
|
||||
let mut node = RepeatOnPointsNode::new(RecordSource::new(content, &content_layout, &content_layout), points, ValueNode(false), &content_layout);
|
||||
let mut node = RepeatOnPointsNode::new(RecordSource::new(content, &content_layout, &content_layout), points, ValueSource::new(false), &content_layout);
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_on_points_layout_meta().resolve(&[Some(&content_layout)]));
|
||||
let leveled = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(leveled.depth, 1);
|
||||
@@ -407,7 +408,7 @@ mod test {
|
||||
let content_layout = transform_layout();
|
||||
let content = PositionProbe { layout: content_layout.clone() };
|
||||
|
||||
let mut node = RepeatOnPointsNode::new(RecordSource::new(content, &content_layout, &content_layout), points, ValueNode(true), &content_layout);
|
||||
let mut node = RepeatOnPointsNode::new(RecordSource::new(content, &content_layout, &content_layout), points, ValueSource::new(true), &content_layout);
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_on_points_layout_meta().resolve(&[Some(&content_layout)]));
|
||||
|
||||
let mut expected = positions.clone();
|
||||
|
||||
Reference in New Issue
Block a user