This commit is contained in:
Dennis Kobert
2026-07-30 20:48:30 +00:00
parent 07b95ba59b
commit 4556e320df
22 changed files with 813 additions and 1603 deletions

View File

@@ -1,10 +1,10 @@
use core_types::arena::{Arena, ArenaCell};
use core_types::context::{Ctx, CtxSnapshot, DeriveCtx, ExtractAll};
use core_types::frame_table::{FrameTable, Lookup};
use core_types::gnode::GNode;
use core_types::gpoll::{Extent, Finality, GPoll, Interrupt};
use core_types::graphene_hash::CacheHash;
use core_types::memo::*;
use core_types::node::Node;
use core_types::registry::cache_key;
use std::sync::Arc;
use std::sync::Mutex;
@@ -35,7 +35,7 @@ fn memoize<I: CacheHash, T: Clone>(input: I, #[data] cache: Arc<Mutex<Option<(u6
fn memoize_extent<C, T, NodeContent>(node: &MemoizeNode<T, NodeContent>, ctx: &C) -> GPoll<Extent>
where
T: Clone,
NodeContent: GNode<C, Output = T>,
NodeContent: Node<C, Output = T>,
{
node.content.extent(ctx)
}
@@ -71,7 +71,7 @@ fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<'
fn frame_memo_extent<C, T, NodeContent>(node: &FrameMemoNode<T, NodeContent>, ctx: &C) -> GPoll<Extent>
where
T: Clone + 'static,
NodeContent: GNode<C, Output = T>,
NodeContent: Node<C, Output = T>,
{
node.content.extent(ctx)
}
@@ -129,12 +129,12 @@ mod tests {
use core_types::Type;
use core_types::concrete;
use core_types::context::{ContextImpl, EvalScope};
use core_types::registry::{EdgeHandle, ErasedGNode, ErasedLendGNode};
use core_types::registry::{EdgeHandle, ErasedLendNode, ErasedNode};
use std::sync::atomic::{AtomicU32, Ordering};
struct CountingNode(AtomicU32);
impl<Input> GNode<Input> for CountingNode {
impl<Input> Node<Input> for CountingNode {
type Output = u32;
fn eval(&self, _input: &Input) -> GPoll<u32> {
@@ -144,7 +144,7 @@ mod tests {
struct PartialCountingNode(AtomicU32);
impl<Input> GNode<Input> for PartialCountingNode {
impl<Input> Node<Input> for PartialCountingNode {
type Output = u32;
fn eval(&self, _input: &Input) -> GPoll<u32> {
@@ -154,7 +154,7 @@ mod tests {
struct ValueNode<T>(T);
impl<T: Clone, Input> GNode<Input> for ValueNode<T> {
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> GPoll<T> {
@@ -173,7 +173,7 @@ mod tests {
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc<ErasedGNode<u32>>);
let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc<ErasedNode<u32>>);
assert!(handle.serialize().is_none(), "no record before the first eval");
let edge = handle.duplicate().downcast::<u32>().unwrap();
@@ -233,8 +233,8 @@ mod tests {
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc<ErasedGNode<u32>>);
let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::<u32>().unwrap())) as Arc<ErasedGNode<u32>>);
let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc<ErasedNode<u32>>);
let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::<u32>().unwrap())) as Arc<ErasedNode<u32>>);
let stacked = MemoizeNode::new(memoized.downcast::<u32>().unwrap());
assert_eq!(stacked.eval(&ctx), GPoll::Final(1));
@@ -248,8 +248,8 @@ mod tests {
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let edge = EdgeHandle::new(Arc::new(ValueNode("lent out".to_string())) as Arc<ErasedGNode<String>>);
let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::<String>().unwrap())) as Arc<ErasedLendGNode<String>>);
let edge = EdgeHandle::new(Arc::new(ValueNode("lent out".to_string())) as Arc<ErasedNode<String>>);
let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::<String>().unwrap())) as Arc<ErasedLendNode<String>>);
assert_eq!(*lending.ty(), core_types::registry::lend_edge_type::<String>());
let node = lending.downcast_lend::<String>().unwrap();

