Add the leveled boundary helpers and the deep group element copy

This commit is contained in:
Dennis Kobert
2026-08-22 11:48:26 +00:00
parent 8eaa541c8c
commit 5821816228
10 changed files with 399 additions and 17 deletions

View File

@@ -1,11 +1,11 @@
use core_types::arena::ArenaCell;
use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll};
use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll, InjectIndex};
use core_types::extent::{ExtentIn, LevelIn};
use core_types::frame_table::{FrameTable, Lookup};
use core_types::gpoll::{Extent, Finality, GPoll};
use core_types::graphene_hash::CacheHash;
use core_types::memo::IORecord;
use core_types::record::{OwnedRecord, RecordCapture, RecordValue, copy_record_bytes, record_from_bytes};
use core_types::record::{LevelStatus, OwnedRecord, RecordCapture, RecordValue, copy_record_bytes, record_from_bytes};
use core_types::registry::cache_key;
use std::sync::Arc;
use std::sync::Mutex;
@@ -100,11 +100,24 @@ type MonitorValue = Arc<Mutex<Option<IORecord<CtxSnapshot, RecordCapture>>>>;
/// The Monitor node is used by the editor to access the data flowing through it.
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"))]
fn monitor<'e>(ctx: impl Ctx + DeriveCtx + ExtractAll + ExtractArena<'e>, #[data] io: MonitorValue, content: impl Node<Context<'_>, Output = RecordValue<'e>>) -> GPoll<RecordValue<'e>> {
fn monitor<'e>(
ctx: impl Ctx + DeriveCtx + ExtractAll + ExtractArena<'e> + InjectIndex + Copy,
#[data] io: MonitorValue,
content: impl Node<Context<'_>, Output = RecordValue<'e>>,
) -> GPoll<RecordValue<'e>> {
let result = content.eval(&ctx);
if let GPoll::Final(value) | GPoll::Partial(value) = &result {
// SAFETY: the value came from this edge, so it carries the edge's layout.
let captured = unsafe { RecordCapture::capture(content.layout(), content.layout().rec(value), ctx.arena()) };
let captured = match content.layout().depth {
// SAFETY: the value came from this edge, so it carries the edge's layout.
0 => unsafe { RecordCapture::capture(content.layout(), content.layout().rec(value), ctx.arena()) },
// A leveled wire captures its whole extent, not the one lane this
// context addresses.
_ => match content.materialize_level(ctx, ctx.arena()) {
// SAFETY: the batch came from this edge, so it carries the edge's layout.
LevelStatus::Batch(batch, _) => unsafe { RecordCapture::capture_level(content.layout(), batch, ctx.arena()) },
LevelStatus::Pending | LevelStatus::Error(_) => None,
},
};
*io.lock().unwrap() = captured.map(|output| IORecord {
input: CtxSnapshot::capture(ctx),
output,
@@ -194,6 +207,55 @@ mod tests {
assert_eq!(*element.downcast_ref::<u32>().unwrap(), 11);
}
#[test]
fn a_leveled_monitor_captures_the_whole_extent() {
let arena = Arena::new(1 << 12).unwrap();
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let source = core_types::value::LeveledValueSource::new(vec![10u32, 20, 30]);
let layout = Node::<ContextImpl>::layout(&source).clone();
let monitor = MonitorNode::new(source, &layout);
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 {
panic!("expected a final record");
};
let io = handle.serialize().expect("the eval landed a capture");
let io = io.downcast_ref::<IORecord<CtxSnapshot, RecordCapture>>().expect("the capture is the monitor io");
assert_eq!(io.output.lanes(), 3, "the capture holds the whole extent, not the addressed lane");
let batch = io.output.batch(&arena).expect("the capture lives in this generation");
let lanes = unsafe { core_types::node::List::<u32>::new(batch) };
let values: Vec<u32> = (0..lanes.len()).map(|lane| *lanes.element_ref(lane)).collect();
assert_eq!(values, vec![10, 20, 30]);
}
#[test]
fn memo_copy_out_consults_the_deep_element_clone() {
#[derive(Clone, Debug, PartialEq)]
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)) };
Box::new(Payload(value.0.clone(), value.1 + 1))
}
core_types::record::register_deep_element_clone::<Payload>(deep);
let arena = Arena::new(4096).unwrap();
let generations = [];
let scope = scope_fixture(&generations, &arena);
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 = 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(), 1)), "the hit replays the deep copy");
}
#[test]
fn memoize_caches_across_evals() {
let arena = Arena::new(1024).unwrap();

View File

@@ -614,6 +614,43 @@ mod tests {
unsafe { stack::rewind(mark) };
}
#[test]
fn a_wire_materializes_into_a_group_for_the_renderer() {
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) {
graphic_types::boundary::LevelGroup::Group(group, _) => {
let list = graphic_types::graphic::group_to_legacy_list(&group);
assert_eq!(list.len(), 2);
}
_ => panic!("expected a materialized group"),
}
}
#[test]
fn a_level_capture_converts_to_its_legacy_list() {
let arena = Arena::new(1 << 16).unwrap();
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let source = core_types::value::LeveledValueSource::new(vec![1.5f64, 2.5]);
let layout = Node::<ContextImpl>::layout(&source).clone();
let record::LevelStatus::Batch(batch, _) = record::materialize_level(&source, &ctx, &arena) else {
panic!("expected a batch");
};
let capture = unsafe { record::RecordCapture::capture_level(&layout, batch, &arena) }.expect("the capture parks in the arena");
let legacy = graphic_types::boundary::capture_to_legacy(&capture, &arena).expect("f64 is in the legacy vocabulary");
let list = legacy.downcast_ref::<List<f64>>().unwrap();
assert_eq!(list.len(), 2);
assert_eq!(list.element(0).copied(), Some(1.5));
assert_eq!(list.element(1).copied(), Some(2.5));
}
#[test]
fn wrap_collects_the_level_into_a_group() {
let arena = Arena::new(1 << 16).unwrap();
@@ -653,6 +690,35 @@ mod tests {
}
}
#[test]
fn a_group_element_deep_copies_to_its_legacy_form() {
let arena = Arena::new(1 << 16).unwrap();
let generations = [];
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let layout = graphic_layout();
let rows = vec![(text("a"), translation(1.)), (text("b"), translation(2.))];
let node = install(
WrapNode::new(RecordSource::new(GraphicSource { layout: layout.clone(), rows }, &layout, &layout), &layout),
wrap_layout_meta(),
&[Some(&layout)],
);
let out = Node::<ContextImpl>::layout(&node).clone();
let head = ctx.index_head();
let GPoll::Final(value) = node.eval(&ctx.promoted(&head, 0)) else {
panic!("expected a final record");
};
let copy = unsafe { (out.element.clone_out)(out.rec(&value).ptr()) };
let Graphic::Graphic(list) = *copy.downcast::<Graphic>().expect("the deep copy replays at the element's own type") else {
panic!("expected the legacy-converted form");
};
assert_eq!(list.len(), 2);
assert_eq!(text_of(list.element(0).unwrap()), "a");
assert_eq!(text_of(list.element(1).unwrap()), "b");
}
#[test]
fn colors_fold_into_evenly_spaced_stops() {
struct ColorSource {