diff --git a/node-graph/libraries/core-types/src/context.rs b/node-graph/libraries/core-types/src/context.rs index d0ba3778ca..509389cd09 100644 --- a/node-graph/libraries/core-types/src/context.rs +++ b/node-graph/libraries/core-types/src/context.rs @@ -592,6 +592,38 @@ impl Hash for Box { pub trait AnyHash: DynHash + Any {} impl AnyHash for T {} +pub trait VarArg: AnyHash { + fn clone_slot(&self) -> OwnedSlot; +} + +impl VarArg for T { + fn clone_slot(&self) -> OwnedSlot { + OwnedSlot(Box::new(self.clone())) + } +} + +pub struct OwnedSlot(Box); + +impl Clone for OwnedSlot { + fn clone(&self) -> Self { + self.0.clone_slot() + } +} + +impl std::ops::Deref for OwnedSlot { + type Target = dyn VarArg + Send + Sync; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl std::fmt::Debug for OwnedSlot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("OwnedSlot") + } +} + impl OwnedContextImpl { pub fn set_footprint(&mut self, footprint: Footprint) { self.footprint = Some(footprint); @@ -657,7 +689,7 @@ pub struct PositionLink<'a> { pub outer: Option<&'a PositionLink<'a>>, } -pub type DynSlot<'a> = &'a (dyn AnyHash + Send + Sync); +pub type DynSlot<'a> = &'a (dyn VarArg + Send + Sync); #[derive(Clone, Copy)] pub enum VarArgSlots<'a> { @@ -857,6 +889,7 @@ pub struct CtxSnapshot { pointer_position: Option, index: Vec, positions: Vec, + varargs: Vec>, generations: Vec<(SourceId, u64)>, } @@ -872,6 +905,9 @@ impl CtxSnapshot { pointer_position: ctx.try_pointer_position(), index: ctx.try_index().map(|levels| levels.collect()).unwrap_or_default(), positions: ctx.try_position().map(|positions| positions.collect()).unwrap_or_default(), + varargs: std::iter::successors(ctx.varargs_head(), |link| link.outer) + .map(|link| link.args.iter().map(|slot| slot.clone_slot()).collect()) + .collect(), generations: ctx.scope().generations().to_vec(), } } @@ -921,6 +957,32 @@ impl ExtractPosition for CtxSnapshot { } } +impl ExtractVarArgs for CtxSnapshot { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + if self.varargs.is_empty() { + return Err(VarArgsResult::NoVarArgs); + } + let slot = self.varargs.iter().flatten().nth(index).ok_or(VarArgsResult::IndexOutOfBounds)?; + Ok(&**slot as DynRef<'_>) + } + + fn varargs_len(&self) -> Result { + if self.varargs.is_empty() { + return Err(VarArgsResult::NoVarArgs); + } + Ok(self.varargs.iter().map(|level| level.len()).sum()) + } + + fn hash_varargs(&self, hasher: &mut dyn Hasher) { + let mut count = 0u64; + for slot in self.varargs.iter().flatten() { + slot.dyn_hash(&mut *hasher); + count += 1; + } + count.hash(&mut &mut *hasher); + } +} + pub struct VarArgScope<'c, C> { ctx: &'c C, link: VarArgLink<'c>, @@ -1356,6 +1418,51 @@ mod context_impl_tests { assert_ne!(hash_of(&outer_ctx), hash_of(&inner_ctx)); } + #[test] + fn snapshot_captures_vararg_levels() { + let arena = Arena::new(64); + let generations = []; + let scope = scope_fixture(&generations, &arena); + let root = ContextImpl::root(&scope); + + let outer_value = 7u32; + let outer_args: [DynSlot; 1] = [&outer_value]; + let outer_link = VarArgLink { + args: VarArgSlots::Slice(&outer_args), + outer: None, + }; + let inner_value = String::from("inner"); + let inner_link = VarArgLink { + args: VarArgSlots::Single(&inner_value), + outer: Some(&outer_link), + }; + let ctx = root.with_varargs(&inner_link); + + let snapshot = CtxSnapshot::capture(&ctx); + assert_eq!(snapshot.varargs_len(), Ok(2)); + assert_eq!(snapshot.vararg(0).unwrap().downcast_ref::(), Some(&inner_value)); + assert_eq!(snapshot.vararg(1).unwrap().downcast_ref::(), Some(&outer_value)); + assert!(matches!(snapshot.vararg(2), Err(VarArgsResult::IndexOutOfBounds))); + + let hash_via = |target: &dyn Fn(&mut dyn Hasher)| { + let mut hasher = std::hash::DefaultHasher::new(); + target(&mut hasher); + hasher.finish() + }; + assert_eq!( + hash_via(&|hasher| snapshot.hash_varargs(hasher)), + hash_via(&|hasher| ctx.hash_varargs(hasher)), + "snapshot varargs must hash like the borrowed chain" + ); + + let cloned = snapshot.clone(); + assert_eq!(cloned.vararg(0).unwrap().downcast_ref::(), Some(&inner_value)); + + let empty = CtxSnapshot::capture(&root); + assert_eq!(empty.varargs_len(), Err(VarArgsResult::NoVarArgs)); + assert!(matches!(empty.vararg(0), Err(VarArgsResult::NoVarArgs))); + } + #[test] fn scope_arena_reaches_kernels_through_extract_arena() { let arena = Arena::new(1024); diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index f610c0154f..9625f38dae 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -109,7 +109,7 @@ pub fn extract_element( } #[node_macro::node(category("General"))] -fn map( +fn map( ctx: impl Ctx + DeriveCtx, #[implementations( List,