View File

@@ -3,9 +3,6 @@ use core_types::runtime::SourceFuture;
use core_types::{Ctx, ExtractFootprint, ops::Convert, ops::ConvertAsync, transform::Footprint};
use std::marker::PhantomData;
// Re-export TypeNode from core-types for convenience
pub use core_types::ops::TypeNode;
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
#[node_macro::node(category("General"), skip_impl)]
fn passthrough<'i, T: 'i + Send>(_: impl Ctx, content: T) -> T {

View File

@@ -190,14 +190,14 @@ mod tests {
use super::*;
use core_types::arena::Arena;
use core_types::context::{ContextImpl, EvalScope, VarArgsResult};
use core_types::gnode::GNode;
use core_types::gpoll::GPoll;
use core_types::node::Node;
use core_types::{ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime};
use graphene_application_io::TimingInformation;
struct ProbeNode;
impl<'a> GNode<ContextImpl<'a>> for ProbeNode {
impl<'a> Node<ContextImpl<'a>> for ProbeNode {
type Output = RenderOutput;
fn eval(&self, ctx: &ContextImpl<'a>) -> GPoll<RenderOutput> {
@@ -241,7 +241,7 @@ mod tests {
let ctx = root.with_varargs(&varargs);
let graph = CreateContextNode::new(ProbeNode);
let GPoll::Final(result) = <CreateContextNode<ProbeNode> as GNode<ContextImpl>>::eval(&graph, &ctx) else {
let GPoll::Final(result) = <CreateContextNode<ProbeNode> as Node<ContextImpl>>::eval(&graph, &ctx) else {
panic!("create_context must complete synchronously");
};
assert_eq!(

View File

@@ -1066,15 +1066,15 @@ mod graphene_test {
use super::*;
use core_types::arena::Arena;
use core_types::context::{ContextImpl, EvalScope, ExtractIndex};
use core_types::gnode::{BatchStatus, GNode};
use core_types::gpoll::{Finality, GPoll};
use core_types::registry::{EdgeHandle, ErasedGNode, construct};
use core_types::node::{BatchStatus, Node};
use core_types::registry::{EdgeHandle, ErasedNode, construct};
use std::mem::MaybeUninit;
use std::sync::Arc;
struct SourceNode<T>(T);
impl<T: Clone, Input> GNode<Input> for SourceNode<T> {
impl<T: Clone, Input> Node<Input> for SourceNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> GPoll<T> {
@@ -1084,7 +1084,7 @@ mod graphene_test {
struct IndexNode;
impl<Input: ExtractIndex> GNode<Input> for IndexNode {
impl<Input: ExtractIndex> Node<Input> for IndexNode {
type Output = f64;
fn eval(&self, input: &Input) -> GPoll<f64> {
@@ -1097,13 +1097,13 @@ mod graphene_test {
}
#[test]
fn generated_add_evaluates_through_the_gnode_path() {
fn generated_add_evaluates_through_the_node_path() {
let arena = Arena::new(64);
let scope = scope_fixture(&arena);
let ctx = ContextImpl::root(&scope);
let graph = AddNode::new(SourceNode(1.0f64), SourceNode(2.0f64));
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(3.0));
assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(3.0));
}
#[test]
@@ -1112,7 +1112,7 @@ mod graphene_test {
let scope = scope_fixture(&arena);
let ctx = ContextImpl::root(&scope);
let erased: Box<ErasedGNode<f64>> = Box::new(AddNode::new(IndexNode, SourceNode(10.0f64)));
let erased: Box<ErasedNode<f64>> = Box::new(AddNode::new(IndexNode, SourceNode(10.0f64)));
let mut scratch = [const { MaybeUninit::uninit() }; 4];
let status = erased.eval_batch(&ctx, 2..6, Some(&mut scratch));
let BatchStatus::Filled(lanes, finality) = status else {
@@ -1129,11 +1129,11 @@ mod graphene_test {
let ctx = ContextImpl::root(&scope);
let entries = logical_or_entries();
let value = EdgeHandle::new(Arc::new(SourceNode(true)) as Arc<ErasedGNode<bool>>);
let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc<ErasedGNode<bool>>);
let value = EdgeHandle::new(Arc::new(SourceNode(true)) as Arc<ErasedNode<bool>>);
let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc<ErasedNode<bool>>);
let wired = construct(&entries[0], vec![value, other_value]).unwrap().downcast::<bool>().unwrap();
assert_eq!(GNode::eval(&wired, &ctx), GPoll::Final(true));
assert_eq!(Node::eval(&wired, &ctx), GPoll::Final(true));
}
#[test]
@@ -1159,11 +1159,11 @@ mod graphene_test {
assert_eq!(entries[3].io.inputs, vec![core_types::concrete!(DVec2), core_types::concrete!(DVec2)]);
assert_eq!(entries[3].io.output, core_types::concrete!(DVec2));
let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc<ErasedGNode<f64>>);
let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc<ErasedGNode<f64>>);
let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc<ErasedNode<f64>>);
let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc<ErasedNode<f64>>);
let wired = construct(&entries[0], vec![augend, addend]).unwrap().downcast::<f64>().unwrap();
assert_eq!(GNode::eval(&wired, &ctx), GPoll::Final(4.0));
assert_eq!(Node::eval(&wired, &ctx), GPoll::Final(4.0));
}
#[test]
@@ -1173,7 +1173,7 @@ mod graphene_test {
struct CountingSource(Arc<AtomicU32>, f64);
impl<Input> GNode<Input> for CountingSource {
impl<Input> Node<Input> for CountingSource {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
@@ -1190,7 +1190,7 @@ mod graphene_test {
let untaken = Arc::new(AtomicU32::new(0));
let graph = SwitchNode::new(SourceNode(true), CountingSource(taken.clone(), 1.0), CountingSource(untaken.clone(), 2.0));
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(1.0));
assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(1.0));
assert_eq!(taken.load(Ordering::Relaxed), 1);
assert_eq!(untaken.load(Ordering::Relaxed), 0);
}
@@ -1199,7 +1199,7 @@ mod graphene_test {
fn converted_switch_passes_branch_status_through() {
struct PendingSource;
impl<Input> GNode<Input> for PendingSource {
impl<Input> Node<Input> for PendingSource {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
@@ -1209,7 +1209,7 @@ mod graphene_test {
struct PartialSource;
impl<Input> GNode<Input> for PartialSource {
impl<Input> Node<Input> for PartialSource {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
@@ -1222,17 +1222,17 @@ mod graphene_test {
let ctx = ContextImpl::root(&scope);
let pending = SwitchNode::new(SourceNode(true), PendingSource, PartialSource);
assert_eq!(GNode::eval(&pending, &ctx), GPoll::Pending);
assert_eq!(Node::eval(&pending, &ctx), GPoll::Pending);
let partial = SwitchNode::new(SourceNode(false), PendingSource, PartialSource);
assert_eq!(GNode::eval(&partial, &ctx), GPoll::Partial(7.0));
assert_eq!(Node::eval(&partial, &ctx), GPoll::Partial(7.0));
}
#[test]
fn converted_switch_merges_condition_status_into_the_branch_result() {
struct PartialCondition;
impl<Input> GNode<Input> for PartialCondition {
impl<Input> Node<Input> for PartialCondition {
type Output = bool;
fn eval(&self, _input: &Input) -> GPoll<bool> {
@@ -1245,14 +1245,14 @@ mod graphene_test {
let ctx = ContextImpl::root(&scope);
let graph = SwitchNode::new(PartialCondition, SourceNode(1.0f64), SourceNode(2.0f64));
assert_eq!(GNode::eval(&graph, &ctx), GPoll::Partial(1.0));
assert_eq!(Node::eval(&graph, &ctx), GPoll::Partial(1.0));
}
#[test]
fn generated_eval_computes_on_stand_in_and_traces_fallback() {
struct FallbackNode;
impl<Input> GNode<Input> for FallbackNode {
impl<Input> Node<Input> for FallbackNode {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
@@ -1265,7 +1265,7 @@ mod graphene_test {
let ctx = ContextImpl::root(&scope);
let graph = AddNode::new(FallbackNode, SourceNode(5.0f64));
let GPoll::Fallback(boxed) = GNode::eval(&graph, &ctx) else {
let GPoll::Fallback(boxed) = Node::eval(&graph, &ctx) else {
panic!("fallback must propagate with the computed stand-in");
};
assert_eq!(boxed.0, 5.0);

View File

@@ -179,143 +179,3 @@ fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
Ok(result_list)
}
#[cfg(test)]
mod test {
use super::*;
use core_types::Ctx;
use core_types::Node;
use core_types::transform::Footprint;
use glam::DVec2;
use graphene_core::ReadPositionNode;
use graphene_core::extract_xy::{ExtractXyNode, XY};
use graphic_types::Vector;
use kurbo::Shape;
use kurbo::{BezPath, DEFAULT_ACCURACY, Rect};
use std::future::Future;
use std::pin::Pin;
use vector_nodes::generator_nodes::RectangleNode;
use vector_types::subpath::Subpath;
fn vector_node_from_bezpath(bezpath: BezPath) -> List<Vector> {
List::new_from_element(Vector::from_bezpath(bezpath))
}
#[derive(Clone)]
pub struct FutureWrapperNode<T: Clone>(T);
impl<'i, I: Ctx, T: 'i + Clone + Send> Node<'i, I> for FutureWrapperNode<T> {
type Output = Pin<Box<dyn Future<Output = T> + 'i + Send>>;
fn eval(&'i self, _input: I) -> Self::Output {
let value = self.0.clone();
Box::pin(async move { value })
}
}
#[tokio::test]
async fn repeat_on_points_test() {
let context = OwnedContextImpl::default().into_context();
let rect = RectangleNode::new(
FutureWrapperNode(()),
ExtractXyNode::new(ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(0)), FutureWrapperNode(XY::Y)),
FutureWrapperNode(2_f64),
FutureWrapperNode(false),
FutureWrapperNode(0_f64),
FutureWrapperNode(false),
);
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = List::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
let generated = super::repeat_on_points(context, points, &rect, false).await;
assert_eq!(generated.len(), positions.len());
for (position, index) in positions.into_iter().zip(0..generated.len()) {
let bounds = generated
.element(index)
.unwrap()
.bounding_box_with_transform(generated.attribute_cloned_or_default(ATTR_TRANSFORM, index))
.unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);
}
}
#[tokio::test]
async fn repeat() {
let direction = DVec2::X * 1.5;
let count = 3;
let context = OwnedContextImpl::default().into_context();
let repeated = super::repeat_array(
context,
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
direction,
0.,
count,
)
.await;
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 3);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
}
}
#[tokio::test]
async fn repeat_single_copy() {
let context = OwnedContextImpl::default().into_context();
let repeated = super::repeat_array(
context,
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
DVec2::new(12., 10.),
45.,
1,
)
.await;
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 1);
let (_, manipulator_groups) = vector.region_manipulator_groups().next().unwrap();
let anchor = manipulator_groups[0].anchor;
assert!(anchor.length() < 1e-5, "Expected the single copy to be untransformed, found anchor {anchor}");
}
#[tokio::test]
async fn repeat_transform_position() {
let direction = DVec2::new(12., 10.);
let count = 8;
let context = OwnedContextImpl::default().into_context();
let repeated = super::repeat_array(
context,
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
direction,
0.,
count,
)
.await;
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
}
}
#[tokio::test]
async fn repeat_radial() {
let context = OwnedContextImpl::default().into_context();
let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;
let vector_list = vector_nodes::flatten_path(Footprint::default(), repeated).await;
let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
let expected_angle = (index as f64 + 1.) * 45.;
let center = (manipulator_groups[0].anchor + manipulator_groups[2].anchor) / 2.;
let actual_angle = DVec2::Y.angle_to(center).to_degrees();
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5, "Expected {expected_angle} found {actual_angle}");
}
}
}