mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Cleanup
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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>>))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
@@ -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.
|
||||
|
||||
@@ -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>());
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(()), ());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ®ular_fields);
|
||||
let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes);
|
||||
let entries_reexport = match entries.is_empty() {
|
||||
true => quote!(),
|
||||
false => {
|
||||
let entries_name = format_ident!("{}_entries", fn_name);
|
||||
quote! {
|
||||
#cfg
|
||||
#[doc(hidden)]
|
||||
pub use #mod_name::#entries_name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let top_level = quote! {
|
||||
#entries_reexport
|
||||
|
||||
#cfg
|
||||
#[automatically_derived]
|
||||
impl<#(#generics,)* #(#node_generics,)*> #core_types::node::Node<#ctx_ident> for #mod_name::#struct_name<#(#struct_type_params,)*>
|
||||
where
|
||||
#(#node_bounds,)*
|
||||
#(#clampable_bounds,)*
|
||||
#(#async_bounds,)*
|
||||
#(#where_predicates,)*
|
||||
{
|
||||
type Output = #trait_output;
|
||||
|
||||
fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<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())
|
||||
}
|
||||
|
||||
@@ -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, ®ular_fields);
|
||||
let cfg = crate::shader_nodes::modify_cfg(&parsed.attributes);
|
||||
let entries_reexport = match entries.is_empty() {
|
||||
true => quote!(),
|
||||
false => {
|
||||
let entries_name = format_ident!("{}_entries", fn_name);
|
||||
quote! {
|
||||
#cfg
|
||||
#[doc(hidden)]
|
||||
pub use #mod_name::#entries_name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let top_level = quote! {
|
||||
#entries_reexport
|
||||
|
||||
#cfg
|
||||
#[automatically_derived]
|
||||
impl<#(#generics,)* #(#node_generics,)*> #core_types::gnode::GNode<#ctx_ident> for #mod_name::#struct_name<#(#struct_type_params,)*>
|
||||
where
|
||||
#(#node_bounds,)*
|
||||
#(#clampable_bounds,)*
|
||||
#(#async_bounds,)*
|
||||
#(#where_predicates,)*
|
||||
{
|
||||
type Output = #trait_output;
|
||||
|
||||
fn eval(&self, __input: &#ctx_ident) -> #core_types::gpoll::GPoll<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())
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user