mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Cleanup
This commit is contained in:
@@ -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(()), ());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user