mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Drop the list driver, make attribute values Copy over arena references, and land record-tier offset io on a per-thread record stack
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
//! Pilot record nodes exercising the macro's attribute io over materialized
|
||||
//! lists: `Attr<A>` reads, tuple writes, column carry, and the census
|
||||
//! defaults. These are the flat-wave law tests; the node forms are the
|
||||
//! production authoring surface, the list driver behind them is interim.
|
||||
//! Pilot record nodes exercising the macro's record-tier attribute io:
|
||||
//! offset reads and writes against record edges, the ElToken byte-carry for
|
||||
//! passthrough elements, and the `_: ()` no-carrier form. These are the
|
||||
//! flat-wave law tests; the node forms are the production authoring surface,
|
||||
//! and the wiring is by hand until the compiler pass constructs layouts.
|
||||
|
||||
use core_types::attribute::{Attr, Opacity};
|
||||
use core_types::gpoll::{GraphError, Interrupt};
|
||||
use core_types::context::ExtractArena;
|
||||
use core_types::gpoll::{ErrorKind, GraphError, Interrupt};
|
||||
use core_types::{Context, Ctx};
|
||||
|
||||
core_types::attribute! {
|
||||
/// Test-only measured length of an element.
|
||||
pub Length("length"): f64;
|
||||
/// Test-only label parked in the arena by its writer.
|
||||
pub Label("label"): &str;
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Test"))]
|
||||
@@ -40,6 +44,26 @@ fn scale(_: impl Ctx, element: f64, factor: &f64, opacity: Attr<Opacity>) -> (f6
|
||||
(element * *factor, Attr(*opacity))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Test"))]
|
||||
fn fade<T>(_: impl Ctx, element: T, factor: f64, opacity: Attr<Opacity>) -> (T, Attr<Opacity>) {
|
||||
(element, Attr(*opacity * factor))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Test"))]
|
||||
fn source_opacity(_: impl Ctx, _: (), element: f64, opacity: f64) -> (f64, Attr<Opacity>) {
|
||||
(element, Attr(opacity))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Test"))]
|
||||
fn label<'e>(ctx: impl Ctx + ExtractArena<'e>, element: f64, text: String, label: Attr<Label>) -> Result<(f64, Attr<'e, Label>), Interrupt> {
|
||||
let joined = format!("{}{text}", *label);
|
||||
let (parked, _) = ctx.arena().alloc(joined).ok_or(GraphError {
|
||||
kind: ErrorKind::ArenaExhausted,
|
||||
trace: Vec::new(),
|
||||
})?;
|
||||
Ok((element, Attr(parked.as_str())))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Test"))]
|
||||
fn pick<T>(ctx: impl Ctx, take_second: bool, first: impl Node<Context<'_>, Output = T>, second: impl Node<Context<'_>, Output = T>) -> Result<T, Interrupt> {
|
||||
if take_second { second.eval(ctx) } else { first.eval(ctx) }
|
||||
@@ -57,6 +81,7 @@ fn forward_record<T>(_: impl Ctx, element: T) -> T {
|
||||
element
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -65,8 +90,8 @@ mod tests {
|
||||
use core_types::attribute::Attribute as AttributeMarker;
|
||||
use core_types::context::{ContextImpl, EvalScope};
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::list::List;
|
||||
use core_types::node::Node;
|
||||
use core_types::record::{Layout, Rec, RecordSource, RecordValue, stack};
|
||||
|
||||
struct ValueNode<T>(T);
|
||||
|
||||
@@ -78,13 +103,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct PartialListNode(List<f64>);
|
||||
struct RecordSourceNode<E> {
|
||||
frame_bytes: usize,
|
||||
element: E,
|
||||
fields: Vec<(usize, f64)>,
|
||||
partial: bool,
|
||||
}
|
||||
|
||||
impl<Input> Node<Input> for PartialListNode {
|
||||
type Output = List<f64>;
|
||||
impl<'e, E: Copy> Node<ContextImpl<'e>> for RecordSourceNode<E> {
|
||||
type Output = RecordValue<'e>;
|
||||
|
||||
fn eval(&self, _input: &Input) -> GPoll<List<f64>> {
|
||||
GPoll::Partial(self.0.clone())
|
||||
fn eval(&self, _input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
let dst = stack::push(self.frame_bytes);
|
||||
let value = unsafe {
|
||||
dst.cast::<E>().write(self.element);
|
||||
for (offset, value) in &self.fields {
|
||||
dst.add(*offset).cast::<f64>().write(*value);
|
||||
}
|
||||
RecordValue::from_rec(Rec::new(dst))
|
||||
};
|
||||
stack::pop(dst);
|
||||
match self.partial {
|
||||
true => GPoll::Partial(value),
|
||||
false => GPoll::Final(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,12 +134,26 @@ mod tests {
|
||||
EvalScope::new(Some(0.5), None, None, generations, arena)
|
||||
}
|
||||
|
||||
fn elements(list: &List<f64>) -> Vec<f64> {
|
||||
list.iter_element_values().copied().collect()
|
||||
fn f64_layout(names: &[&'static str]) -> Layout {
|
||||
let writes: Vec<(&'static str, u8, usize, usize)> = names.iter().map(|name| (*name, 0, 8, 8)).collect();
|
||||
Layout::default().with_writes(0, (8, 8), &writes)
|
||||
}
|
||||
|
||||
fn column(list: &List<f64>, key: &str) -> Vec<f64> {
|
||||
list.iter_attribute_values::<f64>(key).unwrap().copied().collect()
|
||||
fn frame_bytes(layout: &Layout) -> usize {
|
||||
layout.size.next_multiple_of(8)
|
||||
}
|
||||
|
||||
fn reserve_for(layouts: &[&Layout]) {
|
||||
stack::reserve(layouts.iter().map(|layout| frame_bytes(layout)).sum());
|
||||
}
|
||||
|
||||
fn bare_source(layout: &Layout, element: f64) -> RecordSourceNode<f64> {
|
||||
RecordSourceNode {
|
||||
frame_bytes: frame_bytes(layout),
|
||||
element,
|
||||
fields: vec![],
|
||||
partial: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -107,15 +163,23 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let chain = MultiplyOpacityNode::new(
|
||||
MultiplyOpacityNode::new(ValueNode(List::from_element_values(vec![1., 2.])), ValueNode(0.5)),
|
||||
let source_layout = f64_layout(&[]);
|
||||
let modified = multiply_opacity_layout(&source_layout);
|
||||
let stacked = multiply_opacity_layout(&modified);
|
||||
reserve_for(&[&source_layout, &modified, &stacked]);
|
||||
|
||||
let chain = MultiplyOpacityNode::wire(
|
||||
MultiplyOpacityNode::wire(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout),
|
||||
ValueNode(0.5),
|
||||
&modified,
|
||||
);
|
||||
let GPoll::Final(list) = chain.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
assert_eq!(chain.layout(), Some(&stacked));
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(elements(&list), vec![1., 2.]);
|
||||
assert_eq!(column(&list, Opacity::NAME), vec![0.25, 0.25]);
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 2.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(stacked.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -125,27 +189,38 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let chain = MeasureNode::new(ValueNode(List::from_element_values(vec![-2., 3.])));
|
||||
let GPoll::Final(list) = chain.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
let source_layout = f64_layout(&[]);
|
||||
let measured = measure_layout(&source_layout);
|
||||
reserve_for(&[&source_layout, &measured]);
|
||||
|
||||
let chain = MeasureNode::wire(bare_source(&source_layout, -2.), &source_layout);
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(elements(&list), vec![-2., 3.]);
|
||||
assert_eq!(column(&list, Length::NAME), vec![2., 3.]);
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, -2.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elementwise_write_carries_unrelated_columns() {
|
||||
fn elementwise_write_carries_unrelated_fields() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let chain = MeasureNode::new(MultiplyOpacityNode::new(ValueNode(List::from_element_values(vec![-2., 3.])), ValueNode(0.5)));
|
||||
let GPoll::Final(list) = chain.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
let source_layout = f64_layout(&[]);
|
||||
let modified = multiply_opacity_layout(&source_layout);
|
||||
let measured = measure_layout(&modified);
|
||||
reserve_for(&[&source_layout, &modified, &measured]);
|
||||
|
||||
let chain = MeasureNode::wire(MultiplyOpacityNode::wire(bare_source(&source_layout, -2.), ValueNode(0.5), &source_layout), &modified);
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(column(&list, Opacity::NAME), vec![0.5, 0.5]);
|
||||
assert_eq!(column(&list, Length::NAME), vec![2., 3.]);
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||
assert_eq!(unsafe { rec.read::<f64>(measured.offset_of(Length::NAME, 0).unwrap()) }, 2.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -155,18 +230,83 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let bare = ShadeNode::new(ValueNode(List::from_element_values(vec![4., 6.])));
|
||||
let GPoll::Final(list) = bare.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
};
|
||||
assert_eq!(elements(&list), vec![4., 6.]);
|
||||
let source_layout = f64_layout(&[]);
|
||||
let modified = multiply_opacity_layout(&source_layout);
|
||||
let shaded = shade_layout(&modified);
|
||||
reserve_for(&[&source_layout, &modified, &shaded]);
|
||||
|
||||
let shaded = ShadeNode::new(MultiplyOpacityNode::new(ValueNode(List::from_element_values(vec![4., 6.])), ValueNode(0.5)));
|
||||
let GPoll::Final(list) = shaded.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
let bare = ShadeNode::wire(bare_source(&source_layout, 4.), &source_layout);
|
||||
let GPoll::Final(value) = bare.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(elements(&list), vec![2., 3.]);
|
||||
assert_eq!(column(&list, Opacity::NAME), vec![0.5, 0.5]);
|
||||
assert_eq!(unsafe { value.rec().element::<f64>() }, 4.);
|
||||
|
||||
let chain = ShadeNode::wire(MultiplyOpacityNode::wire(bare_source(&source_layout, 4.), ValueNode(0.5), &source_layout), &modified);
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 2.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(shaded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_passthrough_carries_any_element_type() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let f64_source = f64_layout(&[]);
|
||||
let f64_faded = fade_layout(&f64_source);
|
||||
let u32_source = Layout::default().with_writes(0, (4, 4), &[]);
|
||||
let u32_faded = fade_layout(&u32_source);
|
||||
reserve_for(&[&f64_source, &f64_faded, &u32_source, &u32_faded]);
|
||||
|
||||
let wide = FadeNode::wire(bare_source(&f64_source, 8.), ValueNode(0.5), &f64_source);
|
||||
let GPoll::Final(value) = wide.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 8.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(f64_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||
|
||||
let narrow = FadeNode::wire(
|
||||
RecordSourceNode {
|
||||
frame_bytes: frame_bytes(&u32_source),
|
||||
element: 7u32,
|
||||
fields: vec![],
|
||||
partial: false,
|
||||
},
|
||||
ValueNode(0.25),
|
||||
&u32_source,
|
||||
);
|
||||
let GPoll::Final(value) = narrow.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<u32>() }, 7);
|
||||
assert_eq!(unsafe { rec.read::<f64>(u32_faded.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_carrier_form_writes_a_fresh_record() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = source_opacity_layout();
|
||||
reserve_for(&[&layout]);
|
||||
|
||||
let node = SourceOpacityNode::wire(ValueNode(3.), ValueNode(0.25));
|
||||
assert_eq!(Node::<ContextImpl>::layout(&node), Some(&layout));
|
||||
let GPoll::Final(value) = node.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 3.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of(Opacity::NAME, 0).unwrap()) }, 0.25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -176,46 +316,48 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let chain = MultiplyOpacityNode::new(PartialListNode(List::from_element_values(vec![1.])), ValueNode(0.5));
|
||||
let GPoll::Partial(list) = chain.eval(&ctx) else {
|
||||
panic!("expected a partial list");
|
||||
let source_layout = f64_layout(&[]);
|
||||
let modified = multiply_opacity_layout(&source_layout);
|
||||
reserve_for(&[&source_layout, &modified]);
|
||||
|
||||
let chain = MultiplyOpacityNode::wire(
|
||||
RecordSourceNode {
|
||||
frame_bytes: frame_bytes(&source_layout),
|
||||
element: 1.,
|
||||
fields: vec![],
|
||||
partial: true,
|
||||
},
|
||||
ValueNode(0.5),
|
||||
&source_layout,
|
||||
);
|
||||
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a partial record");
|
||||
};
|
||||
assert_eq!(column(&list, Opacity::NAME), vec![0.5]);
|
||||
assert_eq!(unsafe { value.rec().read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||
}
|
||||
|
||||
use core_types::context::ExtractFrame;
|
||||
use core_types::record::{Frame, FrameLayout, Layout, Rec, RecordSource, RecordValue};
|
||||
#[test]
|
||||
fn interrupt_kernel_errors_stop_the_eval() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
struct RecordSourceNode {
|
||||
slot: usize,
|
||||
element: f64,
|
||||
fields: Vec<(usize, f64)>,
|
||||
partial: bool,
|
||||
}
|
||||
let source_layout = f64_layout(&[]);
|
||||
let modified = checked_multiply_opacity_layout(&source_layout);
|
||||
reserve_for(&[&source_layout, &modified]);
|
||||
|
||||
impl<'e> Node<ContextImpl<'e>> for RecordSourceNode {
|
||||
type Output = RecordValue<'e>;
|
||||
let ok = CheckedMultiplyOpacityNode::wire(bare_source(&source_layout, 1.), ValueNode(0.5), &source_layout);
|
||||
let GPoll::Final(value) = ok.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(unsafe { value.rec().read::<f64>(modified.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||
|
||||
fn eval(&self, input: &ContextImpl<'e>) -> GPoll<RecordValue<'e>> {
|
||||
let frame = ExtractFrame::frame(input).unwrap();
|
||||
let value = unsafe {
|
||||
let dst = frame.slot(self.slot);
|
||||
dst.cast::<f64>().write(self.element);
|
||||
for (offset, value) in &self.fields {
|
||||
dst.add(*offset).cast::<f64>().write(*value);
|
||||
}
|
||||
RecordValue::from_rec(Rec::new(dst))
|
||||
};
|
||||
match self.partial {
|
||||
true => GPoll::Partial(value),
|
||||
false => GPoll::Final(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn f64_layout(names: &[&'static str]) -> Layout {
|
||||
let writes: Vec<(&'static str, u8, usize, usize)> = names.iter().map(|name| (*name, 0, 8, 8)).collect();
|
||||
Layout::default().with_writes(0, (8, 8), &writes)
|
||||
let failing = CheckedMultiplyOpacityNode::wire(bare_source(&source_layout, 1.), ValueNode(-1.), &source_layout);
|
||||
let GPoll::Error(error) = failing.eval(&ctx) else {
|
||||
panic!("expected an error");
|
||||
};
|
||||
assert!(error.kind == "negative factor");
|
||||
}
|
||||
|
||||
static FACTOR: f64 = 3.;
|
||||
@@ -237,61 +379,88 @@ mod tests {
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let chain = ScaleNode::new(
|
||||
MultiplyOpacityNode::new(ValueNode(List::from_element_values(vec![1., 2.])), ValueNode(0.5)),
|
||||
let source_layout = f64_layout(&[]);
|
||||
let modified = multiply_opacity_layout(&source_layout);
|
||||
let scaled = scale_layout(&modified);
|
||||
reserve_for(&[&source_layout, &modified, &scaled]);
|
||||
|
||||
let chain = ScaleNode::wire(
|
||||
MultiplyOpacityNode::wire(bare_source(&source_layout, 2.), ValueNode(0.5), &source_layout),
|
||||
StaticLendNode(&FACTOR),
|
||||
&modified,
|
||||
);
|
||||
let GPoll::Final(list) = chain.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
assert_eq!(elements(&list), vec![3., 6.]);
|
||||
assert_eq!(column(&list, Opacity::NAME), vec![0.5, 0.5]);
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 6.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(scaled.offset_of(Opacity::NAME, 0).unwrap()) }, 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parked_reference_attributes_write_and_carry() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let source_layout = f64_layout(&[]);
|
||||
let labeled = label_layout(&source_layout);
|
||||
let relabeled = label_layout(&labeled);
|
||||
reserve_for(&[&source_layout, &labeled, &relabeled]);
|
||||
|
||||
let chain = LabelNode::wire(
|
||||
LabelNode::wire(bare_source(&source_layout, 1.), ValueNode(String::from("a")), &source_layout),
|
||||
ValueNode(String::from("b")),
|
||||
&labeled,
|
||||
);
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = value.rec();
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 1.);
|
||||
assert_eq!(unsafe { rec.read::<&str>(relabeled.offset_of(Label::NAME, 0).unwrap()) }, "ab");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn census_fills_reference_defaults_from_static_data() {
|
||||
let source = f64_layout(&[]);
|
||||
let labeled = Layout::default().with_writes(0, (8, 8), &[(Label::NAME, 0, 16, 8)]);
|
||||
|
||||
let plan = core_types::record::SourcePlan::new(&source, &labeled).unwrap();
|
||||
let record = [5f64];
|
||||
let mut buffer = vec![0u64; labeled.size.div_ceil(8)];
|
||||
let translated = unsafe { plan.translate(Rec::new(record.as_ptr().cast()), buffer.as_mut_ptr().cast()) };
|
||||
assert_eq!(unsafe { translated.element::<f64>() }, 5.);
|
||||
assert_eq!(unsafe { translated.read::<&str>(labeled.offset_of(Label::NAME, 0).unwrap()) }, "");
|
||||
}
|
||||
|
||||
fn f64_record_source(layout: &Layout, element: f64, fields: Vec<(usize, f64)>) -> RecordSourceNode<f64> {
|
||||
RecordSourceNode {
|
||||
frame_bytes: frame_bytes(layout),
|
||||
element,
|
||||
fields,
|
||||
partial: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn routing_unions_branch_layouts_and_fills_census_defaults() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout_a = f64_layout(&["opacity"]);
|
||||
let layout_b = f64_layout(&["length"]);
|
||||
let union = Layout::union(&[&layout_a, &layout_b]);
|
||||
|
||||
let mut frame_layout = FrameLayout::default();
|
||||
let slot_a = frame_layout.slot(&layout_a);
|
||||
let slot_b = frame_layout.slot(&layout_b);
|
||||
let translate_a = frame_layout.slot(&union);
|
||||
let translate_b = frame_layout.slot(&union);
|
||||
let frame = Frame::new(frame_layout.size());
|
||||
|
||||
let scope = scope_fixture(&generations, &arena).with_frame(&frame);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
reserve_for(&[&layout_a, &layout_b, &union, &union]);
|
||||
|
||||
let taken = |second: bool| {
|
||||
PickNode::new(
|
||||
ValueNode(second),
|
||||
RecordSource::wire(
|
||||
RecordSourceNode {
|
||||
slot: slot_a,
|
||||
element: 1.,
|
||||
fields: vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)],
|
||||
partial: false,
|
||||
},
|
||||
&layout_a,
|
||||
&union,
|
||||
translate_a,
|
||||
),
|
||||
RecordSource::wire(
|
||||
RecordSourceNode {
|
||||
slot: slot_b,
|
||||
element: 3.,
|
||||
fields: vec![(layout_b.offset_of("length", 0).unwrap(), 3.)],
|
||||
partial: false,
|
||||
},
|
||||
&layout_b,
|
||||
&union,
|
||||
translate_b,
|
||||
),
|
||||
RecordSource::wire(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
||||
RecordSource::wire(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -316,45 +485,18 @@ mod tests {
|
||||
fn routing_provenance_survives_later_evaluations() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout_a = f64_layout(&["opacity"]);
|
||||
let layout_b = f64_layout(&["length"]);
|
||||
let union = Layout::union(&[&layout_a, &layout_b]);
|
||||
|
||||
let mut frame_layout = FrameLayout::default();
|
||||
let slot_a = frame_layout.slot(&layout_a);
|
||||
let slot_b = frame_layout.slot(&layout_b);
|
||||
let translate_a = frame_layout.slot(&union);
|
||||
let translate_b = frame_layout.slot(&union);
|
||||
let frame = Frame::new(frame_layout.size());
|
||||
|
||||
let scope = scope_fixture(&generations, &arena).with_frame(&frame);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
reserve_for(&[&layout_a, &layout_b, &union, &union]);
|
||||
|
||||
let chain = HoldFirstNode::new(
|
||||
ValueNode(false),
|
||||
RecordSource::wire(
|
||||
RecordSourceNode {
|
||||
slot: slot_a,
|
||||
element: 1.,
|
||||
fields: vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)],
|
||||
partial: false,
|
||||
},
|
||||
&layout_a,
|
||||
&union,
|
||||
translate_a,
|
||||
),
|
||||
RecordSource::wire(
|
||||
RecordSourceNode {
|
||||
slot: slot_b,
|
||||
element: 3.,
|
||||
fields: vec![(layout_b.offset_of("length", 0).unwrap(), 3.)],
|
||||
partial: false,
|
||||
},
|
||||
&layout_b,
|
||||
&union,
|
||||
translate_b,
|
||||
),
|
||||
RecordSource::wire(f64_record_source(&layout_a, 1., vec![(layout_a.offset_of("opacity", 0).unwrap(), 0.5)]), &layout_a, &union),
|
||||
RecordSource::wire(f64_record_source(&layout_b, 3., vec![(layout_b.offset_of("length", 0).unwrap(), 3.)]), &layout_b, &union),
|
||||
);
|
||||
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
@@ -370,32 +512,25 @@ mod tests {
|
||||
fn identity_layouts_forward_the_record_pointer() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
|
||||
let layout = f64_layout(&["opacity"]);
|
||||
let mut frame_layout = FrameLayout::default();
|
||||
let slot = frame_layout.slot(&layout);
|
||||
let frame = Frame::new(frame_layout.size());
|
||||
|
||||
let scope = scope_fixture(&generations, &arena).with_frame(&frame);
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = f64_layout(&["opacity"]);
|
||||
reserve_for(&[&layout]);
|
||||
let base = stack::push(0);
|
||||
stack::pop(base);
|
||||
|
||||
let chain = ForwardRecordNode::new(RecordSource::wire(
|
||||
RecordSourceNode {
|
||||
slot,
|
||||
element: 4.,
|
||||
fields: vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)],
|
||||
partial: false,
|
||||
},
|
||||
f64_record_source(&layout, 4., vec![(layout.offset_of("opacity", 0).unwrap(), 0.25)]),
|
||||
&layout,
|
||||
&layout.clone(),
|
||||
0,
|
||||
));
|
||||
|
||||
let GPoll::Final(value) = chain.eval(&ctx) else {
|
||||
panic!("expected a final record");
|
||||
};
|
||||
let rec = value.rec();
|
||||
assert_eq!(rec.ptr(), unsafe { frame.slot(slot) }.cast_const());
|
||||
assert_eq!(rec.ptr(), base.cast_const());
|
||||
assert_eq!(unsafe { rec.element::<f64>() }, 4.);
|
||||
assert_eq!(unsafe { rec.read::<f64>(layout.offset_of("opacity", 0).unwrap()) }, 0.25);
|
||||
}
|
||||
@@ -404,25 +539,21 @@ mod tests {
|
||||
fn partial_routing_sources_downgrade_the_output() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let layout = f64_layout(&["opacity"]);
|
||||
let mut frame_layout = FrameLayout::default();
|
||||
let slot = frame_layout.slot(&layout);
|
||||
let frame = Frame::new(frame_layout.size());
|
||||
|
||||
let scope = scope_fixture(&generations, &arena).with_frame(&frame);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
reserve_for(&[&layout]);
|
||||
|
||||
let chain = ForwardRecordNode::new(RecordSource::wire(
|
||||
RecordSourceNode {
|
||||
slot,
|
||||
frame_bytes: frame_bytes(&layout),
|
||||
element: 4.,
|
||||
fields: vec![],
|
||||
partial: true,
|
||||
},
|
||||
&layout,
|
||||
&layout.clone(),
|
||||
0,
|
||||
));
|
||||
|
||||
let GPoll::Partial(value) = chain.eval(&ctx) else {
|
||||
@@ -430,24 +561,4 @@ mod tests {
|
||||
};
|
||||
assert_eq!(unsafe { value.rec().element::<f64>() }, 4.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupt_kernel_errors_stop_the_eval() {
|
||||
let arena = Arena::new(1024).unwrap();
|
||||
let generations = [];
|
||||
let scope = scope_fixture(&generations, &arena);
|
||||
let ctx = ContextImpl::root(&scope);
|
||||
|
||||
let ok = CheckedMultiplyOpacityNode::new(ValueNode(List::from_element_values(vec![1.])), ValueNode(0.5));
|
||||
let GPoll::Final(list) = ok.eval(&ctx) else {
|
||||
panic!("expected a final list");
|
||||
};
|
||||
assert_eq!(column(&list, Opacity::NAME), vec![0.5]);
|
||||
|
||||
let failing = CheckedMultiplyOpacityNode::new(ValueNode(List::from_element_values(vec![1.])), ValueNode(-1.));
|
||||
let GPoll::Error(error) = failing.eval(&ctx) else {
|
||||
panic!("expected an error");
|
||||
};
|
||||
assert!(error.kind == "negative factor");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user