mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Pass frame space by argument and delete the record stack
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::{FrameClaim, LevelStatus, OwnedRecord, Served, copy_record_bytes};
|
||||
use core_types::record::{FrameClaim, LevelStatus, MaterializedSpan, OwnedRecord, Served, copy_record_bytes};
|
||||
use core_types::registry::cache_key;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
@@ -14,9 +14,9 @@ use std::sync::Mutex;
|
||||
#[derive(Debug)]
|
||||
pub struct MemoLevel {
|
||||
key: u64,
|
||||
generation: u64,
|
||||
frames: usize,
|
||||
stride: usize,
|
||||
/// The arena region the level materialized into, resolvable only while its
|
||||
/// generation is live.
|
||||
span: Option<MaterializedSpan>,
|
||||
lanes: Vec<OwnedRecord>,
|
||||
finality: Finality,
|
||||
}
|
||||
@@ -33,7 +33,7 @@ fn memoize<'e, 'l>(
|
||||
ctx: impl Ctx + CacheHash + DeriveCtx + ExtractArena<'e> + ModifyIndex + Copy,
|
||||
#[data] cache: Arc<Mutex<Option<MemoLevel>>>,
|
||||
content: impl Node<Context<'_>>,
|
||||
slot: FrameClaim<'l>,
|
||||
slot: FrameClaim<'e, '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,
|
||||
@@ -57,15 +57,17 @@ fn memoize<'e, 'l>(
|
||||
};
|
||||
// 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>| {
|
||||
let serve = |entry: &MemoLevel, mut slot: FrameClaim<'e, 'l>| {
|
||||
if lane >= entry.lanes.len() {
|
||||
// The cached level ends here; the past-end signal serves drains.
|
||||
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.
|
||||
unsafe { slot.fill_copy((entry.frames + lane * entry.stride) as *const u8) };
|
||||
if let Some(span) = entry.span
|
||||
&& let Some(src) = span.lane(ctx.arena(), lane, content.layout())
|
||||
{
|
||||
// SAFETY: the span resolved in generation, so the lane is live and
|
||||
// immutable at the layout it was materialized under.
|
||||
unsafe { slot.fill_copy(src) };
|
||||
// SAFETY: the copy images a complete record of this layout.
|
||||
return finalized(unsafe { slot.finish_served() }, &entry.finality);
|
||||
}
|
||||
@@ -88,12 +90,7 @@ fn memoize<'e, 'l>(
|
||||
let lanes: Vec<OwnedRecord> = (0..batch.len()).map(|index| unsafe { OwnedRecord::copy_out(layout, batch.get(index).rec()) }).collect();
|
||||
let entry = MemoLevel {
|
||||
key,
|
||||
generation: ctx.arena().generation(),
|
||||
frames: match batch.len() {
|
||||
0 => 0,
|
||||
_ => batch.get(0).rec().ptr() as usize,
|
||||
},
|
||||
stride: layout.lane_stride(),
|
||||
span: MaterializedSpan::of(&batch, ctx.arena()),
|
||||
lanes,
|
||||
finality,
|
||||
};
|
||||
@@ -119,9 +116,7 @@ fn memoize<'e, 'l>(
|
||||
key,
|
||||
// A scalar record replays from the deep copy; the value the serve
|
||||
// returned already lives in this frame.
|
||||
generation: u64::MAX,
|
||||
frames: 0,
|
||||
stride: 0,
|
||||
span: None,
|
||||
lanes: vec![copy],
|
||||
finality,
|
||||
});
|
||||
@@ -134,7 +129,7 @@ fn frame_memo<'e, 'l>(
|
||||
ctx: impl Ctx + CacheHash + ExtractArena<'e>,
|
||||
#[data] cell: ArenaCell<FrameTable<Box<[u8]>, 32>>,
|
||||
content: impl Node<Context<'_>>,
|
||||
frame: FrameClaim<'l>,
|
||||
frame: FrameClaim<'e, 'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
let arena = ctx.arena();
|
||||
let table = match cell.load(arena) {
|
||||
@@ -150,7 +145,7 @@ fn frame_memo<'e, 'l>(
|
||||
// SAFETY: published bytes are same-frame copies of this edge's records,
|
||||
// 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 {
|
||||
let revive = |mut frame: FrameClaim<'e, 'l>, bytes: &Box<[u8]>| unsafe {
|
||||
frame.fill_copy(bytes.as_ptr());
|
||||
frame.finish_served()
|
||||
};
|
||||
@@ -191,7 +186,7 @@ fn monitor<'e, 'l>(
|
||||
ctx: impl Ctx + DeriveCtx + ExtractAll + ExtractArena<'e> + ModifyIndex + Copy,
|
||||
#[data] io: MonitorValue,
|
||||
content: impl Node<Context<'_>>,
|
||||
slot: FrameClaim<'l>,
|
||||
slot: FrameClaim<'e, 'l>,
|
||||
) -> GPoll<Served<'e>> {
|
||||
if ctx.index() == 0 {
|
||||
*io.lock().unwrap() = Some(CtxSnapshot::capture(ctx));
|
||||
@@ -233,10 +228,6 @@ mod tests {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -249,6 +240,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn monitor_serialize_recreates_the_value_from_its_snapshot() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -260,14 +252,14 @@ mod tests {
|
||||
assert!(handle.serialize().is_none(), "no snapshot before the first eval");
|
||||
|
||||
let edge = handle.duplicate().downcast_record::<u32>().unwrap();
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
|
||||
let io = handle.serialize().expect("the eval landed a snapshot");
|
||||
let snapshot = io.downcast_ref::<CtxSnapshot>().expect("the monitor serializes its context snapshot");
|
||||
let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains");
|
||||
let GPoll::Final(served) = core_types::record::capture(&edge, &ctx) else {
|
||||
let GPoll::Final(served) = core_types::record::capture(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(served.element::<u32>(), 11);
|
||||
@@ -275,6 +267,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_leveled_monitor_recreates_the_whole_extent() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 12).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -286,14 +279,14 @@ 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(_) = core_types::record::serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(_) = core_types::record::serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
|
||||
let io = handle.serialize().expect("the eval landed a snapshot");
|
||||
let snapshot = io.downcast_ref::<CtxSnapshot>().expect("the monitor serializes its context snapshot");
|
||||
let ctx = snapshot.rehydrate(&scope).expect("the arena holds the chains");
|
||||
let LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&edge, &ctx, &arena) else {
|
||||
let LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&edge, &ctx, &arena, &frames) else {
|
||||
panic!("expected a materialized level");
|
||||
};
|
||||
assert_eq!(batch.len(), 3, "the recreation holds the whole extent, not the addressed lane");
|
||||
@@ -304,8 +297,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn memo_copy_out_consults_the_deep_element_clone() {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(dyn_any::DynAny)]
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
#[derive(Clone, Debug, PartialEq, dyn_any::DynAny)]
|
||||
struct Payload(String, u32);
|
||||
unsafe fn deep(ptr: *const u8) -> Box<dyn std::any::Any + Send + Sync> {
|
||||
let value = unsafe { core_types::record::borrow_element::<Payload>(core_types::record::Rec::new(ptr)) };
|
||||
@@ -326,12 +319,17 @@ mod tests {
|
||||
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");
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(Payload("deep".to_string(), 2)), "the hit replays through both halves of the deep glue");
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Final(Payload("deep".to_string(), 0)), "the miss serves the live value");
|
||||
assert_eq!(
|
||||
memoized.eval(&ctx, &frames),
|
||||
GPoll::Final(Payload("deep".to_string(), 2)),
|
||||
"the hit replays through both halves of the deep glue"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memoize_caches_across_evals() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -341,12 +339,13 @@ mod tests {
|
||||
let memoized = MemoizeNode::new(counting(), &layout);
|
||||
let memoized = core_types::record::RecordExtract::<u32, _>::new(memoized, &layout);
|
||||
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Final(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_invalidates_on_generation_bump() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let source: SourceId = 7;
|
||||
let before = [(source, 1)];
|
||||
@@ -358,13 +357,14 @@ mod tests {
|
||||
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));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before)), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_after)), GPoll::Final(2));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before), &frames), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_before), &frames), GPoll::Final(1));
|
||||
assert_eq!(memoized.eval(&ContextImpl::root(&scope_after), &frames), GPoll::Final(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memo_replays_partiality_on_hit() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -374,12 +374,13 @@ mod tests {
|
||||
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));
|
||||
assert_eq!(memoized.eval(&ctx), GPoll::Partial(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Partial(1));
|
||||
assert_eq!(memoized.eval(&ctx, &frames), GPoll::Partial(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memoized_edges_stack_and_rewire() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -391,12 +392,13 @@ mod tests {
|
||||
let stacked = MemoizeNode::new(memoized.downcast_record::<u32>().unwrap(), &layout);
|
||||
let stacked = core_types::record::RecordExtract::<u32, _>::new(stacked, &layout);
|
||||
|
||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
|
||||
assert_eq!(stacked.eval(&ctx, &frames), GPoll::Final(1));
|
||||
assert_eq!(stacked.eval(&ctx, &frames), GPoll::Final(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_memo_shares_one_record_copy_per_frame() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(4096).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -405,10 +407,10 @@ mod tests {
|
||||
let layout = element_layout::<String>();
|
||||
let memo = FrameMemoNode::new(lifted::<String>("lent out".to_string()), &layout);
|
||||
|
||||
let GPoll::Final(first) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
let GPoll::Final(first) = core_types::record::serve_edge(&memo, &ctx, &frames) else {
|
||||
panic!("the miss must fill the frame table");
|
||||
};
|
||||
let GPoll::Final(second) = core_types::record::serve_edge(&memo, &ctx) else {
|
||||
let GPoll::Final(second) = core_types::record::serve_edge(&memo, &ctx, &frames) else {
|
||||
panic!("the hit must revive the published record");
|
||||
};
|
||||
let first: &String = unsafe { core_types::record::borrow_element(layout.rec(&first)) };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -225,7 +225,7 @@ mod tests {
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{self, FrameClaim, Layout, RecordSource, Served, stack};
|
||||
use core_types::record::{self, FrameClaim, Layout, RecordSource, Served};
|
||||
use core_types::value::ValueSource;
|
||||
|
||||
struct GraphicSource {
|
||||
@@ -234,20 +234,22 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for GraphicSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, '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, ExtractArena::arena(input));
|
||||
frame.element(graphic.clone());
|
||||
frame.attr::<Transform>(*transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(graphic.clone(), arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<Transform>(&mut frame, &self.layout, *transform);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -259,9 +261,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a field at the layout's resolved offset, the wiring-proven pairing
|
||||
/// a generated node performs.
|
||||
fn write_field_at<T: Copy + 'static>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, name: &str, level: u8, value: T) {
|
||||
let field = layout
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| field.name == name && field.level == level)
|
||||
.expect("the layout carries the written field");
|
||||
assert_eq!(field.type_id, std::any::TypeId::of::<T>(), "the field was declared at this value type");
|
||||
// SAFETY: the offset is this layout's own, at the field's declared type.
|
||||
unsafe { frame.attr_at(field.offset, value) };
|
||||
}
|
||||
|
||||
/// [`write_field_at`] for a census marker at level 0.
|
||||
fn write_attr_at<A: core_types::attribute::Attribute>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, value: A::Value<'static>)
|
||||
where
|
||||
A::Value<'static>: Copy + 'static,
|
||||
{
|
||||
write_field_at(frame, layout, A::NAME, 0, value);
|
||||
}
|
||||
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)
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
fn install<N: Node<ContextImpl<'static>>>(mut node: N, meta: record::LayoutMeta, inputs: &[Option<&Layout>]) -> N {
|
||||
@@ -294,7 +315,7 @@ mod tests {
|
||||
Graphic::Text(label.to_string())
|
||||
}
|
||||
|
||||
fn group(children: Vec<(Graphic<'static>, DAffine2)>) -> Graphic {
|
||||
fn group(children: Vec<(Graphic<'static>, DAffine2)>) -> Graphic<'static> {
|
||||
let mut list = List::new();
|
||||
for (index, (child, transform)) in children.into_iter().enumerate() {
|
||||
list.push(Item::new_from_element(child));
|
||||
@@ -362,7 +383,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex + core_types::ExtractVarArgs> Node<C> for PerRowSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, 'l>) -> GPoll<Served<'e>>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'e Arena>,
|
||||
{
|
||||
@@ -372,15 +393,17 @@ mod tests {
|
||||
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, ExtractArena::arena(input));
|
||||
frame.element(graphic);
|
||||
frame.attr::<Transform>(translated);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(graphic, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<Transform>(&mut frame, &self.layout, translated);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -415,6 +438,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn map_scans_ragged_rows() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -441,12 +465,12 @@ mod tests {
|
||||
assert_eq!(out.depth, 2);
|
||||
// The extent-fn-less levels report a lower bound; addressing below
|
||||
// proves the lanes are all reachable regardless.
|
||||
assert_eq!(node.extent_at(&ctx, 1), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(node.extent_at(&ctx, 1, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -457,6 +481,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flat_map_matches_flatten_of_map() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -504,18 +529,18 @@ mod tests {
|
||||
assert_eq!(composed_out.depth, 1);
|
||||
// Both spellings report the same lower bound; the lane loop below is
|
||||
// the law.
|
||||
assert_eq!(flat.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(composed.extent_at(&ctx, 0), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(flat.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
assert_eq!(composed.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::AtLeast(0)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, &(label, x)) in RAGGED_FLAT.iter().enumerate() {
|
||||
let scoped = ctx.promoted(&head, lane as u64);
|
||||
let GPoll::Final(direct) = record::capture(&flat, &scoped) else {
|
||||
let GPoll::Final(direct) = record::capture(&flat, &scoped, &frames) else {
|
||||
panic!("expected a final record from flat_map");
|
||||
};
|
||||
let direct_label = text_of(&direct.element::<Graphic>()).to_string();
|
||||
let direct_x: DAffine2 = direct.attr::<Transform>();
|
||||
let GPoll::Final(value) = record::capture(&composed, &scoped) else {
|
||||
let GPoll::Final(value) = record::capture(&composed, &scoped, &frames) else {
|
||||
panic!("expected a final record from flatten(map)");
|
||||
};
|
||||
assert_eq!(text_of(&value.element::<Graphic>()), direct_label, "lane {lane}");
|
||||
@@ -540,6 +565,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flat_map_batch_matches_per_lane_eval() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -567,14 +593,14 @@ mod tests {
|
||||
let scoped = ctx.promoted(&head, 0);
|
||||
|
||||
let mut scratch = vec![std::mem::MaybeUninit::<u64>::uninit(); 5 * out.lane_stride() / 8];
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..5, Some(&mut scratch)) else {
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..5, Some(&mut scratch), &frames) else {
|
||||
panic!("expected a filled batch");
|
||||
};
|
||||
let batch = batch.into_shared();
|
||||
assert_eq!(batch.len(), 5);
|
||||
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
|
||||
for lane in 0..5 {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let single = text_of(&record.element::<Graphic>()).to_string();
|
||||
@@ -587,6 +613,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_expands_one_level() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -596,13 +623,13 @@ mod tests {
|
||||
let node = build!(layout, fixture_rows(), false);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(out.depth, 1);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
// Lane 2 is the unexpanded subgroup H, riding as a leaf at G's depth.
|
||||
let expected: [(&str, f64); 2] = [("a", 1.), ("b", 20.5)];
|
||||
for (lane, &(label, x)) in expected.iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -610,7 +637,7 @@ mod tests {
|
||||
assert_eq!(transform.translation.x, x, "lane {lane}");
|
||||
}
|
||||
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, 2)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, 2), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Graphic(children) = record.element::<Graphic>() else {
|
||||
@@ -629,13 +656,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_wire_materializes_into_a_group_for_the_renderer() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
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![text("a"), text("b")]);
|
||||
match graphic_types::boundary::materialize_group(&source, &ctx, &arena) {
|
||||
match graphic_types::boundary::materialize_group(&source, &ctx, &arena, &frames) {
|
||||
graphic_types::boundary::LevelGroup::Group(group, _) => {
|
||||
let list = graphic_types::graphic::group_to_legacy_list(&group);
|
||||
assert_eq!(list.len(), 2);
|
||||
@@ -646,6 +674,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_level_batch_converts_to_its_legacy_list() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -653,7 +682,7 @@ mod tests {
|
||||
|
||||
let source = core_types::value::LeveledValueSource::new(vec![1.5f64, 2.5]);
|
||||
let layout = Node::<ContextImpl>::layout(&source).clone();
|
||||
let record::LevelStatus::Batch(batch, _) = record::materialize_level(&source, &ctx, &arena) else {
|
||||
let record::LevelStatus::Batch(batch, _) = record::materialize_level(&source, &ctx, &arena, &frames) else {
|
||||
panic!("expected a batch");
|
||||
};
|
||||
let legacy = graphic_types::boundary::batch_to_legacy(&layout, batch, &arena).expect("f64 is in the legacy vocabulary");
|
||||
@@ -665,6 +694,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn wrap_collects_the_level_into_a_group() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -679,10 +709,10 @@ mod tests {
|
||||
);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(out.depth, 1);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(1)), "the group is the level's single lane");
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(1)), "the group is the level's single lane");
|
||||
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(out.rec(&value)) }) else {
|
||||
@@ -702,6 +732,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_group_element_deep_copies_to_its_owned_form_and_replays() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -717,7 +748,7 @@ mod tests {
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let copy = unsafe { (out.element.clone_out)(out.rec(&value).ptr()) };
|
||||
@@ -742,25 +773,28 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn colors_fold_into_evenly_spaced_stops() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
struct ColorSource {
|
||||
layout: Layout,
|
||||
colors: Vec<Color>,
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for ColorSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, '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, ExtractArena::arena(input));
|
||||
frame.element(color);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(color, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -782,7 +816,7 @@ mod tests {
|
||||
let build = |colors: Vec<Color>| install_flip(ToGradientNode::new(RecordSource::new(ColorSource { layout: layout.clone(), colors }, &layout, &layout), &layout), &out);
|
||||
let stops_of = |colors: Vec<Color>| {
|
||||
let node = build(colors);
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
record.element::<GradientStops>()
|
||||
@@ -801,6 +835,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_group_converts_to_its_legacy_list() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -815,7 +850,7 @@ mod tests {
|
||||
);
|
||||
let out = Node::<ContextImpl>::layout(&node).clone();
|
||||
let head = ctx.index_head();
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0)) else {
|
||||
let GPoll::Final(value) = record::serve_edge(&node, &ctx.promoted(&head, 0), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let Graphic::Group(group) = (unsafe { record::borrow_element::<Graphic>(out.rec(&value)) }) else {
|
||||
@@ -832,6 +867,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_reverses_wrap() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -849,8 +885,8 @@ mod tests {
|
||||
let group = {
|
||||
// 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) = record::serve_edge(&wrapped, &ctx.promoted(&head, 0)) else {
|
||||
let scope = frames.scope();
|
||||
let GPoll::Final(value) = record::serve_edge(&wrapped, &ctx.promoted(&head, 0), &scope) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let group = unsafe { record::borrow_element::<Graphic>(wrap_out.rec(&value)) }.clone();
|
||||
@@ -860,11 +896,11 @@ mod tests {
|
||||
// One row holding the wrapped group flattens back to the lanes, the
|
||||
// group's identity transform composed onto each child's.
|
||||
let node = build!(layout, vec![(group, DAffine2::IDENTITY)], false);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(2)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(2)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (lane, &(label, x)) in [("a", 1.), ("b", 2.)].iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -875,6 +911,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_fully_composes_the_path() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -884,12 +921,12 @@ mod tests {
|
||||
rows.push((group(vec![]), translation(9.)));
|
||||
let layout = graphic_layout();
|
||||
let node = build!(layout, rows, true);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves");
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)), "the empty group contributes no leaves");
|
||||
|
||||
let head = ctx.index_head();
|
||||
let expected: [(&str, f64); 3] = [("a", 1.), ("b", 20.5), ("c", 4300.5)];
|
||||
for (lane, &(label, x)) in expected.iter().enumerate() {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(text_of(&record.element::<Graphic>()), label, "lane {lane}");
|
||||
@@ -900,6 +937,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn flatten_batch_matches_per_lane_eval() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -912,14 +950,14 @@ mod tests {
|
||||
let scoped = ctx.promoted(&head, 0);
|
||||
|
||||
let mut scratch = vec![std::mem::MaybeUninit::<u64>::uninit(); 3 * out.lane_stride() / 8];
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..3, Some(&mut scratch)) else {
|
||||
let core_types::node::BatchStatus::Filled(batch, ..) = node.eval_batch(&scoped, 0..3, Some(&mut scratch), &frames) else {
|
||||
panic!("expected a filled batch");
|
||||
};
|
||||
let batch = batch.into_shared();
|
||||
assert_eq!(batch.len(), 3);
|
||||
let offset = out.offset_of(<Transform as AttributeMarker>::NAME, 0).unwrap();
|
||||
for lane in 0..3 {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64)) else {
|
||||
let GPoll::Final(record) = record::capture(&node, &ctx.promoted(&head, lane as u64), &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let single = text_of(&record.element::<Graphic>()).to_string();
|
||||
|
||||
@@ -261,10 +261,7 @@ mod tests {
|
||||
|
||||
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 frames = core_types::record::test_frames(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.
|
||||
@@ -277,7 +274,7 @@ mod tests {
|
||||
lane_invariant: u32::MAX,
|
||||
},
|
||||
);
|
||||
let GPoll::Final(result) = core_types::record::serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(result) = core_types::record::serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("create_context must complete synchronously");
|
||||
};
|
||||
let output: &RenderOutput = unsafe { core_types::record::borrow_element(layout.rec(&result)) };
|
||||
|
||||
@@ -1014,7 +1014,7 @@ mod graphene_test {
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::gpoll::{Finality, GPoll};
|
||||
use core_types::node::{BatchStatus, Node};
|
||||
use core_types::record::{Layout, LiftedSource, RecordValue, serve_edge, stack};
|
||||
use core_types::record::{Layout, LiftedSource, RecordValue, serve_edge};
|
||||
use core_types::registry::{ErasedRecordNode, construct};
|
||||
use core_types::value::record_value_edge;
|
||||
use std::mem::MaybeUninit;
|
||||
@@ -1023,9 +1023,9 @@ mod graphene_test {
|
||||
EvalScope::new(None, None, None, &[], arena)
|
||||
}
|
||||
|
||||
fn reserve_for(layouts: &[&Layout]) {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { stack::reserve(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12)); } }
|
||||
fn frames_for(layouts: &[&Layout]) -> core_types::record::Frames<'static> {
|
||||
core_types::record::test_frames(layouts.iter().map(|layout| layout.frame_bytes()).sum::<usize>().max(1 << 12))
|
||||
}
|
||||
|
||||
/// Lifts a plain-element test source onto a record wire, returned beside its
|
||||
/// element-only layout for the generated node's constructor.
|
||||
@@ -1071,9 +1071,9 @@ mod graphene_test {
|
||||
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 frames = frames_for(&[&la, &lb, &out]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 3.0);
|
||||
@@ -1089,12 +1089,12 @@ mod graphene_test {
|
||||
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]);
|
||||
let frames = frames_for(&[&li, &ls, &out]);
|
||||
|
||||
let erased: Box<ErasedRecordNode> = Box::new(node);
|
||||
// One u64 word per lane at the element-only layout.
|
||||
let mut scratch = [const { MaybeUninit::uninit() }; 4];
|
||||
let status = erased.eval_batch(&ctx, 2..6, Some(&mut scratch));
|
||||
let status = erased.eval_batch(&ctx, 2..6, Some(&mut scratch), &frames);
|
||||
let BatchStatus::Filled(batch, finality, _) = status else {
|
||||
panic!("expected filled, got {status:?}");
|
||||
};
|
||||
@@ -1120,9 +1120,9 @@ mod graphene_test {
|
||||
lane_invariant: u32::MAX,
|
||||
});
|
||||
let edge = wired.downcast_record::<bool>().unwrap();
|
||||
reserve_for(&[&layout]);
|
||||
let frames = frames_for(&[&layout]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert!(element::<bool>(&layout, &value));
|
||||
@@ -1166,9 +1166,9 @@ mod graphene_test {
|
||||
lane_invariant: u32::MAX,
|
||||
});
|
||||
let edge = wired.downcast_record::<f64>().unwrap();
|
||||
reserve_for(&[&layout]);
|
||||
let frames = frames_for(&[&layout]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&edge, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&layout, &value), 4.0);
|
||||
@@ -1213,9 +1213,9 @@ mod graphene_test {
|
||||
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 frames = frames_for(&[&lc, <, &lf, &out]);
|
||||
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Final(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 1.0);
|
||||
@@ -1239,10 +1239,10 @@ mod graphene_test {
|
||||
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]);
|
||||
let frames = frames_for(&[&lc1, &lp1, &lpa1, &lc2, &lp2, &lpa2, &out]);
|
||||
|
||||
assert!(matches!(serve_edge(&pending, &ctx), GPoll::Pending));
|
||||
let GPoll::Partial(value) = serve_edge(&partial, &ctx) else {
|
||||
assert!(matches!(serve_edge(&pending, &ctx, &frames), GPoll::Pending));
|
||||
let GPoll::Partial(value) = serve_edge(&partial, &ctx, &frames) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 7.0);
|
||||
@@ -1260,9 +1260,9 @@ mod graphene_test {
|
||||
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 frames = frames_for(&[&lc, <, &lf, &out]);
|
||||
|
||||
let GPoll::Partial(value) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Partial(value) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &value), 1.0);
|
||||
@@ -1278,9 +1278,9 @@ mod graphene_test {
|
||||
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 frames = frames_for(&[&lfb, &ls, &out]);
|
||||
|
||||
let GPoll::Fallback(boxed) = serve_edge(&graph, &ctx) else {
|
||||
let GPoll::Fallback(boxed) = serve_edge(&graph, &ctx, &frames) else {
|
||||
panic!("fallback must propagate with the computed stand-in");
|
||||
};
|
||||
assert_eq!(element::<f64>(&out, &boxed.0), 5.0);
|
||||
|
||||
@@ -70,8 +70,8 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_image_color_palette() {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { core_types::record::stack::reserve(1 << 16); } let arena = core_types::arena::Arena::new(1 << 22).unwrap();
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = core_types::arena::Arena::new(1 << 22).unwrap();
|
||||
let generations = [];
|
||||
let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena);
|
||||
let ctx = core_types::context::ContextImpl::root(&scope);
|
||||
@@ -83,7 +83,7 @@ mod test {
|
||||
base64_string: None,
|
||||
});
|
||||
let source = core_types::value::LeveledValueSource::new(vec![raster]);
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena) else {
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena, &frames) else {
|
||||
panic!("materialize failed")
|
||||
};
|
||||
let image = unsafe { core_types::node::List::<Raster<CPU>>::new(batch) };
|
||||
|
||||
@@ -183,7 +183,7 @@ mod test {
|
||||
use core_types::arena::Arena;
|
||||
use core_types::context::{ContextImpl, EvalScope, ExtractArena};
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{FieldWrite, FrameBuilder, FrameClaim, Layout, RecordSource, Served, capture, element_write, stack};
|
||||
use core_types::record::{FieldWrite, FrameClaim, Layout, RecordSource, Served, capture, element_write};
|
||||
use core_types::value::ValueSource;
|
||||
use vector_types::subpath::Subpath;
|
||||
|
||||
@@ -194,16 +194,18 @@ mod test {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for TransformSource {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, '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() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(self.element, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<TransformAttr>(&mut frame, &self.layout, self.transform);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
@@ -211,9 +213,28 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a field at the layout's resolved offset, the wiring-proven pairing
|
||||
/// a generated node performs.
|
||||
fn write_field_at<T: Copy + 'static>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, name: &str, level: u8, value: T) {
|
||||
let field = layout
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| field.name == name && field.level == level)
|
||||
.expect("the layout carries the written field");
|
||||
assert_eq!(field.type_id, std::any::TypeId::of::<T>(), "the field was declared at this value type");
|
||||
// SAFETY: the offset is this layout's own, at the field's declared type.
|
||||
unsafe { frame.attr_at(field.offset, value) };
|
||||
}
|
||||
|
||||
/// [`write_field_at`] for a census marker at level 0.
|
||||
fn write_attr_at<A: core_types::attribute::Attribute>(frame: &mut FrameClaim<'_, '_>, layout: &Layout, value: A::Value<'static>)
|
||||
where
|
||||
A::Value<'static>: Copy + 'static,
|
||||
{
|
||||
write_field_at(frame, layout, A::NAME, 0, value);
|
||||
}
|
||||
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 << 12); } EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
struct VectorRows {
|
||||
@@ -222,20 +243,22 @@ mod test {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex> Node<C> for VectorRows {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, '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, ExtractArena::arena(input));
|
||||
frame.element(vector.clone());
|
||||
frame.attr::<TransformAttr>(*transform);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(vector.clone(), arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<TransformAttr>(&mut frame, &self.layout, *transform);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8) -> GPoll<Extent>
|
||||
fn extent_at<'x>(&self, _input: &C, _level: u8, _frames: &core_types::record::Frames<'x>) -> GPoll<Extent>
|
||||
where
|
||||
C: ExtractArena<ArenaRef = &'x Arena>,
|
||||
{
|
||||
@@ -256,17 +279,19 @@ mod test {
|
||||
}
|
||||
|
||||
impl<C: ExtractIndex + core_types::context::ExtractPosition> Node<C> for PositionProbe {
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'l>) -> GPoll<Served<'e>>
|
||||
fn serve<'e, 'l>(&self, input: &C, slot: FrameClaim<'e, '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, ExtractArena::arena(input));
|
||||
frame.element(position.x);
|
||||
frame.attr::<TransformAttr>(DAffine2::IDENTITY);
|
||||
let Some(value) = frame.finish() else { return GPoll::arena_exhausted() };
|
||||
// SAFETY: the builder served a record of this node's layout.
|
||||
GPoll::Final(unsafe { slot.forward(&value) })
|
||||
let mut frame = slot;
|
||||
let arena = ExtractArena::arena(input);
|
||||
if frame.element(position.x, arena).is_none() {
|
||||
return GPoll::arena_exhausted();
|
||||
}
|
||||
write_attr_at::<TransformAttr>(&mut frame, &self.layout, DAffine2::IDENTITY);
|
||||
// SAFETY: the writes above complete the record of this layout.
|
||||
GPoll::Final(unsafe { frame.finish_served() })
|
||||
}
|
||||
|
||||
fn layout(&self) -> &Layout {
|
||||
@@ -280,6 +305,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_array_composes_the_step_onto_each_copys_transform() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -302,12 +328,12 @@ mod test {
|
||||
Node::<ContextImpl>::set_layout(&mut node, repeat_array_layout_meta().resolve(&[Some(&layout)]));
|
||||
let leveled = Node::<ContextImpl>::layout(&node).clone();
|
||||
assert_eq!(leveled.depth, 1, "the IList return pushed one rank level above the content");
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(3)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(3)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for copy in 0..3u64 {
|
||||
let lane = ctx.promoted(&head, copy);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(record.element::<f64>(), 7.);
|
||||
@@ -320,6 +346,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_radial_rotates_each_copy_around_the_center() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -335,12 +362,12 @@ mod test {
|
||||
|
||||
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)));
|
||||
assert_eq!(node.extent_at(&ctx, 0, &frames.reborrow()), GPoll::Final(Extent::Exactly(4)));
|
||||
|
||||
let head = ctx.index_head();
|
||||
for copy in 0..4u64 {
|
||||
let lane = ctx.promoted(&head, copy);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(record.element::<f64>(), 7.);
|
||||
@@ -354,6 +381,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_on_points_lands_each_copy_on_its_transformed_point() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -376,14 +404,18 @@ mod test {
|
||||
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);
|
||||
assert_eq!(node.extent_at(&ctx, 0), GPoll::Final(Extent::Exactly(5)), "the pushed level flattens both rows' points");
|
||||
assert_eq!(
|
||||
node.extent_at(&ctx, 0, &frames.reborrow()),
|
||||
GPoll::Final(Extent::Exactly(5)),
|
||||
"the pushed level flattens both rows' points"
|
||||
);
|
||||
|
||||
let expected: Vec<DVec2> = row0.iter().map(|&point| row0_transform.transform_point2(point)).chain(row1.iter().copied()).collect();
|
||||
|
||||
let head = ctx.index_head();
|
||||
for (flat, &point) in expected.iter().enumerate() {
|
||||
let lane = ctx.promoted(&head, flat as u64);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
// The content saw the pushed position, and the output transform lands on it.
|
||||
@@ -395,6 +427,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn repeat_on_points_reverse_flips_each_rows_points() {
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = Arena::new(1 << 16).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
@@ -416,7 +449,7 @@ mod test {
|
||||
let head = ctx.index_head();
|
||||
for (flat, &point) in expected.iter().enumerate() {
|
||||
let lane = ctx.promoted(&head, flat as u64);
|
||||
let GPoll::Final(record) = capture(&node, &lane) else {
|
||||
let GPoll::Final(record) = capture(&node, &lane, &frames) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let composed: DAffine2 = record.attr::<TransformAttr>();
|
||||
|
||||
@@ -3870,8 +3870,8 @@ mod test {
|
||||
}
|
||||
#[test]
|
||||
fn path_length() {
|
||||
// SAFETY: between evaluations, nothing served on the stack is live.
|
||||
unsafe { core_types::record::stack::reserve(1 << 16); } let arena = core_types::arena::Arena::new(1 << 20).unwrap();
|
||||
let frames = core_types::record::test_frames(1 << 16);
|
||||
let arena = core_types::arena::Arena::new(1 << 20).unwrap();
|
||||
let generations = [];
|
||||
let scope = core_types::context::EvalScope::new(None, None, None, &generations, &arena);
|
||||
let ctx = core_types::context::ContextImpl::root(&scope);
|
||||
@@ -3882,7 +3882,7 @@ mod test {
|
||||
// Element-only lanes read identity lane transforms; the transform term
|
||||
// rides the demo gate.
|
||||
let source = core_types::value::LeveledValueSource::new(vec![row; 5]);
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena) else {
|
||||
let core_types::record::LevelStatus::Batch(batch, _) = core_types::record::materialize_level(&source, &ctx, &arena, &frames) else {
|
||||
panic!("materialize failed")
|
||||
};
|
||||
let list = unsafe { core_types::node::List::<Vector>::new(batch) };
|
||||
|
||||
Reference in New Issue
Block a user