diff --git a/node-graph/libraries/core-types/src/gnode.rs b/node-graph/libraries/core-types/src/gnode.rs index 4a65b36554..934701b34a 100644 --- a/node-graph/libraries/core-types/src/gnode.rs +++ b/node-graph/libraries/core-types/src/gnode.rs @@ -119,6 +119,28 @@ where } } +impl GNode for std::sync::Arc +where + N: GNode + ?Sized, +{ + type Output = N::Output; + + fn eval(&self, input: &Input) -> GPoll { + (**self).eval(input) + } + + fn extent(&self, input: &Input) -> GPoll { + (**self).extent(input) + } + + fn eval_batch<'a>(&self, input: &'a Input, range: Range, scratch: Option<&'a mut [MaybeUninit]>) -> BatchStatus<'a, Self::Output> + where + Input: InjectIndex + Copy, + { + (**self).eval_batch(input, range, scratch) + } +} + pub struct StatusCell { finality: Cell, error: Cell>, diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 9208cd0f1f..d4e3dd792d 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -83,8 +83,61 @@ pub enum ConstructionError { Type { expected: Type, found: Type }, } +pub struct SharedEdge { + ptr: std::ptr::NonNull, + own: std::sync::Arc, +} + +impl SharedEdge { + pub fn new(own: std::sync::Arc) -> Self { + Self { + ptr: std::ptr::NonNull::from(&*own), + own, + } + } + + pub fn share(&self) -> Self { + Self { + ptr: self.ptr, + own: self.own.clone(), + } + } +} + +impl GNode for SharedEdge +where + N: GNode + ?Sized, +{ + type Output = N::Output; + + fn eval(&self, input: &Input) -> crate::gpoll::GPoll { + // SAFETY: `own` keeps the payload alive for `self`'s lifetime and Arc + // payloads are address stable. + unsafe { self.ptr.as_ref() }.eval(input) + } + + fn extent(&self, input: &Input) -> crate::gpoll::GPoll { + // SAFETY: as in eval. + unsafe { self.ptr.as_ref() }.extent(input) + } + + fn eval_batch<'a>( + &self, + input: &'a Input, + range: std::ops::Range, + scratch: Option<&'a mut [std::mem::MaybeUninit]>, + ) -> crate::gnode::BatchStatus<'a, Self::Output> + where + Input: crate::context::InjectIndex + Copy, + { + // SAFETY: as in eval. + unsafe { self.ptr.as_ref() }.eval_batch(input, range, scratch) + } +} + pub struct EdgeHandle { node: Box, + share: fn(&dyn std::any::Any) -> Box, ty: Type, } @@ -95,39 +148,48 @@ impl std::fmt::Debug for EdgeHandle { } impl EdgeHandle { - pub fn new(node: Box>) -> Self { + pub fn new(node: std::sync::Arc>) -> Self { Self::new_erased(node, concrete!(T)) } - pub fn new_ref(node: Box>) -> Self { + pub fn new_ref(node: std::sync::Arc>) -> Self { Self::new_erased(node, Type::Ref(Box::new(concrete!(T)))) } - pub fn new_erased(node: Box, ty: Type) -> Self - where - Box: std::any::Any, - { - Self { node: Box::new(node), ty } + pub fn new_erased(node: std::sync::Arc, ty: Type) -> Self { + Self { + node: Box::new(SharedEdge::new(node)), + share: |edge| Box::new(edge.downcast_ref::>().expect("share hook matches the stored edge type").share()), + ty, + } } pub fn ty(&self) -> &Type { &self.ty } - pub fn downcast(self) -> Result>, ConstructionError> { + pub fn duplicate(&self) -> Self { + Self { + node: (self.share)(&*self.node), + share: self.share, + ty: self.ty.clone(), + } + } + + pub fn downcast(self) -> Result>, ConstructionError> { self.downcast_erased(concrete!(T)) } - pub fn downcast_lend(self) -> Result>, ConstructionError> { + pub fn downcast_lend(self) -> Result>, ConstructionError> { self.downcast_erased(Type::Ref(Box::new(concrete!(T)))) } - pub fn downcast_erased(self, expected: Type) -> Result, ConstructionError> - where - Box: std::any::Any, - { + pub fn downcast_erased(self, expected: Type) -> Result, ConstructionError> { let found = self.ty; - self.node.downcast::>().map(|node| *node).map_err(|_| ConstructionError::Type { expected, found }) + self.node + .downcast::>() + .map(|edge| *edge) + .map_err(|_| ConstructionError::Type { expected, found }) } } @@ -397,6 +459,18 @@ mod tests { use crate::arena::Arena; use crate::context::{Ctx, EvalScope, ExtractArena}; use crate::gpoll::GPoll; + use std::sync::Arc; + use std::sync::atomic::{AtomicU32, Ordering}; + + struct CountingNode(AtomicU32); + + impl GNode for CountingNode { + type Output = u32; + + fn eval(&self, _input: &Input) -> GPoll { + GPoll::Final(self.0.fetch_add(1, Ordering::Relaxed) + 1) + } + } struct ValueNode(T); @@ -452,9 +526,9 @@ mod tests { let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let lending = EdgeHandle::new_ref(Box::new(LendNode("held".to_string())) as Box>); + let lending = EdgeHandle::new_ref(Arc::new(LendNode("held".to_string())) as Arc>); let upstream = lending.downcast_lend::().unwrap(); - let node: Box = Box::new(SplitNode { content: upstream }); + let node: Arc = Arc::new(SplitNode { content: upstream }); let handle = EdgeHandle::new_erased(node, concrete!(SplitBorrow<'static>)); assert_eq!(*handle.ty(), concrete!(SplitBorrow<'static>)); @@ -574,7 +648,7 @@ mod tests { let mut args = args.into_iter(); let value = args.next().ok_or(ConstructionError::Arity { expected: 1, got: 0 })?.downcast::()?; drop(value); - Ok(EdgeHandle::new(Box::new(ValueNode(0u32)) as Box>)) + Ok(EdgeHandle::new(Arc::new(ValueNode(0u32)) as Arc>)) } let entry = RegistryEntry { io: NodeIoRecord { @@ -584,12 +658,12 @@ mod tests { constructor: construct_strlen, }; - let owned = EdgeHandle::new(Box::new(ValueNode("typed".to_string())) as Box>); + let owned = EdgeHandle::new(Arc::new(ValueNode("typed".to_string())) as Arc>); assert!(construct(&entry, vec![owned]).is_ok()); assert_eq!(construct(&entry, vec![]).unwrap_err(), ConstructionError::Arity { expected: 1, got: 0 }); - let mistyped = EdgeHandle::new(Box::new(ValueNode(1.0f64)) as Box>); + let mistyped = EdgeHandle::new(Arc::new(ValueNode(1.0f64)) as Arc>); assert_eq!( construct(&entry, vec![mistyped]).unwrap_err(), ConstructionError::Type { @@ -598,7 +672,7 @@ mod tests { } ); - let lent = EdgeHandle::new_ref(Box::new(LendNode("typed".to_string())) as Box>); + let lent = EdgeHandle::new_ref(Arc::new(LendNode("typed".to_string())) as Arc>); assert_eq!( construct(&entry, vec![lent]).unwrap_err(), ConstructionError::Type { @@ -607,4 +681,24 @@ mod tests { } ); } + + #[test] + fn duplicated_edges_share_one_instance_and_outlive_each_other() { + let arena = Arena::new(1024); + let generations = []; + let scope = scope_fixture(&generations, &arena); + let ctx = ContextImpl::root(&scope); + + let handle = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc>); + let duplicate = handle.duplicate(); + assert_eq!(*duplicate.ty(), concrete!(u32)); + + let first = handle.downcast::().unwrap(); + let second = duplicate.downcast::().unwrap(); + assert_eq!(first.eval(&ctx), GPoll::Final(1)); + assert_eq!(second.eval(&ctx), GPoll::Final(2)); + + drop(first); + assert_eq!(second.eval(&ctx), GPoll::Final(3)); + } } diff --git a/node-graph/node-macro/src/gcodegen.rs b/node-graph/node-macro/src/gcodegen.rs index 809214503c..d1a6429604 100644 --- a/node-graph/node-macro/src/gcodegen.rs +++ b/node-graph/node-macro/src/gcodegen.rs @@ -597,8 +597,8 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic let entries = rows.iter().map(|row| { let types = row.iter(); - let boxed_types = row.iter().map(|ty| quote!(::std::boxed::Box>)); - let output = quote!(<#struct_name<#(#boxed_types),*> as gcore::gnode::GNode>>::Output); + let edge_types = row.iter().map(|ty| quote!(gcore::registry::SharedEdge>)); + let output = quote!(<#struct_name<#(#edge_types),*> as gcore::gnode::GNode>>::Output); let downcasts = names.iter().zip(row.iter()).map(|(name, ty)| { quote!(let #name = inputs.next().unwrap().downcast::<#ty>()?;) }); @@ -614,7 +614,7 @@ fn entries_tokens(parsed: &ParsedNodeFn, struct_name: &Ident, data_field_generic } let mut inputs = inputs.into_iter(); #(#downcasts)* - Ok(gcore::registry::EdgeHandle::new(::std::boxed::Box::new(#struct_name::new(#(#names),*)) as ::std::boxed::Box>)) + Ok(gcore::registry::EdgeHandle::new(::std::sync::Arc::new(#struct_name::new(#(#names),*)) as ::std::sync::Arc>)) }, } } diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index b55c5f887b..9064854d5c 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -219,8 +219,8 @@ mod tests { let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let edge = EdgeHandle::new(Box::new(CountingNode(AtomicU32::new(0))) as Box>); - let memoized = EdgeHandle::new(Box::new(MemoizeNode::new(edge.downcast::().unwrap())) as Box>); + let edge = EdgeHandle::new(Arc::new(CountingNode(AtomicU32::new(0))) as Arc>); + let memoized = EdgeHandle::new(Arc::new(MemoizeNode::new(edge.downcast::().unwrap())) as Arc>); let stacked = MemoizeNode::new(memoized.downcast::().unwrap()); assert_eq!(stacked.eval(&ctx), GPoll::Final(1)); @@ -234,8 +234,8 @@ mod tests { let scope = scope_fixture(&generations, &arena); let ctx = ContextImpl::root(&scope); - let edge = EdgeHandle::new(Box::new(ValueNode("lent out".to_string())) as Box>); - let lending = EdgeHandle::new_ref(Box::new(FrameMemoNode::new(edge.downcast::().unwrap())) as Box>); + let edge = EdgeHandle::new(Arc::new(ValueNode("lent out".to_string())) as Arc>); + let lending = EdgeHandle::new_ref(Arc::new(FrameMemoNode::new(edge.downcast::().unwrap())) as Arc>); assert_eq!(*lending.ty(), Type::Ref(Box::new(concrete!(String)))); let node = lending.downcast_lend::().unwrap(); diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 3ab621a8c3..445789745a 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -1070,6 +1070,7 @@ mod graphene_test { use core_types::gpoll::{Finality, GPoll}; use core_types::registry::{EdgeHandle, ErasedGNode, construct}; use std::mem::MaybeUninit; + use std::sync::Arc; struct SourceNode(T); @@ -1128,8 +1129,8 @@ mod graphene_test { let ctx = ContextImpl::root(&scope); let entries = logical_or_entries(); - let value = EdgeHandle::new(Box::new(SourceNode(true)) as Box>); - let other_value = EdgeHandle::new(Box::new(SourceNode(false)) as Box>); + let value = EdgeHandle::new(Arc::new(SourceNode(true)) as Arc>); + let other_value = EdgeHandle::new(Arc::new(SourceNode(false)) as Arc>); let wired = construct(&entries[0], vec![value, other_value]).unwrap().downcast::().unwrap(); assert_eq!(GNode::eval(&wired, &ctx), GPoll::Final(true)); @@ -1148,8 +1149,8 @@ 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(Box::new(SourceNode(1.5f64)) as Box>); - let addend = EdgeHandle::new(Box::new(SourceNode(2.5f64)) as Box>); + let augend = EdgeHandle::new(Arc::new(SourceNode(1.5f64)) as Arc>); + let addend = EdgeHandle::new(Arc::new(SourceNode(2.5f64)) as Arc>); let wired = construct(&entries[0], vec![augend, addend]).unwrap().downcast::().unwrap(); assert_eq!(GNode::eval(&wired, &ctx), GPoll::Final(4.0));