From 4556e320df226662a68deca24b418075b738f854 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jul 2026 20:48:30 +0000 Subject: [PATCH] Cleanup --- editor/src/node_graph_executor/runtime.rs | 2 +- node-graph/graph-craft/src/document/value.rs | 4 +- .../src/dynamic_executor.rs | 9 +- .../interpreted-executor/src/node_registry.rs | 10 +- .../tests/graphene_spike.rs | 288 -------- .../libraries/core-types/src/generic.rs | 17 - node-graph/libraries/core-types/src/lib.rs | 103 +-- .../core-types/src/{gnode.rs => node.rs} | 30 +- node-graph/libraries/core-types/src/ops.rs | 36 - .../libraries/core-types/src/registry.rs | 70 +- .../libraries/core-types/src/runtime.rs | 46 +- node-graph/libraries/core-types/src/value.rs | 201 +---- node-graph/node-macro/src/codegen.rs | 696 +++++++++++++++++- node-graph/node-macro/src/gcodegen.rs | 680 ----------------- node-graph/node-macro/src/lib.rs | 1 - node-graph/node-macro/src/parsing.rs | 2 +- node-graph/node-macro/src/validation.rs | 2 +- node-graph/nodes/gcore/src/memo.rs | 24 +- node-graph/nodes/gcore/src/ops.rs | 3 - node-graph/nodes/gstd/src/render_node.rs | 6 +- node-graph/nodes/math/src/lib.rs | 46 +- node-graph/nodes/repeat/src/repeat_nodes.rs | 140 ---- 22 files changed, 813 insertions(+), 1603 deletions(-) delete mode 100644 node-graph/interpreted-executor/tests/graphene_spike.rs delete mode 100644 node-graph/libraries/core-types/src/generic.rs rename node-graph/libraries/core-types/src/{gnode.rs => node.rs} (93%) delete mode 100644 node-graph/node-macro/src/gcodegen.rs diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index c5af3f6b44..87725d2b78 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -9,6 +9,7 @@ use graph_craft::graphene_compiler::Compiler; use graph_craft::proto::GraphErrors; use graphene_std::application_io::{ApplicationIo, ExportFormat, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig, Texture}; use graphene_std::bounds::RenderBoundingBox; +use graphene_std::core_types::gpoll::GPoll; use graphene_std::list::List; use graphene_std::memo::IORecord; use graphene_std::ops::{Convert, ConvertAsync}; @@ -16,7 +17,6 @@ use graphene_std::ops::{Convert, ConvertAsync}; use graphene_std::platform_application_io::canvas_utils::{Canvas, CanvasSurface, CanvasSurfaceHandle}; use graphene_std::raster_types::Raster; use graphene_std::renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, SvgSegment}; -use graphene_std::core_types::gpoll::GPoll; use graphene_std::runtime::{DynGraphRuntime, DynNotifier, DynSpawner, GraphRuntime, RuntimeHandle, SourceFuture, Spawner}; use graphene_std::transform::RenderQuality; use graphene_std::vector::Vector; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 57f5bcaae2..499e7a1142 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -5,14 +5,14 @@ use crate::proto::Any as DAny; use brush_nodes::brush_stroke::BrushStroke; use core_types::color::SRGBA8; use core_types::context::Context; -use core_types::gnode::GNode; use core_types::gpoll::GPoll; use core_types::list::List; +use core_types::node::Node; use core_types::registry::{EdgeHandle, edge_type}; use core_types::transform::Footprint; use core_types::uuid::NodeId; use core_types::value::value_edge; -use core_types::{CacheHash, Color, ContextModification, MemoHash, Node, Type, TypeDescriptor}; +use core_types::{CacheHash, Color, ContextModification, MemoHash, Type, TypeDescriptor}; use dyn_any::DynAny; pub use dyn_any::StaticType; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index c5879d3d41..78b7e8d5c3 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -1,9 +1,9 @@ use crate::node_registry; use core_types::arena::Arena; use core_types::context::{ContextImpl, DynSlot, EvalScope, VarArg, VarArgLink, VarArgSlots}; -use core_types::gnode::GNode; use core_types::gpoll::GPoll; -use core_types::registry::{EdgeHandle, ErasedGNode}; +use core_types::node::Node; +use core_types::registry::{EdgeHandle, ErasedNode}; use core_types::runtime::{DynGraphRuntime, DynSpawner, GraphRuntime, NoopSpawner}; use graph_craft::Type; use graph_craft::document::NodeId; @@ -35,7 +35,6 @@ fn noop_runtime() -> Arc { Arc::new(GraphRuntime::new(Box::new(NoopSpawner) as Box)) } - impl Default for DynamicExecutor { fn default() -> Self { Self { @@ -291,7 +290,7 @@ impl BorrowTree { self.nodes.insert(id, (node, path)); } - /// Calls the `GNode::serialize` for that specific node, returning for example the captured io record for a monitor node. The node path must match the document node path. + /// Calls the `Node::serialize` for that specific node, returning for example the captured io record for a monitor node. The node path must match the document node path. pub fn introspect(&self, node_path: &[NodeId]) -> Result, IntrospectError> { let (id, _) = self.source_map.get(node_path).ok_or_else(|| IntrospectError::PathNotFound(node_path.to_vec()))?; let (node, _path) = self.nodes.get(id).ok_or(IntrospectError::ProtoNodeNotFound(*id))?; @@ -305,7 +304,7 @@ impl BorrowTree { /// Evaluate a node of the [`BorrowTree`], downcasting its edge to the expected output type. pub fn eval(&self, id: NodeId, input: &I) -> Option> where - ErasedGNode: GNode, + ErasedNode: Node, { let (node, _path) = self.nodes.get(&id)?; let edge = node.duplicate().downcast::().ok()?; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 2026e568e8..81c772d55f 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -13,7 +13,7 @@ use graphene_std::raster::GPU; use graphene_std::raster::color::Color; use graphene_std::raster::*; use graphene_std::raster::{CPU, Raster}; -use graphene_std::registry::{ConstructionError, EdgeHandle, ErasedGNode, NodeIOTypes, RegistryEntry}; +use graphene_std::registry::{ConstructionError, EdgeHandle, ErasedNode, NodeIOTypes, RegistryEntry}; use graphene_std::render_node::RenderIntermediate; use graphene_std::runtime::RuntimeHandle; use graphene_std::transform::Footprint; @@ -367,7 +367,7 @@ mod node_registry_macros { } let mut inputs = inputs.into_iter(); let node = <$path>::new(inputs.next().unwrap().downcast::<$first>()? $(, inputs.next().unwrap().downcast::<$type>()?)*); - Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) + Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) }, }, ) @@ -386,7 +386,7 @@ mod node_registry_macros { } let mut inputs = inputs.into_iter(); let node = graphene_std::ops::IntoNode::<$to, _>::new(inputs.next().unwrap().downcast::<$from>()?); - Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) + Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) }, }, ) @@ -454,7 +454,7 @@ mod node_registry_macros { inputs.next().unwrap().downcast::()?, inputs.next().unwrap().downcast::()?, ); - Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) + Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) }, }, ) @@ -470,7 +470,7 @@ mod node_registry_macros { } let mut inputs = inputs.into_iter(); let node = graphene_std::ops::ConvertNode::<$to, _, _>::new(inputs.next().unwrap().downcast::<$from>()?, inputs.next().unwrap().downcast::<$convert>()?); - Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) + Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc>)) }, }, ) diff --git a/node-graph/interpreted-executor/tests/graphene_spike.rs b/node-graph/interpreted-executor/tests/graphene_spike.rs deleted file mode 100644 index dd55022b76..0000000000 --- a/node-graph/interpreted-executor/tests/graphene_spike.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::any::Any; -use std::mem::MaybeUninit; -use std::ops::Add; - -use core_types::arena::{Arena, ArenaCell}; -use core_types::context::{ContextImpl, Ctx, EvalScope, ExtractArena, InjectIndex}; -use core_types::gnode::{BatchStatus, GNode, StatusCell}; -use core_types::gpoll::{ErrorKind, Finality, GPoll, Interrupt}; - -fn add, B, C: Ctx>(_ctx: &C, augend: A, addend: B) -> >::Output { - augend + addend -} - -struct AddNode { - augend: Node0, - addend: Node1, -} - -impl AddNode { - fn new(augend: Node0, addend: Node1) -> Self { - Self { augend, addend } - } -} - -impl GNode for AddNode -where - A: Add, - Input: Ctx, - Node0: GNode, - Node1: GNode, -{ - type Output = >::Output; - - fn eval(&self, input: &Input) -> GPoll { - let cell = StatusCell::new(); - let augend = match cell.eval_input(0, &self.augend, input) { - Ok(value) => value, - Err(interrupt) => return interrupt.into(), - }; - let addend = match cell.eval_input(1, &self.addend, input) { - Ok(value) => value, - Err(interrupt) => return interrupt.into(), - }; - cell.finish(add(input, augend, addend)) - } -} - -struct ValueNode(T); - -impl GNode for ValueNode { - type Output = T; - - fn eval(&self, _input: &Input) -> GPoll { - GPoll::Final(self.0.clone()) - } -} - -struct ReadIndexNode; - -impl GNode for ReadIndexNode { - type Output = f64; - - fn eval(&self, input: &Input) -> GPoll { - GPoll::Final(input.index_value() as f64) - } -} - -trait ExtractIndexValue { - fn index_value(&self) -> u64; -} - -impl ExtractIndexValue for ContextImpl<'_> { - fn index_value(&self) -> u64 { - self.index_head().index - } -} - -fn string_length(_ctx: &C, value: &String) -> f64 { - value.len() as f64 -} - -struct LendStringNode { - value: String, - cell: ArenaCell, -} - -impl LendStringNode { - fn new(value: String) -> Self { - Self { value, cell: ArenaCell::new() } - } -} - -impl<'e, Input> GNode for LendStringNode -where - Input: Ctx + ExtractArena, -{ - type Output = &'e String; - - fn eval(&self, input: &Input) -> GPoll<&'e String> { - let arena = input.arena(); - if let Some(value) = self.cell.load(arena) { - return GPoll::Final(value); - } - match arena.alloc(self.value.clone()) { - Some((value, weak)) => { - self.cell.store(weak); - GPoll::Final(value) - } - None => GPoll::arena_exhausted(), - } - } -} - -struct StringLengthNode { - value: Node0, -} - -impl StringLengthNode { - fn new(value: Node0) -> Self { - Self { value } - } -} - -impl<'e, Input, Node0> GNode for StringLengthNode -where - Input: Ctx, - Node0: GNode, -{ - type Output = f64; - - fn eval(&self, input: &Input) -> GPoll { - let cell = StatusCell::new(); - let value = match cell.eval_input(0, &self.value, input) { - Ok(value) => value, - Err(interrupt) => return interrupt.into(), - }; - cell.finish(string_length(input, value)) - } -} - -type ErasedGNode = dyn for<'c> GNode, Output = T>; -type ErasedLendEdge = dyn for<'c> GNode, Output = &'c String>; - -fn string_length_constructor(args: Vec>) -> Result>, &'static str> { - let mut args = args.into_iter(); - let value = *args.next().ok_or("arity")?.downcast::>().map_err(|_| "type")?; - Ok(Box::new(StringLengthNode::new(value))) -} - -fn add_constructor_f64(args: Vec>) -> Result>, &'static str> { - let mut args = args.into_iter(); - let augend = *args.next().ok_or("arity")?.downcast::>>().map_err(|_| "type")?; - let addend = *args.next().ok_or("arity")?.downcast::>>().map_err(|_| "type")?; - Ok(Box::new(AddNode::new(augend, addend))) -} - -fn scope_fixture<'a>(generations: &'a [(u64, u64)], arena: &'a Arena) -> EvalScope<'a> { - EvalScope::new(Some(0.5), None, None, generations, arena) -} - -#[test] -fn hand_expansion_evaluates_through_typed_erased_edges() { - let arena = Arena::new(1024); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let augend: Box = Box::new(Box::new(ValueNode(1.0f64)) as Box>); - let addend: Box = Box::new(Box::new(ValueNode(2.0f64)) as Box>); - let wired = add_constructor_f64(vec![augend, addend]).unwrap(); - - assert_eq!(wired.eval(&ctx), GPoll::Final(3.0)); -} - -#[test] -fn wiring_rejects_type_and_arity_mismatches() { - let augend: Box = Box::new(Box::new(ValueNode(1.0f64)) as Box>); - let addend: Box = Box::new(Box::new(ValueNode(2u32)) as Box>); - assert_eq!(add_constructor_f64(vec![augend, addend]).map(|_| ()), Err("type")); - - let augend: Box = Box::new(Box::new(ValueNode(1.0f64)) as Box>); - assert_eq!(add_constructor_f64(vec![augend]).map(|_| ()), Err("arity")); -} - -#[test] -fn spec_loop_batches_through_the_erased_edge() { - let arena = Arena::new(1024); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let graph: Box> = Box::new(AddNode::new(ReadIndexNode, ValueNode(10.0f64))); - let mut scratch = [const { MaybeUninit::uninit() }; 4]; - let status = graph.eval_batch(&ctx, 2..6, Some(&mut scratch)); - let BatchStatus::Filled(lanes, finality) = status else { - panic!("expected filled, got {status:?}"); - }; - assert_eq!(lanes, &[12.0, 13.0, 14.0, 15.0]); - assert_eq!(finality, Finality::AllFinal); -} - -#[test] -fn lending_kernel_clones_once_per_generation_and_lends_after() { - let arena = Arena::new(1024); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let node = LendStringNode::new("lend me".to_string()); - let GPoll::Final(first) = node.eval(&ctx) else { - panic!("first eval must clone into the arena and lend"); - }; - let GPoll::Final(second) = node.eval(&ctx) else { - panic!("second eval must hit the cell"); - }; - assert_eq!(first, "lend me"); - assert!(std::ptr::eq(first, second)); -} - -#[test] -fn exhausted_arena_reports_the_operational_error() { - let arena = Arena::new(0); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let node = LendStringNode::new("too big".to_string()); - let GPoll::Error(error) = node.eval(&ctx) else { - panic!("exhaustion must surface as an operational error"); - }; - assert_eq!(error.kind, ErrorKind::ArenaExhausted); -} - -#[test] -fn lending_edges_erase_and_wire_like_owned_edges() { - let arena = Arena::new(1024); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let value: Box = Box::new(Box::new(LendStringNode::new("across the boundary".to_string())) as Box); - let wired = string_length_constructor(vec![value]).unwrap(); - - assert_eq!(wired.eval(&ctx), GPoll::Final(19.0)); -} - -#[test] -fn spec_loop_batches_through_the_erased_lending_edge() { - let arena = Arena::new(1024); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let graph: Box = Box::new(LendStringNode::new("batched".to_string())); - let mut scratch = [const { MaybeUninit::uninit() }; 3]; - let status = graph.eval_batch(&ctx, 0..3, Some(&mut scratch)); - let BatchStatus::Filled(lanes, finality) = status else { - panic!("expected filled, got {status:?}"); - }; - assert_eq!(lanes.len(), 3); - assert!(lanes.iter().all(|lane| std::ptr::eq(*lane, lanes[0]))); - assert_eq!(*lanes[0], "batched"); - assert_eq!(finality, Finality::AllFinal); -} - -#[test] -fn fallback_input_records_partiality_invisibly() { - struct FallbackNode; - impl GNode for FallbackNode { - type Output = f64; - fn eval(&self, _input: &Input) -> GPoll { - GPoll::fallback(0.0, "upstream failed") - } - } - - let arena = Arena::new(1024); - let generations = []; - let scope = scope_fixture(&generations, &arena); - let ctx = ContextImpl::root(&scope); - - let graph = AddNode::new(FallbackNode, ValueNode(5.0f64)); - let GPoll::Fallback(boxed) = graph.eval(&ctx) else { - panic!("fallback must propagate with the computed stand-in"); - }; - assert_eq!(boxed.0, 5.0); - assert!(boxed.1.kind == "upstream failed"); - assert_eq!(boxed.1.trace, vec![0]); -} diff --git a/node-graph/libraries/core-types/src/generic.rs b/node-graph/libraries/core-types/src/generic.rs deleted file mode 100644 index 055c0cb53d..0000000000 --- a/node-graph/libraries/core-types/src/generic.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::Node; -use std::marker::PhantomData; -#[derive(Clone)] -pub struct FnNode O, I, O>(T, PhantomData<(I, O)>); - -impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode { - type Output = O; - fn eval(&'i self, input: I) -> Self::Output { - self.0(input) - } -} - -impl O, I, O> FnNode { - pub fn new(f: T) -> Self { - FnNode(f, PhantomData) - } -} diff --git a/node-graph/libraries/core-types/src/lib.rs b/node-graph/libraries/core-types/src/lib.rs index 1c5bbab724..5906c6ab65 100644 --- a/node-graph/libraries/core-types/src/lib.rs +++ b/node-graph/libraries/core-types/src/lib.rs @@ -5,13 +5,12 @@ pub mod bounds; pub mod consts; pub mod context; pub mod frame_table; -pub mod generic; -pub mod gnode; pub mod gpoll; pub mod list; pub mod math; pub mod memo; pub mod misc; +pub mod node; pub mod ops; pub mod registry; pub mod render_complexity; @@ -39,113 +38,15 @@ pub use no_std_types::blending; pub use no_std_types::choice_type; pub use no_std_types::color; pub use no_std_types::shaders; +pub use node::Node; pub use num_traits; -use std::any::TypeId; -use std::future::Future; -use std::pin::Pin; #[cfg(feature = "wasm")] pub use tsify; pub use types::Cow; -// pub trait Node: for<'n> NodeIO<'n> { -/// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct. -/// See `node-graph/README.md` for information on how to define a new node. -pub trait Node<'i, Input> { - type Output: 'i; - /// Evaluates the node with the single specified input. - fn eval(&'i self, input: Input) -> Self::Output; - /// Resets the node, e.g. the LetNode's cache is set to None. - fn reset(&self) {} - /// Returns the name of the node for diagnostic purposes. - fn node_name(&self) -> &'static str { - std::any::type_name::() - } - /// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes. - fn serialize(&self) -> Option> { - log::warn!("Node::serialize not implemented for {}", std::any::type_name::()); - None - } -} - mod types; pub use types::*; -pub trait NodeIO<'i, Input>: Node<'i, Input> -where - Self::Output: 'i + StaticTypeSized, - Input: StaticTypeSized, -{ - fn input_type(&self) -> TypeId { - TypeId::of::() - } - fn input_type_name(&self) -> &'static str { - std::any::type_name::() - } - fn output_type(&self) -> TypeId { - TypeId::of::<::Static>() - } - fn output_type_name(&self) -> &'static str { - std::any::type_name::() - } - fn to_node_io(&self, inputs: Vec) -> NodeIOTypes { - NodeIOTypes { - call_argument: concrete!(::Static), - return_value: concrete!(::Static), - inputs, - } - } - fn to_async_node_io(&self, inputs: Vec) -> NodeIOTypes - where - ::Output: StaticTypeSized, - Self::Output: Future, - { - NodeIOTypes { - call_argument: concrete!(::Static), - return_value: future!(<::Output as StaticTypeSized>::Static), - inputs, - } - } -} - -impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N -where - N::Output: 'i + StaticTypeSized, - I: StaticTypeSized, -{ -} - -impl<'i, I: 'i, N: Node<'i, I> + ?Sized> Node<'i, I> for &'i N { - type Output = N::Output; - fn eval(&'i self, input: I) -> N::Output { - (*self).eval(input) - } -} -impl<'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for Box { - type Output = O; - fn eval(&'i self, input: I) -> O { - (**self).eval(input) - } -} -impl<'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for std::sync::Arc { - type Output = O; - fn eval(&'i self, input: I) -> O { - (**self).eval(input) - } -} - -impl<'i, I, O: 'i> Node<'i, I> for Pin + 'i>> { - type Output = O; - fn eval(&'i self, input: I) -> O { - (**self).eval(input) - } -} -impl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> { - type Output = O; - fn eval(&'i self, input: I) -> O { - (**self).eval(input) - } -} - pub trait InputAccessorSource<'a, T>: InputAccessorSourceIdentifier + std::fmt::Debug { fn get_input(&'a self, index: usize) -> Option<&'a T>; fn set_input(&'a mut self, index: usize, value: T); diff --git a/node-graph/libraries/core-types/src/gnode.rs b/node-graph/libraries/core-types/src/node.rs similarity index 93% rename from node-graph/libraries/core-types/src/gnode.rs rename to node-graph/libraries/core-types/src/node.rs index 576da663ae..0bb55d4285 100644 --- a/node-graph/libraries/core-types/src/gnode.rs +++ b/node-graph/libraries/core-types/src/node.rs @@ -22,7 +22,7 @@ pub unsafe fn assume_init_prefix_mut(scratch: &mut [MaybeUninit], len: usi unsafe { std::slice::from_raw_parts_mut(scratch.as_mut_ptr().cast::(), len) } } -pub trait GNode { +pub trait Node { type Output; fn eval(&self, input: &Input) -> GPoll; @@ -80,9 +80,9 @@ pub trait GNode { } } -impl GNode for &N +impl Node for &N where - N: GNode + ?Sized, + N: Node + ?Sized, { type Output = N::Output; @@ -102,9 +102,9 @@ where } } -impl GNode for Box +impl Node for Box where - N: GNode + ?Sized, + N: Node + ?Sized, { type Output = N::Output; @@ -124,9 +124,9 @@ where } } -impl GNode for std::sync::Arc +impl Node for std::sync::Arc where - N: GNode + ?Sized, + N: Node + ?Sized, { type Output = N::Output; @@ -171,7 +171,7 @@ impl StatusCell { Self { no_partial: true, ..Self::new() } } - pub fn eval_input>(&self, input_index: usize, node: &N, input: &Input) -> Result { + pub fn eval_input>(&self, input_index: usize, node: &N, input: &Input) -> Result { match node.eval(input) { GPoll::Final(value) => Ok(value), GPoll::Partial(_) if self.no_partial => Err(Interrupt::Pending), @@ -233,15 +233,15 @@ impl<'a, N> LazyInput<'a, N> { pub fn eval(&self, ctx: &Input) -> Result where - N: GNode, + N: Node, { self.cell.eval_input(self.input_index, self.node, ctx) } } -impl<'a, Input, N> GNode for LazyInput<'a, N> +impl<'a, Input, N> Node for LazyInput<'a, N> where - N: GNode, + N: Node, { type Output = N::Output; @@ -279,7 +279,7 @@ mod tests { struct Double; - impl GNode for Double { + impl Node for Double { type Output = u64; fn eval(&self, input: &TestInput) -> GPoll { @@ -315,7 +315,7 @@ mod tests { #[test] fn partial_lane_downgrades_batch_finality() { struct PartialAtThree; - impl GNode for PartialAtThree { + impl Node for PartialAtThree { type Output = u64; fn eval(&self, input: &TestInput) -> GPoll { match input.index { @@ -344,7 +344,7 @@ mod tests { } } struct PendingAtTwo; - impl GNode for PendingAtTwo { + impl Node for PendingAtTwo { type Output = Probe; fn eval(&self, input: &TestInput) -> GPoll { match input.index { @@ -362,7 +362,7 @@ mod tests { #[test] fn trait_is_object_safe_across_erased_edges() { - let erased: Box> = Box::new(Double); + let erased: Box> = Box::new(Double); let input = TestInput { index: 21 }; assert_eq!(erased.eval(&input), GPoll::Final(42)); let mut scratch = [const { MaybeUninit::uninit() }; 2]; diff --git a/node-graph/libraries/core-types/src/ops.rs b/node-graph/libraries/core-types/src/ops.rs index 2c3eb462cc..afc871dbd5 100644 --- a/node-graph/libraries/core-types/src/ops.rs +++ b/node-graph/libraries/core-types/src/ops.rs @@ -1,43 +1,7 @@ -use crate::Node; use crate::list::{Attribute, AttributeDyn, AttributeValueDyn, Item, List, ListDyn}; use crate::transform::Footprint; use glam::DVec2; use graphene_hash::CacheHash; -use std::future::Future; -use std::marker::PhantomData; - -// Type -// TODO: Document this -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct TypeNode Node<'a, I>, I, O>(pub N, pub PhantomData<(I, O)>); -impl<'i, N, I: 'i, O: 'i> Node<'i, I> for TypeNode -where - N: for<'n> Node<'n, I, Output = O>, -{ - type Output = O; - fn eval(&'i self, input: I) -> Self::Output { - self.0.eval(input) - } - - fn reset(&self) { - self.0.reset(); - } - - fn serialize(&self) -> Option> { - self.0.serialize() - } -} -impl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode>::Output> { - pub fn new(node: N) -> Self { - Self(node, PhantomData) - } -} -impl<'i, N: for<'a> Node<'a, I> + Clone, I: 'i> Clone for TypeNode>::Output> { - fn clone(&self) -> Self { - Self(self.0.clone(), self.1) - } -} -impl<'i, N: for<'a> Node<'a, I> + Copy, I: 'i> Copy for TypeNode>::Output> {} /// The [`Convert`] trait allows for conversion between Rust primitive numeric types. /// Because number casting is lossy, we cannot use the normal [`Into`] trait like we do for other types. diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index bd3f32f574..320386e696 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -1,13 +1,12 @@ use crate::concrete; use crate::context::{Context, ContextImpl}; -use crate::gnode::GNode; +use crate::node::Node; use crate::{ContextFeature, ProtoNodeIdentifier, Type, WasmNotSend, WasmNotSync}; use dyn_any::DynAny; use graphene_hash::CacheHash; pub use no_std_types::registry::types; use std::collections::HashMap; use std::hash::Hasher; -use std::pin::Pin; use std::sync::{LazyLock, Mutex}; // Translation struct between macro and definition @@ -70,13 +69,13 @@ pub static NODE_METADATA: LazyLock = dyn for<'c> GNode, Output = T> + Send + Sync; +pub type ErasedNode = dyn for<'c> Node, Output = T> + Send + Sync; #[cfg(target_family = "wasm")] -pub type ErasedGNode = dyn for<'c> GNode, Output = T>; +pub type ErasedNode = dyn for<'c> Node, Output = T>; #[cfg(not(target_family = "wasm"))] -pub type ErasedLendGNode = dyn for<'c> GNode, Output = &'c T> + Send + Sync; +pub type ErasedLendNode = dyn for<'c> Node, Output = &'c T> + Send + Sync; #[cfg(target_family = "wasm")] -pub type ErasedLendGNode = dyn for<'c> GNode, Output = &'c T>; +pub type ErasedLendNode = dyn for<'c> Node, Output = &'c T>; #[cfg(not(target_family = "wasm"))] type DynEdge = dyn std::any::Any + Send + Sync; @@ -127,9 +126,9 @@ unsafe impl Send for SharedEdge {} // SAFETY: as in Send. unsafe impl Sync for SharedEdge {} -impl GNode for SharedEdge +impl Node for SharedEdge where - N: GNode + ?Sized, + N: Node + ?Sized, { type Output = N::Output; @@ -149,7 +148,7 @@ where unsafe { self.ptr.as_ref() }.serialize() } - fn eval_batch<'a>(&self, input: &'a Input, range: std::ops::Range, scratch: Option<&'a mut [std::mem::MaybeUninit]>) -> crate::gnode::BatchStatus<'a, Self::Output> + fn eval_batch<'a>(&self, input: &'a Input, range: std::ops::Range, scratch: Option<&'a mut [std::mem::MaybeUninit]>) -> crate::node::BatchStatus<'a, Self::Output> where Input: crate::context::InjectIndex + Copy, { @@ -179,23 +178,23 @@ unsafe impl Send for EdgeHandle {} unsafe impl Sync for EdgeHandle {} impl EdgeHandle { - pub fn new(node: std::sync::Arc>) -> Self { + pub fn new(node: std::sync::Arc>) -> Self { Self::new_erased(node, edge_type::()) } - pub fn new_ref(node: std::sync::Arc>) -> Self { + pub fn new_ref(node: std::sync::Arc>) -> Self { Self::new_erased(node, lend_edge_type::()) } pub fn new_erased(node: std::sync::Arc, ty: Type) -> Self where - N: for<'c> GNode>, + N: for<'c> Node>, SharedEdge: WasmNotSend + WasmNotSync, { Self { node: Box::new(SharedEdge::new(node)), share: |edge| Box::new(edge.downcast_ref::>().expect("share hook matches the stored edge type").share()), - serialize: |edge| GNode::::serialize(edge.downcast_ref::>().expect("serialize hook matches the stored edge type")), + serialize: |edge| Node::::serialize(edge.downcast_ref::>().expect("serialize hook matches the stored edge type")), ty, } } @@ -217,11 +216,11 @@ impl EdgeHandle { (self.serialize)(&*self.node) } - pub fn downcast(self) -> Result>, ConstructionError> { + pub fn downcast(self) -> Result>, ConstructionError> { self.downcast_erased(edge_type::()) } - pub fn downcast_lend(self) -> Result>, ConstructionError> { + pub fn downcast_lend(self) -> Result>, ConstructionError> { self.downcast_erased(lend_edge_type::()) } @@ -257,7 +256,6 @@ pub fn construct(entry: &RegistryEntry, inputs: Vec) -> Result = Pin + 'n>>; #[cfg(not(target_family = "wasm"))] pub type Any<'n> = Box + 'n + Send>; #[cfg(target_family = "wasm")] @@ -275,7 +273,7 @@ mod tests { struct CountingNode(AtomicU32); - impl GNode for CountingNode { + impl Node for CountingNode { type Output = u32; fn eval(&self, _input: &Input) -> GPoll { @@ -285,7 +283,7 @@ mod tests { struct ValueNode(T); - impl GNode for ValueNode { + impl Node for ValueNode { type Output = T; fn eval(&self, _input: &Input) -> GPoll { @@ -295,7 +293,7 @@ mod tests { struct LendNode(String); - impl<'e, Input: Ctx + ExtractArena> GNode for LendNode { + impl<'e, Input: Ctx + ExtractArena> Node for LendNode { type Output = &'e String; fn eval(&self, input: &Input) -> GPoll<&'e String> { @@ -318,10 +316,10 @@ mod tests { content: Node0, } - impl<'e, Input, Node0> GNode for SplitNode + impl<'e, Input, Node0> Node for SplitNode where Input: Ctx, - Node0: GNode, + Node0: Node, { type Output = SplitBorrow<'e>; @@ -330,14 +328,14 @@ mod tests { } } - type ErasedSplitEdge = dyn for<'c> GNode, Output = SplitBorrow<'c>> + Send + Sync; + type ErasedSplitEdge = dyn for<'c> Node, Output = SplitBorrow<'c>> + Send + Sync; let arena = Arena::new(4096); let generations = []; let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let lending = EdgeHandle::new_ref(Arc::new(LendNode("held".to_string())) as Arc>); + let lending = EdgeHandle::new_ref(Arc::new(LendNode("held".to_string())) as Arc>); let upstream = lending.downcast_lend::().unwrap(); let node: Arc = Arc::new(SplitNode { content: upstream }); let handle = EdgeHandle::new_erased(node, concrete!(SplitBorrow<'static>)); @@ -359,10 +357,10 @@ mod tests { content: Node0, } - impl GNode for RepeatNode + impl Node for RepeatNode where C: Ctx + DeriveCtx, - Node0: for<'x> GNode, Output = T>, + Node0: for<'x> Node, Output = T>, { type Output = Vec; @@ -382,7 +380,7 @@ mod tests { struct LevelsNode; - impl GNode for LevelsNode { + impl Node for LevelsNode { type Output = Vec; fn eval(&self, input: &Input) -> GPoll> { @@ -398,7 +396,7 @@ mod tests { let nested = RepeatNode { content: RepeatNode { content: LevelsNode }, }; - let erased: Box>>>> = Box::new(nested); + let erased: Box>>>> = Box::new(nested); let GPoll::Final(outer) = erased.eval(&ctx) else { panic!("nested repeat must evaluate"); @@ -417,10 +415,10 @@ mod tests { content: Node0, } - impl GNode for ShiftFootprintNode + impl Node for ShiftFootprintNode where C: Ctx + DeriveCtx + ExtractFootprint, - Node0: for<'x> GNode, Output = T>, + Node0: for<'x> Node, Output = T>, { type Output = T; @@ -434,7 +432,7 @@ mod tests { struct ResolutionNode; - impl GNode for ResolutionNode { + impl Node for ResolutionNode { type Output = u32; fn eval(&self, input: &Input) -> GPoll { @@ -447,7 +445,7 @@ mod tests { let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let graph: Box> = Box::new(ShiftFootprintNode { + let graph: Box> = Box::new(ShiftFootprintNode { content: ShiftFootprintNode { content: ResolutionNode }, }); assert_eq!(graph.eval(&ctx), GPoll::Final(Footprint::DEFAULT.resolution.x + 14)); @@ -459,19 +457,19 @@ mod tests { let mut args = args.into_iter(); let value = args.next().ok_or(ConstructionError::Arity { expected: 1, got: 0 })?.downcast::()?; drop(value); - Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc>)) + Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc>)) } let entry = RegistryEntry { io: NodeIOTypes::new(concrete!(Context), concrete!(u32), vec![edge_type::()]), constructor: construct_strlen, }; - let owned = EdgeHandle::new(Arc::new(ValueNode("typed".to_string())) as Arc>); + let owned = EdgeHandle::new(Arc::new(ValueNode("typed".to_string())) as Arc>); assert!(construct(&entry, vec![owned]).is_ok()); assert_eq!(construct(&entry, vec![]).unwrap_err(), ConstructionError::Arity { expected: 1, got: 0 }); - let mistyped = EdgeHandle::new(Arc::new(ValueNode(1.0f64)) as Arc>); + let mistyped = EdgeHandle::new(Arc::new(ValueNode(1.0f64)) as Arc>); assert_eq!( construct(&entry, vec![mistyped]).unwrap_err(), ConstructionError::Type { @@ -480,7 +478,7 @@ mod tests { } ); - let lent = EdgeHandle::new_ref(Arc::new(LendNode("typed".to_string())) as Arc>); + let lent = EdgeHandle::new_ref(Arc::new(LendNode("typed".to_string())) as Arc>); assert_eq!( construct(&entry, vec![lent]).unwrap_err(), ConstructionError::Type { @@ -497,7 +495,7 @@ mod tests { let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let handle = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc>); + let handle = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc>); let duplicate = handle.duplicate(); assert_eq!(*duplicate.ty(), edge_type::()); diff --git a/node-graph/libraries/core-types/src/runtime.rs b/node-graph/libraries/core-types/src/runtime.rs index 164f5bfe80..a7a23e9072 100644 --- a/node-graph/libraries/core-types/src/runtime.rs +++ b/node-graph/libraries/core-types/src/runtime.rs @@ -152,8 +152,8 @@ mod tests { use super::*; use crate::arena::Arena; use crate::context::{ContextImpl, Ctx, CtxSnapshot, EvalScope, ExtractFootprint, ExtractVarArgs, VarArgLink, VarArgSlots}; - use crate::gnode::GNode; use crate::gpoll::GPoll; + use crate::node::Node; use crate::transform::Footprint; use std::sync::Mutex; use std::sync::atomic::{AtomicU32, Ordering}; @@ -208,7 +208,7 @@ mod tests { struct SourceNode(T); - impl GNode for SourceNode { + impl Node for SourceNode { type Output = T; fn eval(&self, _input: &Input) -> GPoll { @@ -264,7 +264,7 @@ mod tests { struct GatedSource(Arc, f64); - impl GNode for GatedSource { + impl Node for GatedSource { type Output = f64; fn eval(&self, _input: &Input) -> GPoll { @@ -289,13 +289,13 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = SlowDoubleNode::new(SourceNode(21.0f64), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(7u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); assert_eq!(SLOW_DOUBLE_RUNS.load(Ordering::Relaxed), 0); assert_eq!(runtime.drain(), vec![7]); assert_eq!(SLOW_DOUBLE_RUNS.load(Ordering::Relaxed), 1); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(42.0)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(42.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(42.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(42.0)); assert_eq!(SLOW_DOUBLE_RUNS.load(Ordering::Relaxed), 1); } @@ -309,9 +309,9 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = PreviewDoubleNode::new(SourceNode(21.0f64), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(1u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Partial(-1.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Partial(-1.0)); runtime.drain(); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(42.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(42.0)); } #[test] @@ -324,9 +324,9 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = StrictDoubleNode::new(SourceNode(21.0f64), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(2u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); runtime.drain(); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(42.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(42.0)); } #[test] @@ -339,12 +339,12 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = StagedDoubleNode::new(SourceNode(21.0f64), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(8u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); assert_eq!(STAGED_RUNS.load(Ordering::Relaxed), 1, "the prologue runs synchronously on the miss"); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); assert_eq!(STAGED_RUNS.load(Ordering::Relaxed), 1, "in flight must not rerun the prologue"); assert_eq!(runtime.drain(), vec![8]); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(42.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(42.0)); assert_eq!(STAGED_RUNS.load(Ordering::Relaxed), 1); } @@ -359,12 +359,12 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = StagedSumNode::new(SourceNode(40.0f64), GatedSource(gate.clone(), 2.0), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(9u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); assert_eq!(runtime.drain(), Vec::::new(), "an interrupted prologue must not spawn or claim the slot"); gate.store(true, Ordering::Relaxed); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); assert_eq!(runtime.drain(), vec![9]); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(42.0)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(42.0)); } #[test] @@ -383,9 +383,9 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = SnapshotVarargNode::new(SourceNode(()), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(5u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); runtime.drain(); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(21.5)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(21.5)); } #[test] @@ -400,9 +400,9 @@ mod tests { let runtime = Arc::new(MockRuntime::default()); let graph = SnapshotResolutionNode::new(SourceNode(()), SourceNode(RuntimeHandle(runtime.clone())), SourceNode(3u64)); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); runtime.drain(); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Final(Footprint::DEFAULT.resolution.x)); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Final(Footprint::DEFAULT.resolution.x)); } #[test] @@ -492,7 +492,7 @@ mod tests { let snapshot = runtime.snapshot(); let scope = EvalScope::new(None, None, None, &snapshot, &arena); let ctx = ContextImpl::root(&scope); - assert_eq!(GNode::eval(&graph, &ctx), GPoll::Pending); + assert_eq!(Node::eval(&graph, &ctx), GPoll::Pending); assert!(!runtime.take_dirty()); assert_eq!(runtime.spawner().drain(), 1); @@ -502,7 +502,7 @@ mod tests { let bumped_scope = EvalScope::new(None, None, None, &bumped, &arena); let bumped_ctx = ContextImpl::root(&bumped_scope); - assert_eq!(GNode::eval(&graph, &bumped_ctx), GPoll::Final(42.0), "the own-generation-excluded key replays the landed slot"); + assert_eq!(Node::eval(&graph, &bumped_ctx), GPoll::Final(42.0), "the own-generation-excluded key replays the landed slot"); assert_eq!(runtime.spawner().drain(), 0, "a slot hit must not respawn"); let downstream_key = crate::registry::cache_key(&ContextImpl::root(&scope)); diff --git a/node-graph/libraries/core-types/src/value.rs b/node-graph/libraries/core-types/src/value.rs index b57078e254..7fc7852415 100644 --- a/node-graph/libraries/core-types/src/value.rs +++ b/node-graph/libraries/core-types/src/value.rs @@ -1,104 +1,7 @@ -use crate::Node; -use std::cell::{Cell, RefCell, RefMut}; -use std::marker::PhantomData; - -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct IntNode; - -impl<'i, const N: u32, I> Node<'i, I> for IntNode { - type Output = u32; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - N - } -} - -#[derive(Default, Debug, Clone, Copy)] -pub struct ValueNode(pub T); - -impl<'i, T: 'i, I> Node<'i, I> for ValueNode { - type Output = &'i T; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - &self.0 - } -} - -impl ValueNode { - pub const fn new(value: T) -> ValueNode { - ValueNode(value) - } -} - -impl From for ValueNode { - fn from(value: T) -> Self { - ValueNode::new(value) - } -} - -#[derive(Default, Debug, Clone, Copy)] -pub struct AsRefNode, U>(pub T, PhantomData); - -impl<'i, T: 'i + AsRef, U: 'i> Node<'i, ()> for AsRefNode { - type Output = &'i U; - #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { - self.0.as_ref() - } -} - -impl, U> AsRefNode { - pub const fn new(value: T) -> AsRefNode { - AsRefNode(value, PhantomData) - } -} - -#[derive(Default, Debug, Clone)] -pub struct RefCellMutNode(pub RefCell); - -impl<'i, T: 'i> Node<'i, ()> for RefCellMutNode { - type Output = RefMut<'i, T>; - #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { - self.0.borrow_mut() - } -} - -impl RefCellMutNode { - pub const fn new(value: T) -> RefCellMutNode { - RefCellMutNode(RefCell::new(value)) - } -} - -#[derive(Default)] -pub struct OnceCellNode(pub Cell); - -impl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode { - type Output = T; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - self.0.replace(T::default()) - } -} - -impl OnceCellNode { - pub const fn new(value: T) -> OnceCellNode { - OnceCellNode(Cell::new(value)) - } -} - #[derive(Clone, Copy)] pub struct ClonedNode(pub T); -impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode { - type Output = T; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - self.0.clone() - } -} - -impl crate::gnode::GNode for ClonedNode { +impl crate::node::Node for ClonedNode { type Output = T; fn eval(&self, _input: &Input) -> crate::gpoll::GPoll { @@ -107,7 +10,7 @@ impl crate::gnode::GNode for ClonedNode { } pub fn value_edge(value: T) -> crate::registry::EdgeHandle { - crate::registry::EdgeHandle::new(std::sync::Arc::new(ClonedNode(value)) as std::sync::Arc>) + crate::registry::EdgeHandle::new(std::sync::Arc::new(ClonedNode(value)) as std::sync::Arc>) } impl ClonedNode { @@ -121,103 +24,3 @@ impl From for ClonedNode { ClonedNode::new(value) } } - -#[derive(Clone, Copy)] -/// The DebugClonedNode logs every time it is evaluated. -/// This is useful for debugging. -pub struct DebugClonedNode(pub T); - -impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode { - type Output = T; - #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { - // KEEP THIS `debug!()` - It acts as the output for the debug node itself - log::debug!("DebugClonedNode::eval"); - - self.0.clone() - } -} - -impl DebugClonedNode { - pub const fn new(value: T) -> DebugClonedNode { - DebugClonedNode(value) - } -} - -#[derive(Clone, Copy)] -pub struct CopiedNode(pub T); - -impl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode { - type Output = T; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - self.0 - } -} - -impl CopiedNode { - pub const fn new(value: T) -> CopiedNode { - CopiedNode(value) - } -} - -#[derive(Default)] -pub struct DefaultNode(PhantomData); - -impl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode { - type Output = T; - fn eval(&'i self, _input: I) -> Self::Output { - T::default() - } -} - -impl DefaultNode { - pub fn new() -> Self { - Self(PhantomData) - } -} - -#[repr(C)] -/// Return the unit value -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct ForgetNode; - -impl<'i, T: 'i> Node<'i, T> for ForgetNode { - type Output = (); - fn eval(&'i self, _input: T) -> Self::Output {} -} - -impl ForgetNode { - pub const fn new() -> Self { - ForgetNode - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_int_node() { - let node = IntNode::<5>; - assert_eq!(node.eval(()), 5); - } - #[test] - fn test_value_node() { - let node = ValueNode::new(5); - assert_eq!(node.eval(()), &5); - let type_erased = &node as &dyn for<'a> Node<'a, (), Output = &'a i32>; - assert_eq!(type_erased.eval(()), &5); - } - #[test] - fn test_default_node() { - let node = DefaultNode::::new(); - assert_eq!(node.eval(42), 0); - } - #[test] - #[allow(clippy::unit_cmp)] - fn test_unit_node() { - let node = ForgetNode::new(); - assert_eq!(node.eval(()), ()); - } -} diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 4507b3eaa8..751b3ac7bb 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -1,10 +1,12 @@ +use crate::crate_ident::CrateIdent; use crate::parsing::*; use convert_case::{Case, Casing}; use proc_macro2::TokenStream as TokenStream2; use quote::{ToTokens, format_ident, quote}; use std::sync::atomic::AtomicU64; use syn::punctuated::Punctuated; -use syn::{Ident, PatIdent}; +use syn::visit::Visit; +use syn::{GenericArgument, GenericParam, Ident, Lifetime, PatIdent, PathArguments, Type, TypeParam, TypeParamBound}; static NODE_ID: AtomicU64 = AtomicU64::new(0); pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result { @@ -111,8 +113,8 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn quote! { pub(super) #name: #r#gen } }); - let async_source = *is_async || crate::gcodegen::is_source_kernel(output_type); - let slot_value_type = crate::gcodegen::slot_value_type(output_type); + let async_source = *is_async || is_source_kernel(output_type); + let slot_value_type = slot_value_type(output_type); let slot_field = async_source .then(|| quote! { pub(super) slot: std::sync::Arc>>>> }) .into_iter(); @@ -253,11 +255,11 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn } }; let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake)); - let gnode = crate::gcodegen::generate_gnode_code(crate_ident, parsed)?; - let gnode_in_mod = gnode.in_mod; - let gnode_top_level = gnode.top_level; + let node = generate_node_impl(crate_ident, parsed)?; + let node_in_mod = node.in_mod; + let node_top_level = node.top_level; let entries_name = format_ident!("{}_entries", parsed.fn_name); - let register_entries = match gnode_in_mod.is_empty() { + let register_entries = match node_in_mod.is_empty() { true => quote!(), false => quote!(gcore::registry::NODE_REGISTRY.lock().unwrap().entry(#identifier()).or_default().extend(#entries_name());), }; @@ -296,7 +298,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn Ok(quote! { #(#description_doc_attrs)* - #gnode_top_level + #node_top_level #cfg const fn #identifier() -> #core_types::ProtoNodeIdentifier { @@ -340,7 +342,7 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn } } - #gnode_in_mod + #node_in_mod #register_node_impl @@ -478,10 +480,8 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator bool { syn::visit::visit_type(&mut checker, ty); checker.found } + +pub(crate) struct NodeImplTokens { + pub(crate) in_mod: TokenStream2, + pub(crate) top_level: TokenStream2, +} + +pub(crate) fn generate_node_impl(crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result { + let core_types = crate_ident.gcore()?; + + let ctx_param = context_param(parsed); + let ctx_ident = match ctx_param { + Some(ctx_param) => ctx_param.ident.clone(), + None => format_ident!("__Ctx"), + }; + let async_fn = parsed.is_async; + let future_kernel = is_source_kernel(&parsed.output_type); + let async_source = async_fn || future_kernel; + if async_fn && parsed.fields.iter().any(|field| matches!(field.ty, ParsedFieldType::Node(_))) { + return Ok(NodeImplTokens { + in_mod: quote!(), + top_level: quote!(), + }); + } + let snapshot_ctx = async_fn && matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot")); + + let mut ctx_bounds: Vec = match ctx_param { + Some(ctx_param) => ctx_param + .bounds + .iter() + .filter_map(|bound| match bound { + TypeParamBound::Lifetime(_) => None, + bound => Some(desugar_extract_lifetime(bound, core_types)), + }) + .collect(), + None => vec![quote!(#core_types::Ctx)], + }; + if async_source && !snapshot_ctx { + ctx_bounds.push(quote!(#core_types::context::DeriveCtx)); + } + if snapshot_ctx { + ctx_bounds.extend([ + quote!(#core_types::context::DeriveCtx), + quote!(#core_types::context::ExtractFootprint), + quote!(#core_types::context::ExtractRealTime), + quote!(#core_types::context::ExtractAnimationTime), + quote!(#core_types::context::ExtractPointerPosition), + quote!(#core_types::context::ExtractIndex), + quote!(#core_types::context::ExtractPosition), + ]); + } + + let derives = ctx_param.is_some_and(|ctx_param| { + ctx_param.bounds.iter().any(|bound| match bound { + TypeParamBound::Trait(trait_bound) => trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx"), + _ => false, + }) + }); + + let ctx_generic = match ctx_bounds.is_empty() { + true => quote!(#ctx_ident), + false => quote!(#ctx_ident: #(#ctx_bounds)+*), + }; + let mut generics: Vec = parsed + .fn_generics + .iter() + .map(|param| match param { + GenericParam::Type(type_param) if Some(&type_param.ident) == ctx_param.map(|ctx_param| &ctx_param.ident) => ctx_generic.clone(), + param => quote!(#param), + }) + .collect(); + if ctx_param.is_none() { + generics.push(ctx_generic); + } + + let fn_name = &parsed.fn_name; + let mod_name = format_ident!("_{}_mod", parsed.mod_name); + let struct_name = format_ident!("{}Node", parsed.struct_name); + let output_type = &parsed.output_type; + let trait_output = slot_value_type(&parsed.output_type); + let raw_lazy = matches!(kernel_kind(&parsed.output_type), KernelKind::Poll(_)); + let injected_name = |ident: &Ident| async_source && (ident == "_runtime" || ident == "_source"); + let where_predicates: Vec = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect(); + + let (data_fields, regular_fields): (Vec<_>, Vec<_>) = parsed.fields.iter().partition(|field| field.is_data_field); + + let data_field_generic_idents: Vec = parsed + .fn_generics + .iter() + .filter_map(|generic| match generic { + GenericParam::Type(type_param) => Some(type_param.ident.clone()), + _ => None, + }) + .filter(|ident| { + data_fields.iter().any(|field| match &field.ty { + ParsedFieldType::Regular(RegularParsedField { ty, .. }) => crate::codegen::type_contains_ident(ty, ident), + _ => false, + }) + }) + .collect(); + + let node_generics: Vec = regular_fields.iter().enumerate().map(|(index, _)| format_ident!("Node{}", index)).collect(); + let struct_type_params: Vec = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect(); + + let data_names: Vec<&Ident> = data_fields.iter().map(|field| &field.pat_ident.ident).collect(); + let data_params = data_fields.iter().map(|field| { + let pat = &field.pat_ident; + let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { + unreachable!("data fields are regular types"); + }; + quote!(#pat: &#ty) + }); + + let lazy_bound = |output_type: &Type| match derives { + true => quote!(for<'__derived> #core_types::node::Node<#core_types::context::Derived<'__derived, #ctx_ident>, Output = #output_type>), + false => quote!(#core_types::node::Node<#ctx_ident, Output = #output_type>), + }; + + let kernel_params = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).map(|field| { + let pat = &field.pat_ident; + match &field.ty { + ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty), + ParsedFieldType::Node(NodeParsedField { output_type, .. }) if raw_lazy => { + let bound = lazy_bound(output_type); + quote!(#pat: &impl #bound) + } + ParsedFieldType::Node(NodeParsedField { output_type, .. }) => { + let bound = lazy_bound(output_type); + quote!(#pat: #core_types::node::LazyInput<'_, impl #bound>) + } + } + }); + + let node_bounds = regular_fields.iter().zip(&node_generics).map(|(field, node_generic)| match &field.ty { + ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#node_generic: #core_types::node::Node<#ctx_ident, Output = #ty>), + ParsedFieldType::Node(NodeParsedField { output_type, .. }) => { + let bound = lazy_bound(output_type); + quote!(#node_generic: #bound) + } + }); + + let mut async_bounds = match (async_fn, future_kernel) { + (false, false) => Vec::new(), + (false, true) => vec![quote!(#trait_output: Clone)], + (true, _) => { + let output_clone = std::iter::once(quote!(#trait_output: Clone)); + let value_clones = regular_fields.iter().filter_map(|field| match &field.ty { + ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: Clone)), + _ => None, + }); + let data_clones = data_fields.iter().filter_map(|field| match &field.ty { + ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: Clone)), + _ => None, + }); + output_clone.chain(value_clones).chain(data_clones).collect() + } + }; + if async_source { + async_bounds.push(quote!(for<'__derived> #core_types::context::Derived<'__derived, #ctx_ident>: #core_types::CacheHash)); + } + + let clampable_bounds = regular_fields.iter().filter_map(|field| { + let ParsedFieldType::Regular(RegularParsedField { + ty, number_hard_min, number_hard_max, .. + }) = &field.ty + else { + return None; + }; + (number_hard_min.is_some() || number_hard_max.is_some()).then(|| quote!(#ty: #core_types::misc::Clampable)) + }); + + let eval_values = regular_fields.iter().enumerate().map(|(index, field)| { + let name = &field.pat_ident.ident; + match &field.ty { + ParsedFieldType::Regular(_) => quote! { + let #name = match __cell.eval_input(#index, &self.#name, __input) { + Ok(value) => value, + Err(interrupt) => return interrupt.into(), + }; + }, + ParsedFieldType::Node(_) if raw_lazy => quote!(), + ParsedFieldType::Node(_) => quote! { + let #name = #core_types::node::LazyInput::new(&self.#name, &__cell, #index); + }, + } + }); + + let clamps = regular_fields.iter().filter_map(|field| { + let ParsedFieldType::Regular(RegularParsedField { number_hard_min, number_hard_max, .. }) = &field.ty else { + return None; + }; + let name = &field.pat_ident.ident; + let mut tokens = quote!(); + if let Some(min) = number_hard_min { + tokens.extend(quote!(let #name = #core_types::misc::Clampable::clamp_hard_min(#name, #min);)); + } + if let Some(max) = number_hard_max { + tokens.extend(quote!(let #name = #core_types::misc::Clampable::clamp_hard_max(#name, #max);)); + } + (!tokens.is_empty()).then_some(tokens) + }); + + let call_args = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).map(|field| { + let name = &field.pat_ident.ident; + match &field.ty { + ParsedFieldType::Node(_) if raw_lazy => quote!(&self.#name), + _ => quote!(#name), + } + }); + + let value_field_names: Vec<&Ident> = regular_fields + .iter() + .filter(|field| matches!(field.ty, ParsedFieldType::Regular(_))) + .map(|field| &field.pat_ident.ident) + .collect(); + + let extent_impl = match &parsed.attributes.extent { + Some(path) => quote! { + fn extent(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { + #path(self, __input) + } + }, + None if value_field_names.is_empty() => quote!(), + None => { + let first = value_field_names[0]; + let mut meet = quote!(self.#first.extent(__input)); + for name in &value_field_names[1..] { + meet = quote!(#core_types::gpoll::Extent::meet(#meet, self.#name.extent(__input))); + } + quote! { + fn extent(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { + #meet + } + } + } + }; + + let serialize_impl = match &parsed.attributes.serialize { + Some(path) => { + let data_refs = data_names.iter().map(|name| quote!(&self.#name)); + quote! { + fn serialize(&self) -> Option<::std::sync::Arc> { + #path(#(#data_refs),*) + } + } + } + None => quote!(), + }; + + let batch_impl = match &parsed.attributes.batch { + Some(path) => quote! { + fn eval_batch<'__batch>( + &self, + __input: &'__batch #ctx_ident, + __range: ::std::ops::Range, + __scratch: Option<&'__batch mut [::std::mem::MaybeUninit]>, + ) -> #core_types::node::BatchStatus<'__batch, Self::Output> + where + #ctx_ident: #core_types::context::InjectIndex + Copy, + { + #path(self, __input, __range, __scratch) + } + }, + None => quote!(), + }; + + let ctx_pat = &parsed.input.pat_ident; + let fn_where = &parsed.where_clause; + let body = &parsed.body; + let vis = &parsed.vis; + let kernel_fields: Vec<&&ParsedField> = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).collect(); + let kernel = match async_fn { + false => quote! { + #[allow(clippy::too_many_arguments)] + #vis fn #fn_name<#(#generics,)*>(#ctx_pat: &#ctx_ident #(, #data_params)* #(, #kernel_params)*) -> #output_type #fn_where #body + }, + true => { + let kernel_generics = parsed.fn_generics.iter().filter(|param| match param { + GenericParam::Type(type_param) => Some(&type_param.ident) != ctx_param.map(|ctx_param| &ctx_param.ident), + _ => true, + }); + let snapshot_param = snapshot_ctx.then(|| quote!(#ctx_pat: #core_types::context::CtxSnapshot)).into_iter(); + let data_kernel_params = data_fields.iter().map(|field| { + let pat = &field.pat_ident; + let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { + unreachable!("data fields are regular types"); + }; + quote!(#pat: #ty) + }); + let value_kernel_params = kernel_fields.iter().map(|field| { + let pat = &field.pat_ident; + let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { + unreachable!("async source fields are eager values"); + }; + quote!(#pat: #ty) + }); + let params = snapshot_param.chain(data_kernel_params).chain(value_kernel_params); + quote! { + #[allow(clippy::too_many_arguments)] + #vis async fn #fn_name<#(#kernel_generics,)*>(#(#params),*) -> #output_type #fn_where #body + } + } + }; + let cell_constructor = match parsed.attributes.no_partial { + true => quote!(#core_types::node::StatusCell::no_partial()), + false => quote!(#core_types::node::StatusCell::new()), + }; + let kernel_call = quote!(self::#fn_name(__input #(, &self.#data_names)* #(, #call_args)*)); + let lift = match kernel_kind(&parsed.output_type) { + KernelKind::Interrupt(_) => quote! { + match #kernel_call { + Ok(value) => __cell.finish(value), + Err(interrupt) => interrupt.into(), + } + }, + KernelKind::Poll(_) => quote!(__cell.merge(#kernel_call)), + _ => quote!(__cell.finish(#kernel_call)), + }; + + let placeholder_value_names: Vec<&Ident> = kernel_fields + .iter() + .filter(|field| matches!(field.ty, ParsedFieldType::Regular(_))) + .map(|field| &field.pat_ident.ident) + .collect(); + let inflight = match &parsed.attributes.placeholder { + Some(path) => quote!(__cell.merge(#core_types::gpoll::GPoll::Partial(#path(#(&#placeholder_value_names),*)))), + None => quote!(#core_types::gpoll::GPoll::Pending), + }; + let slot_check = quote! { + let __scope = #core_types::context::DeriveCtx::scope(__input).excluding(_source); + let __key = #core_types::registry::cache_key(&#core_types::context::DeriveCtx::with_scope(__input, &__scope)); + { + let __entries = self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some(__state) = __entries.get(&__key) { + return match __state { + Some(value) => __cell.merge(value.clone()), + None => #inflight, + }; + } + } + }; + let future_completion = |payload: &Type| match kernel_kind(payload) { + KernelKind::Poll(_) => quote!(__future.await), + KernelKind::Interrupt(_) => quote! { + match __future.await { + Ok(value) => #core_types::gpoll::GPoll::Final(value), + Err(interrupt) => interrupt.into(), + } + }, + _ => quote!(#core_types::gpoll::GPoll::Final(__future.await)), + }; + let eval_tail = match (async_fn, future_kernel) { + (false, false) => lift, + (true, _) => { + let kernel_value_names: Vec<&Ident> = kernel_fields.iter().map(|field| &field.pat_ident.ident).collect(); + let snapshot_binding = snapshot_ctx.then(|| quote!(let __snapshot = #core_types::context::CtxSnapshot::capture(__input);)).into_iter(); + let snapshot_arg = snapshot_ctx.then(|| quote!(__snapshot)).into_iter(); + let future_args = snapshot_arg + .chain(data_names.iter().map(|name| quote!(self.#name.clone()))) + .chain(kernel_value_names.iter().map(|name| quote!(#name.clone()))); + let completion = future_completion(&parsed.output_type); + quote! { + #slot_check + self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, None); + let __slot = std::sync::Arc::clone(&self.slot); + #(#snapshot_binding)* + let __future = self::#fn_name(#(#future_args),*); + _runtime.0.spawn(_source, Box::pin(async move { + let __value = #completion; + __slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, Some(__value)); + })); + #inflight + } + } + (false, true) => { + let (placeholder_binding, spawn_return) = match &parsed.attributes.placeholder { + Some(path) => ( + quote!(let __placeholder = #path(#(&#placeholder_value_names),*);), + quote!(__cell.merge(#core_types::gpoll::GPoll::Partial(__placeholder))), + ), + None => (quote!(), quote!(#core_types::gpoll::GPoll::Pending)), + }; + let acquire = match kernel_kind(&parsed.output_type) { + KernelKind::FutureInterrupt(_) => quote! { + let __future = match #kernel_call { + Ok(future) => future, + Err(interrupt) => return interrupt.into(), + }; + }, + _ => quote!(let __future = #kernel_call;), + }; + let payload = match kernel_kind(&parsed.output_type) { + KernelKind::Future(payload) | KernelKind::FutureInterrupt(payload) => payload, + _ => unreachable!("guarded by future_kernel"), + }; + let completion = future_completion(&payload); + quote! { + #slot_check + #placeholder_binding + #acquire + self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, None); + let __slot = std::sync::Arc::clone(&self.slot); + _runtime.0.spawn(_source, Box::pin(async move { + let __value = #completion; + __slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, Some(__value)); + })); + #spawn_return + } + } + }; + + let entries = entries_tokens(parsed, &struct_name, &data_field_generic_idents, ®ular_fields); + let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes); + let entries_reexport = match entries.is_empty() { + true => quote!(), + false => { + let entries_name = format_ident!("{}_entries", fn_name); + quote! { + #cfg + #[doc(hidden)] + pub use #mod_name::#entries_name; + } + } + }; + + let top_level = quote! { + #entries_reexport + + #cfg + #[automatically_derived] + impl<#(#generics,)* #(#node_generics,)*> #core_types::node::Node<#ctx_ident> for #mod_name::#struct_name<#(#struct_type_params,)*> + where + #(#node_bounds,)* + #(#clampable_bounds,)* + #(#async_bounds,)* + #(#where_predicates,)* + { + type Output = #trait_output; + + fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll { + let __cell = #cell_constructor; + #(#eval_values)* + #(#clamps)* + #eval_tail + } + + #extent_impl + + #serialize_impl + + #batch_impl + } + }; + + Ok(NodeImplTokens { + in_mod: entries, + top_level: quote! { + #kernel + + #top_level + }, + }) +} + +pub(crate) fn slot_value_type(output: &Type) -> Type { + match kernel_kind(output) { + KernelKind::Plain => output.clone(), + KernelKind::Poll(inner) | KernelKind::Interrupt(inner) => inner, + KernelKind::Future(payload) | KernelKind::FutureInterrupt(payload) => match kernel_kind(&payload) { + KernelKind::Poll(inner) | KernelKind::Interrupt(inner) => inner, + _ => payload, + }, + } +} + +pub(crate) fn is_source_kernel(output: &Type) -> bool { + matches!(kernel_kind(output), KernelKind::Future(_) | KernelKind::FutureInterrupt(_)) +} + +enum KernelKind { + Plain, + Interrupt(Type), + Poll(Type), + Future(Type), + FutureInterrupt(Type), +} + +fn source_future_payload(segment: &syn::PathSegment) -> Type { + let PathArguments::AngleBracketed(args) = &segment.arguments else { + return syn::parse_quote!(()); + }; + args.args + .iter() + .find_map(|argument| match argument { + GenericArgument::Type(ty) => Some(ty.clone()), + _ => None, + }) + .unwrap_or_else(|| syn::parse_quote!(())) +} + +fn kernel_kind(output: &Type) -> KernelKind { + let plain = || KernelKind::Plain; + let Type::Path(path) = output else { return plain() }; + let Some(segment) = path.path.segments.last() else { return plain() }; + match segment.ident.to_string().as_str() { + "GPoll" => { + let PathArguments::AngleBracketed(args) = &segment.arguments else { return plain() }; + let inner = args.args.iter().find_map(|argument| match argument { + GenericArgument::Type(ty) => Some(ty.clone()), + _ => None, + }); + inner.map(KernelKind::Poll).unwrap_or_else(plain) + } + "SourceFuture" => KernelKind::Future(source_future_payload(segment)), + "Result" => { + let PathArguments::AngleBracketed(args) = &segment.arguments else { return plain() }; + let mut types = args.args.iter().filter_map(|argument| match argument { + GenericArgument::Type(ty) => Some(ty), + _ => None, + }); + let (Some(inner), Some(Type::Path(error_path))) = (types.next(), types.next()) else { + return plain(); + }; + if !error_path.path.segments.last().is_some_and(|segment| segment.ident == "Interrupt") { + return plain(); + } + if let Type::Path(inner_path) = inner { + if let Some(inner_segment) = inner_path.path.segments.last() { + if inner_segment.ident == "SourceFuture" { + return KernelKind::FutureInterrupt(source_future_payload(inner_segment)); + } + } + } + KernelKind::Interrupt(inner.clone()) + } + _ => plain(), + } +} + +fn context_param<'a>(parsed: &'a ParsedNodeFn) -> Option<&'a TypeParam> { + let Type::Path(path) = &parsed.input.ty else { + return None; + }; + let ident = path.path.get_ident()?; + parsed.fn_generics.iter().find_map(|param| match param { + GenericParam::Type(type_param) if &type_param.ident == ident => Some(type_param), + _ => None, + }) +} + +fn type_disqualifies(ty: &Type) -> bool { + struct Disqualifier { + found: bool, + } + + impl<'ast> Visit<'ast> for Disqualifier { + fn visit_type_reference(&mut self, _: &'ast syn::TypeReference) { + self.found = true; + } + + fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) { + self.found = true; + } + + fn visit_lifetime(&mut self, _: &'ast Lifetime) { + self.found = true; + } + } + + let mut visitor = Disqualifier { found: false }; + visitor.visit_type(ty); + visitor.found +} + +fn desugar_extract_lifetime(bound: &TypeParamBound, core_types: &TokenStream2) -> TokenStream2 { + let TypeParamBound::Trait(trait_bound) = bound else { + return quote!(#bound); + }; + let Some(segment) = trait_bound.path.segments.last() else { + return quote!(#bound); + }; + if segment.ident != "ExtractArena" { + return quote!(#bound); + } + let PathArguments::AngleBracketed(args) = &segment.arguments else { + return quote!(#bound); + }; + if args.args.len() != 1 { + return quote!(#bound); + } + let Some(GenericArgument::Lifetime(lifetime)) = args.args.first() else { + return quote!(#bound); + }; + quote!(#core_types::context::ExtractArena) +} + +fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic_idents: &[Ident], regular_fields: &[&ParsedField]) -> TokenStream2 { + if !data_field_generic_idents.is_empty() { + return quote!(); + } + let Some(rows) = implementation_rows(parsed, regular_fields) else { + return quote!(); + }; + let rows: Vec<&Vec> = rows.iter().filter(|row| row.iter().all(|ty| !type_disqualifies(ty))).collect(); + if rows.is_empty() { + return quote!(); + } + + let fn_name = &parsed.fn_name; + let entries_name = format_ident!("{}_entries", fn_name); + let arity = regular_fields.len(); + let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect(); + + let entries = rows.iter().map(|row| { + let types = row.iter(); + let edge_types = row.iter().map(|ty| quote!(gcore::registry::SharedEdge>)); + let output = quote!(<#struct_name<#(#edge_types),*> as gcore::node::Node>>::Output); + let downcasts = names.iter().zip(row.iter()).map(|(name, ty)| quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;)); + quote! { + gcore::registry::RegistryEntry { + io: gcore::registry::NodeIOTypes::new( + gcore::concrete!(gcore::context::ContextImpl<'static>), + gcore::concrete!(#output), + vec![#(gcore::registry::edge_type::<#types>()),*], + ), + constructor: |inputs| { + if inputs.len() != #arity { + return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() }); + } + let mut inputs = inputs.into_iter(); + #(#downcasts)* + Ok(gcore::registry::EdgeHandle::new(::std::sync::Arc::new(#struct_name::new(#(#names),*)) as ::std::sync::Arc>)) + }, + } + } + }); + + quote! { + pub fn #entries_name() -> ::std::vec::Vec { + vec![#(#entries),*] + } + } +} + +fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option>> { + let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone()); + let open_generics: Vec<&Ident> = parsed + .fn_generics + .iter() + .filter_map(|param| match param { + GenericParam::Type(type_param) if Some(&type_param.ident) != ctx_ident.as_ref() => Some(&type_param.ident), + _ => None, + }) + .collect(); + + let candidates: Vec> = regular_fields + .iter() + .map(|field| match &field.ty { + ParsedFieldType::Regular(RegularParsedField { ty, implementations, .. }) => match implementations.is_empty() { + false => Some(implementations.iter().cloned().collect()), + true => open_generics.iter().all(|generic| !crate::codegen::type_contains_ident(ty, generic)).then(|| vec![ty.clone()]), + }, + ParsedFieldType::Node(NodeParsedField { output_type, implementations, .. }) => match implementations.is_empty() { + false => Some(implementations.iter().map(|implementation| implementation.output.clone()).collect()), + true => open_generics + .iter() + .all(|generic| !crate::codegen::type_contains_ident(output_type, generic)) + .then(|| vec![output_type.clone()]), + }, + }) + .collect::>()?; + + let row_count = candidates.iter().map(|types| types.len()).max().unwrap_or(1).max(1); + Some((0..row_count).map(|row| candidates.iter().map(|types| types[row.min(types.len() - 1)].clone()).collect()).collect()) +} diff --git a/node-graph/node-macro/src/gcodegen.rs b/node-graph/node-macro/src/gcodegen.rs deleted file mode 100644 index a988cb06e9..0000000000 --- a/node-graph/node-macro/src/gcodegen.rs +++ /dev/null @@ -1,680 +0,0 @@ -use crate::crate_ident::CrateIdent; -use crate::parsing::*; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; -use syn::visit::Visit; -use syn::{GenericArgument, GenericParam, Ident, Lifetime, PathArguments, Type, TypeParam, TypeParamBound}; - -pub(crate) struct GNodeTokens { - pub(crate) in_mod: TokenStream2, - pub(crate) top_level: TokenStream2, -} - -pub(crate) fn generate_gnode_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result { - let core_types = crate_ident.gcore()?; - - let ctx_param = context_param(parsed); - let ctx_ident = match ctx_param { - Some(ctx_param) => ctx_param.ident.clone(), - None => format_ident!("__Ctx"), - }; - let async_fn = parsed.is_async; - let future_kernel = is_source_kernel(&parsed.output_type); - let async_source = async_fn || future_kernel; - if async_fn && parsed.fields.iter().any(|field| matches!(field.ty, ParsedFieldType::Node(_))) { - return Ok(GNodeTokens { - in_mod: quote!(), - top_level: quote!(), - }); - } - let snapshot_ctx = async_fn && matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot")); - - let mut ctx_bounds: Vec = match ctx_param { - Some(ctx_param) => ctx_param - .bounds - .iter() - .filter_map(|bound| match bound { - TypeParamBound::Lifetime(_) => None, - bound => Some(desugar_extract_lifetime(bound, core_types)), - }) - .collect(), - None => vec![quote!(#core_types::Ctx)], - }; - if async_source && !snapshot_ctx { - ctx_bounds.push(quote!(#core_types::context::DeriveCtx)); - } - if snapshot_ctx { - ctx_bounds.extend([ - quote!(#core_types::context::DeriveCtx), - quote!(#core_types::context::ExtractFootprint), - quote!(#core_types::context::ExtractRealTime), - quote!(#core_types::context::ExtractAnimationTime), - quote!(#core_types::context::ExtractPointerPosition), - quote!(#core_types::context::ExtractIndex), - quote!(#core_types::context::ExtractPosition), - ]); - } - - let derives = ctx_param.is_some_and(|ctx_param| { - ctx_param.bounds.iter().any(|bound| match bound { - TypeParamBound::Trait(trait_bound) => trait_bound.path.segments.last().is_some_and(|segment| segment.ident == "DeriveCtx"), - _ => false, - }) - }); - - let ctx_generic = match ctx_bounds.is_empty() { - true => quote!(#ctx_ident), - false => quote!(#ctx_ident: #(#ctx_bounds)+*), - }; - let mut generics: Vec = parsed - .fn_generics - .iter() - .map(|param| match param { - GenericParam::Type(type_param) if Some(&type_param.ident) == ctx_param.map(|ctx_param| &ctx_param.ident) => ctx_generic.clone(), - param => quote!(#param), - }) - .collect(); - if ctx_param.is_none() { - generics.push(ctx_generic); - } - - let fn_name = &parsed.fn_name; - let mod_name = format_ident!("_{}_mod", parsed.mod_name); - let struct_name = format_ident!("{}Node", parsed.struct_name); - let output_type = &parsed.output_type; - let trait_output = slot_value_type(&parsed.output_type); - let raw_lazy = matches!(kernel_kind(&parsed.output_type), KernelKind::Poll(_)); - let injected_name = |ident: &Ident| async_source && (ident == "_runtime" || ident == "_source"); - let where_predicates: Vec = parsed.where_clause.iter().flat_map(|clause| clause.predicates.iter()).map(|predicate| quote!(#predicate)).collect(); - - let (data_fields, regular_fields): (Vec<_>, Vec<_>) = parsed.fields.iter().partition(|field| field.is_data_field); - - let data_field_generic_idents: Vec = parsed - .fn_generics - .iter() - .filter_map(|generic| match generic { - GenericParam::Type(type_param) => Some(type_param.ident.clone()), - _ => None, - }) - .filter(|ident| { - data_fields.iter().any(|field| match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, .. }) => crate::codegen::type_contains_ident(ty, ident), - _ => false, - }) - }) - .collect(); - - let node_generics: Vec = regular_fields.iter().enumerate().map(|(index, _)| format_ident!("Node{}", index)).collect(); - let struct_type_params: Vec = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect(); - - let data_names: Vec<&Ident> = data_fields.iter().map(|field| &field.pat_ident.ident).collect(); - let data_params = data_fields.iter().map(|field| { - let pat = &field.pat_ident; - let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { - unreachable!("data fields are regular types"); - }; - quote!(#pat: &#ty) - }); - - let lazy_bound = |output_type: &Type| match derives { - true => quote!(for<'__derived> #core_types::gnode::GNode<#core_types::context::Derived<'__derived, #ctx_ident>, Output = #output_type>), - false => quote!(#core_types::gnode::GNode<#ctx_ident, Output = #output_type>), - }; - - let kernel_params = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).map(|field| { - let pat = &field.pat_ident; - match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#pat: #ty), - ParsedFieldType::Node(NodeParsedField { output_type, .. }) if raw_lazy => { - let bound = lazy_bound(output_type); - quote!(#pat: &impl #bound) - } - ParsedFieldType::Node(NodeParsedField { output_type, .. }) => { - let bound = lazy_bound(output_type); - quote!(#pat: #core_types::gnode::LazyInput<'_, impl #bound>) - } - } - }); - - let node_bounds = regular_fields.iter().zip(&node_generics).map(|(field, node_generic)| match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, .. }) => quote!(#node_generic: #core_types::gnode::GNode<#ctx_ident, Output = #ty>), - ParsedFieldType::Node(NodeParsedField { output_type, .. }) => { - let bound = lazy_bound(output_type); - quote!(#node_generic: #bound) - } - }); - - let mut async_bounds = match (async_fn, future_kernel) { - (false, false) => Vec::new(), - (false, true) => vec![quote!(#trait_output: Clone)], - (true, _) => { - let output_clone = std::iter::once(quote!(#trait_output: Clone)); - let value_clones = regular_fields.iter().filter_map(|field| match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: Clone)), - _ => None, - }); - let data_clones = data_fields.iter().filter_map(|field| match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, .. }) => Some(quote!(#ty: Clone)), - _ => None, - }); - output_clone.chain(value_clones).chain(data_clones).collect() - } - }; - if async_source { - async_bounds.push(quote!(for<'__derived> #core_types::context::Derived<'__derived, #ctx_ident>: #core_types::CacheHash)); - } - - let clampable_bounds = regular_fields.iter().filter_map(|field| { - let ParsedFieldType::Regular(RegularParsedField { - ty, number_hard_min, number_hard_max, .. - }) = &field.ty - else { - return None; - }; - (number_hard_min.is_some() || number_hard_max.is_some()).then(|| quote!(#ty: #core_types::misc::Clampable)) - }); - - let eval_values = regular_fields.iter().enumerate().map(|(index, field)| { - let name = &field.pat_ident.ident; - match &field.ty { - ParsedFieldType::Regular(_) => quote! { - let #name = match __cell.eval_input(#index, &self.#name, __input) { - Ok(value) => value, - Err(interrupt) => return interrupt.into(), - }; - }, - ParsedFieldType::Node(_) if raw_lazy => quote!(), - ParsedFieldType::Node(_) => quote! { - let #name = #core_types::gnode::LazyInput::new(&self.#name, &__cell, #index); - }, - } - }); - - let clamps = regular_fields.iter().filter_map(|field| { - let ParsedFieldType::Regular(RegularParsedField { number_hard_min, number_hard_max, .. }) = &field.ty else { - return None; - }; - let name = &field.pat_ident.ident; - let mut tokens = quote!(); - if let Some(min) = number_hard_min { - tokens.extend(quote!(let #name = #core_types::misc::Clampable::clamp_hard_min(#name, #min);)); - } - if let Some(max) = number_hard_max { - tokens.extend(quote!(let #name = #core_types::misc::Clampable::clamp_hard_max(#name, #max);)); - } - (!tokens.is_empty()).then_some(tokens) - }); - - let call_args = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).map(|field| { - let name = &field.pat_ident.ident; - match &field.ty { - ParsedFieldType::Node(_) if raw_lazy => quote!(&self.#name), - _ => quote!(#name), - } - }); - - let value_field_names: Vec<&Ident> = regular_fields - .iter() - .filter(|field| matches!(field.ty, ParsedFieldType::Regular(_))) - .map(|field| &field.pat_ident.ident) - .collect(); - - let extent_impl = match &parsed.attributes.extent { - Some(path) => quote! { - fn extent(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { - #path(self, __input) - } - }, - None if value_field_names.is_empty() => quote!(), - None => { - let first = value_field_names[0]; - let mut meet = quote!(self.#first.extent(__input)); - for name in &value_field_names[1..] { - meet = quote!(#core_types::gpoll::Extent::meet(#meet, self.#name.extent(__input))); - } - quote! { - fn extent(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<#core_types::gpoll::Extent> { - #meet - } - } - } - }; - - let serialize_impl = match &parsed.attributes.serialize { - Some(path) => { - let data_refs = data_names.iter().map(|name| quote!(&self.#name)); - quote! { - fn serialize(&self) -> Option<::std::sync::Arc> { - #path(#(#data_refs),*) - } - } - } - None => quote!(), - }; - - let batch_impl = match &parsed.attributes.batch { - Some(path) => quote! { - fn eval_batch<'__batch>( - &self, - __input: &'__batch #ctx_ident, - __range: ::std::ops::Range, - __scratch: Option<&'__batch mut [::std::mem::MaybeUninit]>, - ) -> #core_types::gnode::BatchStatus<'__batch, Self::Output> - where - #ctx_ident: #core_types::context::InjectIndex + Copy, - { - #path(self, __input, __range, __scratch) - } - }, - None => quote!(), - }; - - let ctx_pat = &parsed.input.pat_ident; - let fn_where = &parsed.where_clause; - let body = &parsed.body; - let vis = &parsed.vis; - let kernel_fields: Vec<&&ParsedField> = regular_fields.iter().filter(|field| !injected_name(&field.pat_ident.ident)).collect(); - let kernel = match async_fn { - false => quote! { - #[allow(clippy::too_many_arguments)] - #vis fn #fn_name<#(#generics,)*>(#ctx_pat: &#ctx_ident #(, #data_params)* #(, #kernel_params)*) -> #output_type #fn_where #body - }, - true => { - let kernel_generics = parsed.fn_generics.iter().filter(|param| match param { - GenericParam::Type(type_param) => Some(&type_param.ident) != ctx_param.map(|ctx_param| &ctx_param.ident), - _ => true, - }); - let snapshot_param = snapshot_ctx.then(|| quote!(#ctx_pat: #core_types::context::CtxSnapshot)).into_iter(); - let data_kernel_params = data_fields.iter().map(|field| { - let pat = &field.pat_ident; - let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { - unreachable!("data fields are regular types"); - }; - quote!(#pat: #ty) - }); - let value_kernel_params = kernel_fields.iter().map(|field| { - let pat = &field.pat_ident; - let ParsedFieldType::Regular(RegularParsedField { ty, .. }) = &field.ty else { - unreachable!("async source fields are eager values"); - }; - quote!(#pat: #ty) - }); - let params = snapshot_param.chain(data_kernel_params).chain(value_kernel_params); - quote! { - #[allow(clippy::too_many_arguments)] - #vis async fn #fn_name<#(#kernel_generics,)*>(#(#params),*) -> #output_type #fn_where #body - } - } - }; - let cell_constructor = match parsed.attributes.no_partial { - true => quote!(#core_types::gnode::StatusCell::no_partial()), - false => quote!(#core_types::gnode::StatusCell::new()), - }; - let kernel_call = quote!(self::#fn_name(__input #(, &self.#data_names)* #(, #call_args)*)); - let lift = match kernel_kind(&parsed.output_type) { - KernelKind::Interrupt(_) => quote! { - match #kernel_call { - Ok(value) => __cell.finish(value), - Err(interrupt) => interrupt.into(), - } - }, - KernelKind::Poll(_) => quote!(__cell.merge(#kernel_call)), - _ => quote!(__cell.finish(#kernel_call)), - }; - - let placeholder_value_names: Vec<&Ident> = kernel_fields - .iter() - .filter(|field| matches!(field.ty, ParsedFieldType::Regular(_))) - .map(|field| &field.pat_ident.ident) - .collect(); - let inflight = match &parsed.attributes.placeholder { - Some(path) => quote!(__cell.merge(#core_types::gpoll::GPoll::Partial(#path(#(&#placeholder_value_names),*)))), - None => quote!(#core_types::gpoll::GPoll::Pending), - }; - let slot_check = quote! { - let __scope = #core_types::context::DeriveCtx::scope(__input).excluding(_source); - let __key = #core_types::registry::cache_key(&#core_types::context::DeriveCtx::with_scope(__input, &__scope)); - { - let __entries = self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner); - if let Some(__state) = __entries.get(&__key) { - return match __state { - Some(value) => __cell.merge(value.clone()), - None => #inflight, - }; - } - } - }; - let future_completion = |payload: &Type| match kernel_kind(payload) { - KernelKind::Poll(_) => quote!(__future.await), - KernelKind::Interrupt(_) => quote! { - match __future.await { - Ok(value) => #core_types::gpoll::GPoll::Final(value), - Err(interrupt) => interrupt.into(), - } - }, - _ => quote!(#core_types::gpoll::GPoll::Final(__future.await)), - }; - let eval_tail = match (async_fn, future_kernel) { - (false, false) => lift, - (true, _) => { - let kernel_value_names: Vec<&Ident> = kernel_fields.iter().map(|field| &field.pat_ident.ident).collect(); - let snapshot_binding = snapshot_ctx.then(|| quote!(let __snapshot = #core_types::context::CtxSnapshot::capture(__input);)).into_iter(); - let snapshot_arg = snapshot_ctx.then(|| quote!(__snapshot)).into_iter(); - let future_args = snapshot_arg - .chain(data_names.iter().map(|name| quote!(self.#name.clone()))) - .chain(kernel_value_names.iter().map(|name| quote!(#name.clone()))); - let completion = future_completion(&parsed.output_type); - quote! { - #slot_check - self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, None); - let __slot = std::sync::Arc::clone(&self.slot); - #(#snapshot_binding)* - let __future = self::#fn_name(#(#future_args),*); - _runtime.0.spawn(_source, Box::pin(async move { - let __value = #completion; - __slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, Some(__value)); - })); - #inflight - } - } - (false, true) => { - let (placeholder_binding, spawn_return) = match &parsed.attributes.placeholder { - Some(path) => ( - quote!(let __placeholder = #path(#(&#placeholder_value_names),*);), - quote!(__cell.merge(#core_types::gpoll::GPoll::Partial(__placeholder))), - ), - None => (quote!(), quote!(#core_types::gpoll::GPoll::Pending)), - }; - let acquire = match kernel_kind(&parsed.output_type) { - KernelKind::FutureInterrupt(_) => quote! { - let __future = match #kernel_call { - Ok(future) => future, - Err(interrupt) => return interrupt.into(), - }; - }, - _ => quote!(let __future = #kernel_call;), - }; - let payload = match kernel_kind(&parsed.output_type) { - KernelKind::Future(payload) | KernelKind::FutureInterrupt(payload) => payload, - _ => unreachable!("guarded by future_kernel"), - }; - let completion = future_completion(&payload); - quote! { - #slot_check - #placeholder_binding - #acquire - self.slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, None); - let __slot = std::sync::Arc::clone(&self.slot); - _runtime.0.spawn(_source, Box::pin(async move { - let __value = #completion; - __slot.lock().unwrap_or_else(std::sync::PoisonError::into_inner).insert(__key, Some(__value)); - })); - #spawn_return - } - } - }; - - let entries = entries_tokens(parsed, &struct_name, &data_field_generic_idents, ®ular_fields); - let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes); - let entries_reexport = match entries.is_empty() { - true => quote!(), - false => { - let entries_name = format_ident!("{}_entries", fn_name); - quote! { - #cfg - #[doc(hidden)] - pub use #mod_name::#entries_name; - } - } - }; - - let top_level = quote! { - #entries_reexport - - #cfg - #[automatically_derived] - impl<#(#generics,)* #(#node_generics,)*> #core_types::gnode::GNode<#ctx_ident> for #mod_name::#struct_name<#(#struct_type_params,)*> - where - #(#node_bounds,)* - #(#clampable_bounds,)* - #(#async_bounds,)* - #(#where_predicates,)* - { - type Output = #trait_output; - - fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll { - let __cell = #cell_constructor; - #(#eval_values)* - #(#clamps)* - #eval_tail - } - - #extent_impl - - #serialize_impl - - #batch_impl - } - }; - - Ok(GNodeTokens { - in_mod: entries, - top_level: quote! { - #kernel - - #top_level - }, - }) -} - -pub(crate) fn slot_value_type(output: &Type) -> Type { - match kernel_kind(output) { - KernelKind::Plain => output.clone(), - KernelKind::Poll(inner) | KernelKind::Interrupt(inner) => inner, - KernelKind::Future(payload) | KernelKind::FutureInterrupt(payload) => match kernel_kind(&payload) { - KernelKind::Poll(inner) | KernelKind::Interrupt(inner) => inner, - _ => payload, - }, - } -} - -pub(crate) fn is_source_kernel(output: &Type) -> bool { - matches!(kernel_kind(output), KernelKind::Future(_) | KernelKind::FutureInterrupt(_)) -} - -enum KernelKind { - Plain, - Interrupt(Type), - Poll(Type), - Future(Type), - FutureInterrupt(Type), -} - -fn source_future_payload(segment: &syn::PathSegment) -> Type { - let PathArguments::AngleBracketed(args) = &segment.arguments else { - return syn::parse_quote!(()); - }; - args.args - .iter() - .find_map(|argument| match argument { - GenericArgument::Type(ty) => Some(ty.clone()), - _ => None, - }) - .unwrap_or_else(|| syn::parse_quote!(())) -} - -fn kernel_kind(output: &Type) -> KernelKind { - let plain = || KernelKind::Plain; - let Type::Path(path) = output else { return plain() }; - let Some(segment) = path.path.segments.last() else { return plain() }; - match segment.ident.to_string().as_str() { - "GPoll" => { - let PathArguments::AngleBracketed(args) = &segment.arguments else { return plain() }; - let inner = args.args.iter().find_map(|argument| match argument { - GenericArgument::Type(ty) => Some(ty.clone()), - _ => None, - }); - inner.map(KernelKind::Poll).unwrap_or_else(plain) - } - "SourceFuture" => KernelKind::Future(source_future_payload(segment)), - "Result" => { - let PathArguments::AngleBracketed(args) = &segment.arguments else { return plain() }; - let mut types = args.args.iter().filter_map(|argument| match argument { - GenericArgument::Type(ty) => Some(ty), - _ => None, - }); - let (Some(inner), Some(Type::Path(error_path))) = (types.next(), types.next()) else { - return plain(); - }; - if !error_path.path.segments.last().is_some_and(|segment| segment.ident == "Interrupt") { - return plain(); - } - if let Type::Path(inner_path) = inner { - if let Some(inner_segment) = inner_path.path.segments.last() { - if inner_segment.ident == "SourceFuture" { - return KernelKind::FutureInterrupt(source_future_payload(inner_segment)); - } - } - } - KernelKind::Interrupt(inner.clone()) - } - _ => plain(), - } -} - -fn context_param<'a>(parsed: &'a ParsedNodeFn) -> Option<&'a TypeParam> { - let Type::Path(path) = &parsed.input.ty else { - return None; - }; - let ident = path.path.get_ident()?; - parsed.fn_generics.iter().find_map(|param| match param { - GenericParam::Type(type_param) if &type_param.ident == ident => Some(type_param), - _ => None, - }) -} - -fn type_disqualifies(ty: &Type) -> bool { - struct Disqualifier { - found: bool, - } - - impl<'ast> Visit<'ast> for Disqualifier { - fn visit_type_reference(&mut self, _: &'ast syn::TypeReference) { - self.found = true; - } - - fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) { - self.found = true; - } - - fn visit_lifetime(&mut self, _: &'ast Lifetime) { - self.found = true; - } - } - - let mut visitor = Disqualifier { found: false }; - visitor.visit_type(ty); - visitor.found -} - -fn desugar_extract_lifetime(bound: &TypeParamBound, core_types: &TokenStream2) -> TokenStream2 { - let TypeParamBound::Trait(trait_bound) = bound else { - return quote!(#bound); - }; - let Some(segment) = trait_bound.path.segments.last() else { - return quote!(#bound); - }; - if segment.ident != "ExtractArena" { - return quote!(#bound); - } - let PathArguments::AngleBracketed(args) = &segment.arguments else { - return quote!(#bound); - }; - if args.args.len() != 1 { - return quote!(#bound); - } - let Some(GenericArgument::Lifetime(lifetime)) = args.args.first() else { - return quote!(#bound); - }; - quote!(#core_types::context::ExtractArena) -} - -fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic_idents: &[Ident], regular_fields: &[&ParsedField]) -> TokenStream2 { - if !data_field_generic_idents.is_empty() { - return quote!(); - } - let Some(rows) = implementation_rows(parsed, regular_fields) else { - return quote!(); - }; - let rows: Vec<&Vec> = rows.iter().filter(|row| row.iter().all(|ty| !type_disqualifies(ty))).collect(); - if rows.is_empty() { - return quote!(); - } - - let fn_name = &parsed.fn_name; - let entries_name = format_ident!("{}_entries", fn_name); - let arity = regular_fields.len(); - let names: Vec<&Ident> = regular_fields.iter().map(|field| &field.pat_ident.ident).collect(); - - let entries = rows.iter().map(|row| { - let types = row.iter(); - let edge_types = row.iter().map(|ty| quote!(gcore::registry::SharedEdge>)); - let output = quote!(<#struct_name<#(#edge_types),*> as gcore::gnode::GNode>>::Output); - let downcasts = names.iter().zip(row.iter()).map(|(name, ty)| quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;)); - quote! { - gcore::registry::RegistryEntry { - io: gcore::registry::NodeIOTypes::new( - gcore::concrete!(gcore::context::ContextImpl<'static>), - gcore::concrete!(#output), - vec![#(gcore::registry::edge_type::<#types>()),*], - ), - constructor: |inputs| { - if inputs.len() != #arity { - return Err(gcore::registry::ConstructionError::Arity { expected: #arity, got: inputs.len() }); - } - let mut inputs = inputs.into_iter(); - #(#downcasts)* - Ok(gcore::registry::EdgeHandle::new(::std::sync::Arc::new(#struct_name::new(#(#names),*)) as ::std::sync::Arc>)) - }, - } - } - }); - - quote! { - pub fn #entries_name() -> ::std::vec::Vec { - vec![#(#entries),*] - } - } -} - -fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option>> { - let ctx_ident = context_param(parsed).map(|ctx| ctx.ident.clone()); - let open_generics: Vec<&Ident> = parsed - .fn_generics - .iter() - .filter_map(|param| match param { - GenericParam::Type(type_param) if Some(&type_param.ident) != ctx_ident.as_ref() => Some(&type_param.ident), - _ => None, - }) - .collect(); - - let candidates: Vec> = regular_fields - .iter() - .map(|field| match &field.ty { - ParsedFieldType::Regular(RegularParsedField { ty, implementations, .. }) => match implementations.is_empty() { - false => Some(implementations.iter().cloned().collect()), - true => open_generics.iter().all(|generic| !crate::codegen::type_contains_ident(ty, generic)).then(|| vec![ty.clone()]), - }, - ParsedFieldType::Node(NodeParsedField { output_type, implementations, .. }) => match implementations.is_empty() { - false => Some(implementations.iter().map(|implementation| implementation.output.clone()).collect()), - true => open_generics - .iter() - .all(|generic| !crate::codegen::type_contains_ident(output_type, generic)) - .then(|| vec![output_type.clone()]), - }, - }) - .collect::>()?; - - let row_count = candidates.iter().map(|types| types.len()).max().unwrap_or(1).max(1); - Some((0..row_count).map(|row| candidates.iter().map(|types| types[row.min(types.len() - 1)].clone()).collect()).collect()) -} diff --git a/node-graph/node-macro/src/lib.rs b/node-graph/node-macro/src/lib.rs index 8302e512e1..35fe604a01 100644 --- a/node-graph/node-macro/src/lib.rs +++ b/node-graph/node-macro/src/lib.rs @@ -7,7 +7,6 @@ mod buffer_struct; mod codegen; mod crate_ident; mod derive_choice_type; -mod gcodegen; mod parsing; mod shader_nodes; mod validation; diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 58ad83c8b7..6b49f5bd87 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -998,7 +998,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result syn::Result<()> { fn validate_async_source(parsed: &ParsedNodeFn) { let snapshot_ctx = matches!(&parsed.input.ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "CtxSnapshot")); - let future_kernel = crate::gcodegen::is_source_kernel(&parsed.output_type); + let future_kernel = crate::codegen::is_source_kernel(&parsed.output_type); if parsed.is_async && future_kernel { emit_error!( parsed.output_type.span(), diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index eaa0cfebe3..6adc8da50c 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -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(input: I, #[data] cache: Arc(node: &MemoizeNode, ctx: &C) -> GPoll where T: Clone, - NodeContent: GNode, + NodeContent: Node, { node.content.extent(ctx) } @@ -71,7 +71,7 @@ fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<' fn frame_memo_extent(node: &FrameMemoNode, ctx: &C) -> GPoll where T: Clone + 'static, - NodeContent: GNode, + NodeContent: Node, { 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 GNode for CountingNode { + impl Node for CountingNode { type Output = u32; fn eval(&self, _input: &Input) -> GPoll { @@ -144,7 +144,7 @@ mod tests { struct PartialCountingNode(AtomicU32); - impl GNode for PartialCountingNode { + impl Node for PartialCountingNode { type Output = u32; fn eval(&self, _input: &Input) -> GPoll { @@ -154,7 +154,7 @@ mod tests { struct ValueNode(T); - impl GNode for ValueNode { + impl Node for ValueNode { type Output = T; fn eval(&self, _input: &Input) -> GPoll { @@ -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>); + let handle = EdgeHandle::new(Arc::new(MonitorNode::new(ValueNode(11u32))) as Arc>); assert!(handle.serialize().is_none(), "no record before the first eval"); let edge = handle.duplicate().downcast::().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>); - let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::().unwrap())) as Arc>); + let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc>); + let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::().unwrap())) as Arc>); let stacked = MemoizeNode::new(memoized.downcast::().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>); - let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::().unwrap())) as Arc>); + let edge = EdgeHandle::new(Arc::new(ValueNode("lent out".to_string())) as Arc>); + let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::().unwrap())) as Arc>); assert_eq!(*lending.ty(), core_types::registry::lend_edge_type::()); let node = lending.downcast_lend::().unwrap(); diff --git a/node-graph/nodes/gcore/src/ops.rs b/node-graph/nodes/gcore/src/ops.rs index 4c48ae146a..d09739cda5 100644 --- a/node-graph/nodes/gcore/src/ops.rs +++ b/node-graph/nodes/gcore/src/ops.rs @@ -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 { diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index bf321daa01..c08d7e058d 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -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> for ProbeNode { + impl<'a> Node> for ProbeNode { type Output = RenderOutput; fn eval(&self, ctx: &ContextImpl<'a>) -> GPoll { @@ -241,7 +241,7 @@ mod tests { let ctx = root.with_varargs(&varargs); let graph = CreateContextNode::new(ProbeNode); - let GPoll::Final(result) = as GNode>::eval(&graph, &ctx) else { + let GPoll::Final(result) = as Node>::eval(&graph, &ctx) else { panic!("create_context must complete synchronously"); }; assert_eq!( diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 69889a40bb..d2263e78d4 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -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); - impl GNode for SourceNode { + impl Node for SourceNode { type Output = T; fn eval(&self, _input: &Input) -> GPoll { @@ -1084,7 +1084,7 @@ mod graphene_test { struct IndexNode; - impl GNode for IndexNode { + impl Node for IndexNode { type Output = f64; fn eval(&self, input: &Input) -> GPoll { @@ -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> = Box::new(AddNode::new(IndexNode, SourceNode(10.0f64))); + let erased: Box> = 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>); - let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc>); + let value = EdgeHandle::new(Arc::new(SourceNode(true)) as Arc>); + let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc>); let wired = construct(&entries[0], vec![value, other_value]).unwrap().downcast::().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>); - let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc>); + let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc>); + let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc>); let wired = construct(&entries[0], vec![augend, addend]).unwrap().downcast::().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, f64); - impl GNode for CountingSource { + impl Node for CountingSource { type Output = f64; fn eval(&self, _input: &Input) -> GPoll { @@ -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 GNode for PendingSource { + impl Node for PendingSource { type Output = f64; fn eval(&self, _input: &Input) -> GPoll { @@ -1209,7 +1209,7 @@ mod graphene_test { struct PartialSource; - impl GNode for PartialSource { + impl Node for PartialSource { type Output = f64; fn eval(&self, _input: &Input) -> GPoll { @@ -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 GNode for PartialCondition { + impl Node for PartialCondition { type Output = bool; fn eval(&self, _input: &Input) -> GPoll { @@ -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 GNode for FallbackNode { + impl Node for FallbackNode { type Output = f64; fn eval(&self, _input: &Input) -> GPoll { @@ -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); diff --git a/node-graph/nodes/repeat/src/repeat_nodes.rs b/node-graph/nodes/repeat/src/repeat_nodes.rs index d55ac22a6b..c2fa1b94de 100644 --- a/node-graph/nodes/repeat/src/repeat_nodes.rs +++ b/node-graph/nodes/repeat/src/repeat_nodes.rs @@ -179,143 +179,3 @@ fn repeat_on_points + 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 { - List::new_from_element(Vector::from_bezpath(bezpath)) - } - - #[derive(Clone)] - pub struct FutureWrapperNode(T); - - impl<'i, I: Ctx, T: 'i + Clone + Send> Node<'i, I> for FutureWrapperNode { - type Output = Pin + '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}"); - } - } -}