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

View File

@@ -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;

View File

@@ -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};

View File

@@ -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<DynGraphRuntime> {
Arc::new(GraphRuntime::new(Box::new(NoopSpawner) as Box<DynSpawner>))
}
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<Arc<dyn std::any::Any + Send + Sync + 'static>, 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<I, T: 'static>(&self, id: NodeId, input: &I) -> Option<GPoll<T>>
where
ErasedGNode<T>: GNode<I, Output = T>,
ErasedNode<T>: Node<I, Output = T>,
{
let (node, _path) = self.nodes.get(&id)?;
let edge = node.duplicate().downcast::<T>().ok()?;

View File

@@ -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<ErasedGNode<$first>>))
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$first>>))
},
},
)
@@ -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<ErasedGNode<$to>>))
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$to>>))
},
},
)
@@ -454,7 +454,7 @@ mod node_registry_macros {
inputs.next().unwrap().downcast::<RuntimeHandle>()?,
inputs.next().unwrap().downcast::<SourceId>()?,
);
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedGNode<$to>>))
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$to>>))
},
},
)
@@ -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<ErasedGNode<$to>>))
Ok(EdgeHandle::new(std::sync::Arc::new(node) as std::sync::Arc<ErasedNode<$to>>))
},
},
)

View File

@@ -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<A: Add<B>, B, C: Ctx>(_ctx: &C, augend: A, addend: B) -> <A as Add<B>>::Output {
augend + addend
}
struct AddNode<Node0, Node1> {
augend: Node0,
addend: Node1,
}
impl<Node0, Node1> AddNode<Node0, Node1> {
fn new(augend: Node0, addend: Node1) -> Self {
Self { augend, addend }
}
}
impl<A, B, Input, Node0, Node1> GNode<Input> for AddNode<Node0, Node1>
where
A: Add<B>,
Input: Ctx,
Node0: GNode<Input, Output = A>,
Node1: GNode<Input, Output = B>,
{
type Output = <A as Add<B>>::Output;
fn eval(&self, input: &Input) -> GPoll<Self::Output> {
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>(T);
impl<T: Clone, Input> GNode<Input> for ValueNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> GPoll<T> {
GPoll::Final(self.0.clone())
}
}
struct ReadIndexNode;
impl<Input: InjectIndex + Copy + ExtractIndexValue> GNode<Input> for ReadIndexNode {
type Output = f64;
fn eval(&self, input: &Input) -> GPoll<f64> {
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<C: Ctx>(_ctx: &C, value: &String) -> f64 {
value.len() as f64
}
struct LendStringNode {
value: String,
cell: ArenaCell<String>,
}
impl LendStringNode {
fn new(value: String) -> Self {
Self { value, cell: ArenaCell::new() }
}
}
impl<'e, Input> GNode<Input> for LendStringNode
where
Input: Ctx + ExtractArena<ArenaRef = &'e Arena>,
{
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<Node0> {
value: Node0,
}
impl<Node0> StringLengthNode<Node0> {
fn new(value: Node0) -> Self {
Self { value }
}
}
impl<'e, Input, Node0> GNode<Input> for StringLengthNode<Node0>
where
Input: Ctx,
Node0: GNode<Input, Output = &'e String>,
{
type Output = f64;
fn eval(&self, input: &Input) -> GPoll<f64> {
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<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = T>;
type ErasedLendEdge = dyn for<'c> GNode<ContextImpl<'c>, Output = &'c String>;
fn string_length_constructor(args: Vec<Box<dyn Any>>) -> Result<Box<ErasedGNode<f64>>, &'static str> {
let mut args = args.into_iter();
let value = *args.next().ok_or("arity")?.downcast::<Box<ErasedLendEdge>>().map_err(|_| "type")?;
Ok(Box::new(StringLengthNode::new(value)))
}
fn add_constructor_f64(args: Vec<Box<dyn Any>>) -> Result<Box<ErasedGNode<f64>>, &'static str> {
let mut args = args.into_iter();
let augend = *args.next().ok_or("arity")?.downcast::<Box<ErasedGNode<f64>>>().map_err(|_| "type")?;
let addend = *args.next().ok_or("arity")?.downcast::<Box<ErasedGNode<f64>>>().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<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
let addend: Box<dyn Any> = Box::new(Box::new(ValueNode(2.0f64)) as Box<ErasedGNode<f64>>);
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<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
let addend: Box<dyn Any> = Box::new(Box::new(ValueNode(2u32)) as Box<ErasedGNode<u32>>);
assert_eq!(add_constructor_f64(vec![augend, addend]).map(|_| ()), Err("type"));
let augend: Box<dyn Any> = Box::new(Box::new(ValueNode(1.0f64)) as Box<ErasedGNode<f64>>);
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<ErasedGNode<f64>> = 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<dyn Any> = Box::new(Box::new(LendStringNode::new("across the boundary".to_string())) as Box<ErasedLendEdge>);
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<ErasedLendEdge> = 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<Input> GNode<Input> for FallbackNode {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
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]);
}

View File

@@ -1,17 +0,0 @@
use crate::Node;
use std::marker::PhantomData;
#[derive(Clone)]
pub struct FnNode<T: Fn(I) -> O, I, O>(T, PhantomData<(I, O)>);
impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode<T, I, O> {
type Output = O;
fn eval(&'i self, input: I) -> Self::Output {
self.0(input)
}
}
impl<T: Fn(I) -> O, I, O> FnNode<T, I, O> {
pub fn new(f: T) -> Self {
FnNode(f, PhantomData)
}
}

View File

@@ -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::<Self>()
}
/// Serialize the node which is used for the `introspect` function which can retrieve values from monitor nodes.
fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
log::warn!("Node::serialize not implemented for {}", std::any::type_name::<Self>());
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::<Input::Static>()
}
fn input_type_name(&self) -> &'static str {
std::any::type_name::<Input>()
}
fn output_type(&self) -> TypeId {
TypeId::of::<<Self::Output as StaticTypeSized>::Static>()
}
fn output_type_name(&self) -> &'static str {
std::any::type_name::<Self::Output>()
}
fn to_node_io(&self, inputs: Vec<Type>) -> NodeIOTypes {
NodeIOTypes {
call_argument: concrete!(<Input as StaticTypeSized>::Static),
return_value: concrete!(<Self::Output as StaticTypeSized>::Static),
inputs,
}
}
fn to_async_node_io(&self, inputs: Vec<Type>) -> NodeIOTypes
where
<Self::Output as Future>::Output: StaticTypeSized,
Self::Output: Future,
{
NodeIOTypes {
call_argument: concrete!(<Input as StaticTypeSized>::Static),
return_value: future!(<<Self::Output as 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<N> {
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<N> {
type Output = O;
fn eval(&'i self, input: I) -> O {
(**self).eval(input)
}
}
impl<'i, I, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + '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);

View File

@@ -22,7 +22,7 @@ pub unsafe fn assume_init_prefix_mut<T>(scratch: &mut [MaybeUninit<T>], len: usi
unsafe { std::slice::from_raw_parts_mut(scratch.as_mut_ptr().cast::<T>(), len) }
}
pub trait GNode<Input> {
pub trait Node<Input> {
type Output;
fn eval(&self, input: &Input) -> GPoll<Self::Output>;
@@ -80,9 +80,9 @@ pub trait GNode<Input> {
}
}
impl<Input, N> GNode<Input> for &N
impl<Input, N> Node<Input> for &N
where
N: GNode<Input> + ?Sized,
N: Node<Input> + ?Sized,
{
type Output = N::Output;
@@ -102,9 +102,9 @@ where
}
}
impl<Input, N> GNode<Input> for Box<N>
impl<Input, N> Node<Input> for Box<N>
where
N: GNode<Input> + ?Sized,
N: Node<Input> + ?Sized,
{
type Output = N::Output;
@@ -124,9 +124,9 @@ where
}
}
impl<Input, N> GNode<Input> for std::sync::Arc<N>
impl<Input, N> Node<Input> for std::sync::Arc<N>
where
N: GNode<Input> + ?Sized,
N: Node<Input> + ?Sized,
{
type Output = N::Output;
@@ -171,7 +171,7 @@ impl StatusCell {
Self { no_partial: true, ..Self::new() }
}
pub fn eval_input<Input, N: GNode<Input>>(&self, input_index: usize, node: &N, input: &Input) -> Result<N::Output, Interrupt> {
pub fn eval_input<Input, N: Node<Input>>(&self, input_index: usize, node: &N, input: &Input) -> Result<N::Output, Interrupt> {
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<Input>(&self, ctx: &Input) -> Result<N::Output, Interrupt>
where
N: GNode<Input>,
N: Node<Input>,
{
self.cell.eval_input(self.input_index, self.node, ctx)
}
}
impl<'a, Input, N> GNode<Input> for LazyInput<'a, N>
impl<'a, Input, N> Node<Input> for LazyInput<'a, N>
where
N: GNode<Input>,
N: Node<Input>,
{
type Output = N::Output;
@@ -279,7 +279,7 @@ mod tests {
struct Double;
impl GNode<TestInput> for Double {
impl Node<TestInput> for Double {
type Output = u64;
fn eval(&self, input: &TestInput) -> GPoll<u64> {
@@ -315,7 +315,7 @@ mod tests {
#[test]
fn partial_lane_downgrades_batch_finality() {
struct PartialAtThree;
impl GNode<TestInput> for PartialAtThree {
impl Node<TestInput> for PartialAtThree {
type Output = u64;
fn eval(&self, input: &TestInput) -> GPoll<u64> {
match input.index {
@@ -344,7 +344,7 @@ mod tests {
}
}
struct PendingAtTwo;
impl GNode<TestInput> for PendingAtTwo {
impl Node<TestInput> for PendingAtTwo {
type Output = Probe;
fn eval(&self, input: &TestInput) -> GPoll<Probe> {
match input.index {
@@ -362,7 +362,7 @@ mod tests {
#[test]
fn trait_is_object_safe_across_erased_edges() {
let erased: Box<dyn GNode<TestInput, Output = u64>> = Box::new(Double);
let erased: Box<dyn Node<TestInput, Output = u64>> = Box::new(Double);
let input = TestInput { index: 21 };
assert_eq!(erased.eval(&input), GPoll::Final(42));
let mut scratch = [const { MaybeUninit::uninit() }; 2];

View File

@@ -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<N: for<'a> Node<'a, I>, I, O>(pub N, pub PhantomData<(I, O)>);
impl<'i, N, I: 'i, O: 'i> Node<'i, I> for TypeNode<N, I, O>
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<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
self.0.serialize()
}
}
impl<'i, N: for<'a> Node<'a, I>, I: 'i> TypeNode<N, I, <N as Node<'i, I>>::Output> {
pub fn new(node: N) -> Self {
Self(node, PhantomData)
}
}
impl<'i, N: for<'a> Node<'a, I> + Clone, I: 'i> Clone for TypeNode<N, I, <N as Node<'i, I>>::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<N, I, <N as Node<'i, I>>::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.

View File

@@ -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<Mutex<HashMap<ProtoNodeIdentifier, NodeMetada
pub use crate::NodeIOTypes;
#[cfg(not(target_family = "wasm"))]
pub type ErasedGNode<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = T> + Send + Sync;
pub type ErasedNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = T> + Send + Sync;
#[cfg(target_family = "wasm")]
pub type ErasedGNode<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = T>;
pub type ErasedNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = T>;
#[cfg(not(target_family = "wasm"))]
pub type ErasedLendGNode<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = &'c T> + Send + Sync;
pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T> + Send + Sync;
#[cfg(target_family = "wasm")]
pub type ErasedLendGNode<T> = dyn for<'c> GNode<ContextImpl<'c>, Output = &'c T>;
pub type ErasedLendNode<T> = dyn for<'c> Node<ContextImpl<'c>, Output = &'c T>;
#[cfg(not(target_family = "wasm"))]
type DynEdge = dyn std::any::Any + Send + Sync;
@@ -127,9 +126,9 @@ unsafe impl<N: ?Sized + Send + Sync> Send for SharedEdge<N> {}
// SAFETY: as in Send.
unsafe impl<N: ?Sized + Send + Sync> Sync for SharedEdge<N> {}
impl<Input, N> GNode<Input> for SharedEdge<N>
impl<Input, N> Node<Input> for SharedEdge<N>
where
N: GNode<Input> + ?Sized,
N: Node<Input> + ?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<u64>, scratch: Option<&'a mut [std::mem::MaybeUninit<Self::Output>]>) -> crate::gnode::BatchStatus<'a, Self::Output>
fn eval_batch<'a>(&self, input: &'a Input, range: std::ops::Range<u64>, scratch: Option<&'a mut [std::mem::MaybeUninit<Self::Output>]>) -> 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<T: 'static>(node: std::sync::Arc<ErasedGNode<T>>) -> Self {
pub fn new<T: 'static>(node: std::sync::Arc<ErasedNode<T>>) -> Self {
Self::new_erased(node, edge_type::<T>())
}
pub fn new_ref<T: 'static>(node: std::sync::Arc<ErasedLendGNode<T>>) -> Self {
pub fn new_ref<T: 'static>(node: std::sync::Arc<ErasedLendNode<T>>) -> Self {
Self::new_erased(node, lend_edge_type::<T>())
}
pub fn new_erased<N: ?Sized + 'static>(node: std::sync::Arc<N>, ty: Type) -> Self
where
N: for<'c> GNode<ContextImpl<'c>>,
N: for<'c> Node<ContextImpl<'c>>,
SharedEdge<N>: WasmNotSend + WasmNotSync,
{
Self {
node: Box::new(SharedEdge::new(node)),
share: |edge| Box::new(edge.downcast_ref::<SharedEdge<N>>().expect("share hook matches the stored edge type").share()),
serialize: |edge| GNode::<ContextImpl>::serialize(edge.downcast_ref::<SharedEdge<N>>().expect("serialize hook matches the stored edge type")),
serialize: |edge| Node::<ContextImpl>::serialize(edge.downcast_ref::<SharedEdge<N>>().expect("serialize hook matches the stored edge type")),
ty,
}
}
@@ -217,11 +216,11 @@ impl EdgeHandle {
(self.serialize)(&*self.node)
}
pub fn downcast<T: 'static>(self) -> Result<SharedEdge<ErasedGNode<T>>, ConstructionError> {
pub fn downcast<T: 'static>(self) -> Result<SharedEdge<ErasedNode<T>>, ConstructionError> {
self.downcast_erased(edge_type::<T>())
}
pub fn downcast_lend<T: 'static>(self) -> Result<SharedEdge<ErasedLendGNode<T>>, ConstructionError> {
pub fn downcast_lend<T: 'static>(self) -> Result<SharedEdge<ErasedLendNode<T>>, ConstructionError> {
self.downcast_erased(lend_edge_type::<T>())
}
@@ -257,7 +256,6 @@ pub fn construct(entry: &RegistryEntry, inputs: Vec<EdgeHandle>) -> Result<EdgeH
(entry.constructor)(inputs)
}
pub type LocalFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n>>;
#[cfg(not(target_family = "wasm"))]
pub type Any<'n> = Box<dyn DynAny<'n> + 'n + Send>;
#[cfg(target_family = "wasm")]
@@ -275,7 +273,7 @@ mod tests {
struct CountingNode(AtomicU32);
impl<Input> GNode<Input> for CountingNode {
impl<Input> Node<Input> for CountingNode {
type Output = u32;
fn eval(&self, _input: &Input) -> GPoll<u32> {
@@ -285,7 +283,7 @@ mod tests {
struct ValueNode<T>(T);
impl<T: Clone, Input> GNode<Input> for ValueNode<T> {
impl<T: Clone, Input> Node<Input> for ValueNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> GPoll<T> {
@@ -295,7 +293,7 @@ mod tests {
struct LendNode(String);
impl<'e, Input: Ctx + ExtractArena<ArenaRef = &'e Arena>> GNode<Input> for LendNode {
impl<'e, Input: Ctx + ExtractArena<ArenaRef = &'e Arena>> Node<Input> 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<Input> for SplitNode<Node0>
impl<'e, Input, Node0> Node<Input> for SplitNode<Node0>
where
Input: Ctx,
Node0: GNode<Input, Output = &'e String>,
Node0: Node<Input, Output = &'e String>,
{
type Output = SplitBorrow<'e>;
@@ -330,14 +328,14 @@ mod tests {
}
}
type ErasedSplitEdge = dyn for<'c> GNode<ContextImpl<'c>, Output = SplitBorrow<'c>> + Send + Sync;
type ErasedSplitEdge = dyn for<'c> Node<ContextImpl<'c>, 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<ErasedLendGNode<String>>);
let lending = EdgeHandle::new_ref(Arc::new(LendNode("held".to_string())) as Arc<ErasedLendNode<String>>);
let upstream = lending.downcast_lend::<String>().unwrap();
let node: Arc<ErasedSplitEdge> = Arc::new(SplitNode { content: upstream });
let handle = EdgeHandle::new_erased(node, concrete!(SplitBorrow<'static>));
@@ -359,10 +357,10 @@ mod tests {
content: Node0,
}
impl<C, T, Node0> GNode<C> for RepeatNode<Node0>
impl<C, T, Node0> Node<C> for RepeatNode<Node0>
where
C: Ctx + DeriveCtx,
Node0: for<'x> GNode<Derived<'x, C>, Output = T>,
Node0: for<'x> Node<Derived<'x, C>, Output = T>,
{
type Output = Vec<T>;
@@ -382,7 +380,7 @@ mod tests {
struct LevelsNode;
impl<Input: ExtractIndex> GNode<Input> for LevelsNode {
impl<Input: ExtractIndex> Node<Input> for LevelsNode {
type Output = Vec<usize>;
fn eval(&self, input: &Input) -> GPoll<Vec<usize>> {
@@ -398,7 +396,7 @@ mod tests {
let nested = RepeatNode {
content: RepeatNode { content: LevelsNode },
};
let erased: Box<ErasedGNode<Vec<Vec<Vec<usize>>>>> = Box::new(nested);
let erased: Box<ErasedNode<Vec<Vec<Vec<usize>>>>> = 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<C, T, Node0> GNode<C> for ShiftFootprintNode<Node0>
impl<C, T, Node0> Node<C> for ShiftFootprintNode<Node0>
where
C: Ctx + DeriveCtx + ExtractFootprint,
Node0: for<'x> GNode<Derived<'x, C>, Output = T>,
Node0: for<'x> Node<Derived<'x, C>, Output = T>,
{
type Output = T;
@@ -434,7 +432,7 @@ mod tests {
struct ResolutionNode;
impl<Input: ExtractFootprint> GNode<Input> for ResolutionNode {
impl<Input: ExtractFootprint> Node<Input> for ResolutionNode {
type Output = u32;
fn eval(&self, input: &Input) -> GPoll<u32> {
@@ -447,7 +445,7 @@ mod tests {
let scope = scope_fixture(&generations, &arena);
let ctx = ContextImpl::root(&scope);
let graph: Box<ErasedGNode<u32>> = Box::new(ShiftFootprintNode {
let graph: Box<ErasedNode<u32>> = 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::<String>()?;
drop(value);
Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc<ErasedGNode<u32>>))
Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc<ErasedNode<u32>>))
}
let entry = RegistryEntry {
io: NodeIOTypes::new(concrete!(Context), concrete!(u32), vec![edge_type::<String>()]),
constructor: construct_strlen,
};
let owned = EdgeHandle::new(Arc::new(ValueNode("typed".to_string())) as Arc<ErasedGNode<String>>);
let owned = EdgeHandle::new(Arc::new(ValueNode("typed".to_string())) as Arc<ErasedNode<String>>);
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<ErasedGNode<f64>>);
let mistyped = EdgeHandle::new(Arc::new(ValueNode(1.0f64)) as Arc<ErasedNode<f64>>);
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<ErasedLendGNode<String>>);
let lent = EdgeHandle::new_ref(Arc::new(LendNode("typed".to_string())) as Arc<ErasedLendNode<String>>);
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<ErasedGNode<u32>>);
let handle = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc<ErasedNode<u32>>);
let duplicate = handle.duplicate();
assert_eq!(*duplicate.ty(), edge_type::<u32>());

View File

@@ -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>(T);
impl<T: Clone, Input> GNode<Input> for SourceNode<T> {
impl<T: Clone, Input> Node<Input> for SourceNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> GPoll<T> {
@@ -264,7 +264,7 @@ mod tests {
struct GatedSource(Arc<std::sync::atomic::AtomicBool>, f64);
impl<Input> GNode<Input> for GatedSource {
impl<Input> Node<Input> for GatedSource {
type Output = f64;
fn eval(&self, _input: &Input) -> GPoll<f64> {
@@ -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::<SourceId>::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));

View File

@@ -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<const N: u32>;
impl<'i, const N: u32, I> Node<'i, I> for IntNode<N> {
type Output = u32;
#[inline(always)]
fn eval(&'i self, _input: I) -> Self::Output {
N
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct ValueNode<T>(pub T);
impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
type Output = &'i T;
#[inline(always)]
fn eval(&'i self, _input: I) -> Self::Output {
&self.0
}
}
impl<T> ValueNode<T> {
pub const fn new(value: T) -> ValueNode<T> {
ValueNode(value)
}
}
impl<T> From<T> for ValueNode<T> {
fn from(value: T) -> Self {
ValueNode::new(value)
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct AsRefNode<T: AsRef<U>, U>(pub T, PhantomData<U>);
impl<'i, T: 'i + AsRef<U>, U: 'i> Node<'i, ()> for AsRefNode<T, U> {
type Output = &'i U;
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
self.0.as_ref()
}
}
impl<T: AsRef<U>, U> AsRefNode<T, U> {
pub const fn new(value: T) -> AsRefNode<T, U> {
AsRefNode(value, PhantomData)
}
}
#[derive(Default, Debug, Clone)]
pub struct RefCellMutNode<T>(pub RefCell<T>);
impl<'i, T: 'i> Node<'i, ()> for RefCellMutNode<T> {
type Output = RefMut<'i, T>;
#[inline(always)]
fn eval(&'i self, _input: ()) -> Self::Output {
self.0.borrow_mut()
}
}
impl<T> RefCellMutNode<T> {
pub const fn new(value: T) -> RefCellMutNode<T> {
RefCellMutNode(RefCell::new(value))
}
}
#[derive(Default)]
pub struct OnceCellNode<T>(pub Cell<T>);
impl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode<T> {
type Output = T;
#[inline(always)]
fn eval(&'i self, _input: I) -> Self::Output {
self.0.replace(T::default())
}
}
impl<T> OnceCellNode<T> {
pub const fn new(value: T) -> OnceCellNode<T> {
OnceCellNode(Cell::new(value))
}
}
#[derive(Clone, Copy)]
pub struct ClonedNode<T: Clone>(pub T);
impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode<T> {
type Output = T;
#[inline(always)]
fn eval(&'i self, _input: I) -> Self::Output {
self.0.clone()
}
}
impl<T: Clone, Input> crate::gnode::GNode<Input> for ClonedNode<T> {
impl<T: Clone, Input> crate::node::Node<Input> for ClonedNode<T> {
type Output = T;
fn eval(&self, _input: &Input) -> crate::gpoll::GPoll<T> {
@@ -107,7 +10,7 @@ impl<T: Clone, Input> crate::gnode::GNode<Input> for ClonedNode<T> {
}
pub fn value_edge<T: Clone + crate::WasmNotSend + crate::WasmNotSync + 'static>(value: T) -> crate::registry::EdgeHandle {
crate::registry::EdgeHandle::new(std::sync::Arc::new(ClonedNode(value)) as std::sync::Arc<crate::registry::ErasedGNode<T>>)
crate::registry::EdgeHandle::new(std::sync::Arc::new(ClonedNode(value)) as std::sync::Arc<crate::registry::ErasedNode<T>>)
}
impl<T: Clone> ClonedNode<T> {
@@ -121,103 +24,3 @@ impl<T: Clone> From<T> for ClonedNode<T> {
ClonedNode::new(value)
}
}
#[derive(Clone, Copy)]
/// The DebugClonedNode logs every time it is evaluated.
/// This is useful for debugging.
pub struct DebugClonedNode<T: Clone>(pub T);
impl<'i, T: Clone + 'i> Node<'i, ()> for DebugClonedNode<T> {
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<T: Clone> DebugClonedNode<T> {
pub const fn new(value: T) -> DebugClonedNode<T> {
DebugClonedNode(value)
}
}
#[derive(Clone, Copy)]
pub struct CopiedNode<T: Copy>(pub T);
impl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode<T> {
type Output = T;
#[inline(always)]
fn eval(&'i self, _input: I) -> Self::Output {
self.0
}
}
impl<T: Copy> CopiedNode<T> {
pub const fn new(value: T) -> CopiedNode<T> {
CopiedNode(value)
}
}
#[derive(Default)]
pub struct DefaultNode<T>(PhantomData<T>);
impl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode<T> {
type Output = T;
fn eval(&'i self, _input: I) -> Self::Output {
T::default()
}
}
impl<T> DefaultNode<T> {
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::<u32>::new();
assert_eq!(node.eval(42), 0);
}
#[test]
#[allow(clippy::unit_cmp)]
fn test_unit_node() {
let node = ForgetNode::new();
assert_eq!(node.eval(()), ());
}
}

View File

@@ -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<TokenStream2> {
@@ -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<std::sync::Mutex<std::collections::HashMap<u64, Option<gcore::gpoll::GPoll<#slot_value_type>>>>> })
.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<Item = &'a crate::Generi
(fn_generic_params, phantom_data_declerations)
}
use crate::crate_ident::CrateIdent;
use crate::shader_nodes::{ShaderCodegen, ShaderTokens};
use syn::visit_mut::VisitMut;
use syn::{Lifetime, Type};
/// Get only the necessary generics.
struct FilterUsedGenerics {
@@ -582,3 +582,677 @@ pub(crate) fn type_contains_ident(ty: &Type, ident: &Ident) -> 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<NodeImplTokens> {
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<TokenStream2> = 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<TokenStream2> = 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<TokenStream2> = 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<Ident> = 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<Ident> = regular_fields.iter().enumerate().map(|(index, _)| format_ident!("Node{}", index)).collect();
let struct_type_params: Vec<Ident> = 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<dyn ::std::any::Any + Send + Sync>> {
#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<u64>,
__scratch: Option<&'__batch mut [::std::mem::MaybeUninit<Self::Output>]>,
) -> #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, &regular_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<Self::Output> {
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<ArenaRef = &#lifetime #core_types::arena::Arena>)
}
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<Type>> = 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<gcore::registry::ErasedNode<#ty>>));
let output = quote!(<#struct_name<#(#edge_types),*> as gcore::node::Node<gcore::context::ContextImpl<'static>>>::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<gcore::registry::ErasedNode<#output>>))
},
}
}
});
quote! {
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
vec![#(#entries),*]
}
}
}
fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option<Vec<Vec<Type>>> {
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<Vec<Type>> = 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::<Option<_>>()?;
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())
}

View File

@@ -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<GNodeTokens> {
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<TokenStream2> = 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<TokenStream2> = 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<TokenStream2> = 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<Ident> = 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<Ident> = regular_fields.iter().enumerate().map(|(index, _)| format_ident!("Node{}", index)).collect();
let struct_type_params: Vec<Ident> = 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<dyn ::std::any::Any + Send + Sync>> {
#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<u64>,
__scratch: Option<&'__batch mut [::std::mem::MaybeUninit<Self::Output>]>,
) -> #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, &regular_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<Self::Output> {
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<ArenaRef = &#lifetime #core_types::arena::Arena>)
}
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<Type>> = 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<gcore::registry::ErasedGNode<#ty>>));
let output = quote!(<#struct_name<#(#edge_types),*> as gcore::gnode::GNode<gcore::context::ContextImpl<'static>>>::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<gcore::registry::ErasedGNode<#output>>))
},
}
}
});
quote! {
pub fn #entries_name() -> ::std::vec::Vec<gcore::registry::RegistryEntry> {
vec![#(#entries),*]
}
}
}
fn implementation_rows(parsed: &ParsedNodeFn, regular_fields: &[&ParsedField]) -> Option<Vec<Vec<Type>>> {
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<Vec<Type>> = 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::<Option<_>>()?;
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())
}

View File

@@ -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;

View File

@@ -998,7 +998,7 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenS
let crate_ident = CrateIdent::default();
let mut parsed_node = parse_node_fn(attr, item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node function:\n{e}")))?;
parsed_node.replace_impl_trait_in_input();
if parsed_node.is_async || crate::gcodegen::is_source_kernel(&parsed_node.output_type) {
if parsed_node.is_async || crate::codegen::is_source_kernel(&parsed_node.output_type) {
let core_types = crate_ident.gcore()?.clone();
parsed_node.inject_async_source_fields(&core_types);
}

View File

@@ -23,7 +23,7 @@ pub fn validate_node_fn(parsed: &ParsedNodeFn) -> 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(),

View File

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

View File

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

View File

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

View File

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

View File

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