From 53ad1d49ee87127182e0b1f64641368df867355c Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 24 Jan 2026 16:32:43 -0800 Subject: [PATCH] Add "Loop Level" to the Position context reader node --- node-graph/interpreted-executor/src/util.rs | 2 +- .../libraries/core-types/src/context.rs | 323 ++++++++++++------ node-graph/nodes/gcore/src/animation.rs | 6 +- node-graph/nodes/gstd/src/render_node.rs | 2 +- node-graph/nodes/vector/src/instance.rs | 26 +- 5 files changed, 247 insertions(+), 112 deletions(-) diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index f1e31c949b..562e70456a 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -57,7 +57,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc Option<&Footprint>; @@ -18,83 +22,128 @@ pub trait ExtractFootprint { }) } } - pub trait ExtractRealTime { fn try_real_time(&self) -> Option; } - pub trait ExtractAnimationTime { fn try_animation_time(&self) -> Option; } - -pub trait ExtractPointer { - fn try_pointer(&self) -> Option; +pub trait ExtractPointerPosition { + fn try_pointer_position(&self) -> Option; +} +pub trait ExtractPosition { + fn try_position(&self) -> Option>; } - pub trait ExtractIndex { fn try_index(&self) -> Option>; } - -// Consider returning a slice or something like that pub trait ExtractVarArgs { + // TODO: Consider returning a slice or something like that + fn vararg(&self, index: usize) -> Result, VarArgsResult>; fn varargs_len(&self) -> Result; fn hash_varargs(&self, hasher: &mut dyn Hasher); } -// Consider returning a slice or something like that pub trait CloneVarArgs: ExtractVarArgs { + // TODO: Consider returning a slice or something like that + // fn box_clone(&self) -> Vec; fn arc_clone(&self) -> Option>; } +// ============= +// INJECT TRAITS +// ============= + // Inject* traits for providing context features to downstream nodes pub trait InjectFootprint {} pub trait InjectRealTime {} pub trait InjectAnimationTime {} -pub trait InjectPointer {} +pub trait InjectPointerPosition {} +pub trait InjectPosition {} pub trait InjectIndex {} pub trait InjectVarArgs {} +// ================ +// EXTRACTALL TRAIT +// ================ + +pub trait ExtractAll: + // Extract traits + ExtractFootprint + + ExtractRealTime + + ExtractAnimationTime + + ExtractPointerPosition + + ExtractPosition + + ExtractIndex + + ExtractVarArgs {} +impl< + T: ?Sized + // Extract traits + + ExtractFootprint + + ExtractRealTime + + ExtractAnimationTime + + ExtractPointerPosition + + ExtractPosition + + ExtractIndex + + ExtractVarArgs, +> ExtractAll for T +{ +} + +// ============= +// INJECT TRAITS +// ============= + +impl InjectFootprint for T {} +impl InjectRealTime for T {} +impl InjectAnimationTime for T {} +impl InjectPointerPosition for T {} +impl InjectPosition for T {} +impl InjectIndex for T {} +impl InjectVarArgs for T {} + +// ============= +// MODIFY TRAITS +// ============= + // Modify* marker traits for context-transparent nodes pub trait ModifyFootprint: ExtractFootprint + InjectFootprint {} pub trait ModifyRealTime: ExtractRealTime + InjectRealTime {} pub trait ModifyAnimationTime: ExtractAnimationTime + InjectAnimationTime {} -pub trait ModifyPointer: ExtractPointer + InjectPointer {} +pub trait ModifyPointerPosition: ExtractPointerPosition + InjectPointerPosition {} +pub trait ModifyPosition: ExtractPosition + InjectPosition {} pub trait ModifyIndex: ExtractIndex + InjectIndex {} pub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {} -pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractPointer + ExtractVarArgs {} - -impl ExtractAll for T {} - -impl InjectFootprint for T {} -impl InjectRealTime for T {} -impl InjectIndex for T {} -impl InjectAnimationTime for T {} -impl InjectPointer for T {} -impl InjectVarArgs for T {} - impl ModifyFootprint for T {} impl ModifyRealTime for T {} -impl ModifyIndex for T {} impl ModifyAnimationTime for T {} -impl ModifyPointer for T {} +impl ModifyPointerPosition for T {} +impl ModifyPosition for T {} +impl ModifyIndex for T {} impl ModifyVarArgs for T {} +// ================ +// CONTEXT FEATURES +// ================ + // Public enum for flexible node macro codegen #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub enum ContextFeature { ExtractFootprint, ExtractRealTime, ExtractAnimationTime, - ExtractPointer, + ExtractPointerPosition, + ExtractPosition, ExtractIndex, ExtractVarArgs, InjectFootprint, InjectRealTime, InjectAnimationTime, - InjectPointer, + InjectPointerPosition, + InjectPosition, InjectIndex, InjectVarArgs, } @@ -107,9 +156,10 @@ bitflags! { const FOOTPRINT = 1 << 0; const REAL_TIME = 1 << 1; const ANIMATION_TIME = 1 << 2; - const POINTER = 1 << 3; - const INDEX = 1 << 4; - const VARARGS = 1 << 5; + const POINTER_POSITION = 1 << 3; + const POSITION = 1 << 4; + const INDEX = 1 << 5; + const VARARGS = 1 << 6; } } @@ -119,7 +169,8 @@ impl ContextFeatures { ContextFeatures::FOOTPRINT => "Footprint", ContextFeatures::REAL_TIME => "RealTime", ContextFeatures::ANIMATION_TIME => "AnimationTime", - ContextFeatures::POINTER => "Pointer", + ContextFeatures::POINTER_POSITION => "PointerPosition", + ContextFeatures::POSITION => "Position", ContextFeatures::INDEX => "Index", ContextFeatures::VARARGS => "VarArgs", _ => "Multiple Features", @@ -127,6 +178,10 @@ impl ContextFeatures { } } +// ==================== +// CONTEXT DEPENDENCIES +// ==================== + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)] pub struct ContextDependencies { pub extract: ContextFeatures, @@ -142,7 +197,8 @@ impl From<&[ContextFeature]> for ContextDependencies { ContextFeature::ExtractFootprint => ContextFeatures::FOOTPRINT, ContextFeature::ExtractRealTime => ContextFeatures::REAL_TIME, ContextFeature::ExtractAnimationTime => ContextFeatures::ANIMATION_TIME, - ContextFeature::ExtractPointer => ContextFeatures::POINTER, + ContextFeature::ExtractPointerPosition => ContextFeatures::POINTER_POSITION, + ContextFeature::ExtractPosition => ContextFeatures::POSITION, ContextFeature::ExtractIndex => ContextFeatures::INDEX, ContextFeature::ExtractVarArgs => ContextFeatures::VARARGS, _ => ContextFeatures::empty(), @@ -151,7 +207,8 @@ impl From<&[ContextFeature]> for ContextDependencies { ContextFeature::InjectFootprint => ContextFeatures::FOOTPRINT, ContextFeature::InjectRealTime => ContextFeatures::REAL_TIME, ContextFeature::InjectAnimationTime => ContextFeatures::ANIMATION_TIME, - ContextFeature::InjectPointer => ContextFeatures::POINTER, + ContextFeature::InjectPointerPosition => ContextFeatures::POINTER_POSITION, + ContextFeature::InjectPosition => ContextFeatures::POSITION, ContextFeature::InjectIndex => ContextFeatures::INDEX, ContextFeature::InjectVarArgs => ContextFeatures::VARARGS, _ => ContextFeatures::empty(), @@ -161,24 +218,9 @@ impl From<&[ContextFeature]> for ContextDependencies { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum VarArgsResult { - IndexOutOfBounds, - NoVarArgs, -} -impl Ctx for Footprint {} -impl ExtractFootprint for () { - fn try_footprint(&self) -> Option<&Footprint> { - log::error!("tried to extract footprint form (), {}", Location::caller()); - None - } -} - -impl ExtractFootprint for &T { - fn try_footprint(&self) -> Option<&Footprint> { - (*self).try_footprint() - } -} +// =================================== +// EXTRACT TRAIT IMPLS FOR `Option` +// =================================== impl ExtractFootprint for Option { fn try_footprint(&self) -> Option<&Footprint> { @@ -202,9 +244,14 @@ impl ExtractAnimationTime for Option { self.as_ref().and_then(|x| x.try_animation_time()) } } -impl ExtractPointer for Option { - fn try_pointer(&self) -> Option { - self.as_ref().and_then(|x| x.try_pointer()) +impl ExtractPointerPosition for Option { + fn try_pointer_position(&self) -> Option { + self.as_ref().and_then(|x| x.try_pointer_position()) + } +} +impl ExtractPosition for Option { + fn try_position(&self) -> Option> { + self.as_ref().and_then(|x| x.try_position()) } } impl ExtractIndex for Option { @@ -229,6 +276,17 @@ impl ExtractVarArgs for Option { } } } + +impl CloneVarArgs for Option { + fn arc_clone(&self) -> Option> { + self.as_ref().and_then(CloneVarArgs::arc_clone) + } +} + +// ================================ +// EXTRACT TRAIT IMPLS FOR `Arc` +// ================================ + impl ExtractFootprint for Arc { fn try_footprint(&self) -> Option<&Footprint> { (**self).try_footprint() @@ -244,9 +302,14 @@ impl ExtractAnimationTime for Arc { (**self).try_animation_time() } } -impl ExtractPointer for Arc { - fn try_pointer(&self) -> Option { - (**self).try_pointer() +impl ExtractPointerPosition for Arc { + fn try_pointer_position(&self) -> Option { + (**self).try_pointer_position() + } +} +impl ExtractPosition for Arc { + fn try_position(&self) -> Option> { + (**self).try_position() } } impl ExtractIndex for Arc { @@ -267,9 +330,20 @@ impl ExtractVarArgs for Arc { (**self).hash_varargs(hasher) } } -impl CloneVarArgs for Option { + +impl CloneVarArgs for Arc { fn arc_clone(&self) -> Option> { - self.as_ref().and_then(CloneVarArgs::arc_clone) + (**self).arc_clone() + } +} + +// ============================ +// EXTRACT TRAIT IMPLS FOR `&T` +// ============================ + +impl ExtractFootprint for &T { + fn try_footprint(&self) -> Option<&Footprint> { + (*self).try_footprint() } } @@ -286,14 +360,25 @@ impl ExtractVarArgs for &T { (*self).hash_varargs(hasher) } } -impl CloneVarArgs for Arc { - fn arc_clone(&self) -> Option> { - (**self).arc_clone() + +// ============================ +// EXTRACT TRAIT IMPLS FOR `()` +// ============================ + +impl Ctx for Footprint {} + +impl ExtractFootprint for () { + fn try_footprint(&self) -> Option<&Footprint> { + log::error!("tried to extract footprint form (), {}", Location::caller()); + None } } +// ===================================== +// EXTRACT TRAIT IMPLS FOR `ContextImpl` +// ===================================== + impl Ctx for ContextImpl<'_> {} -impl ArcCtx for OwnedContextImpl {} impl ExtractFootprint for ContextImpl<'_> { fn try_footprint(&self) -> Option<&Footprint> { @@ -305,6 +390,11 @@ impl ExtractRealTime for ContextImpl<'_> { self.real_time } } +impl ExtractPosition for ContextImpl<'_> { + fn try_position(&self) -> Option> { + self.position.clone().map(|x| x.into_iter()) + } +} impl ExtractIndex for ContextImpl<'_> { fn try_index(&self) -> Option> { self.index.clone().map(|x| x.into_iter()) @@ -326,6 +416,12 @@ impl ExtractVarArgs for ContextImpl<'_> { } } +// ========================================== +// EXTRACT TRAIT IMPLS FOR `OwnedContextImpl` +// ========================================== + +impl ArcCtx for OwnedContextImpl {} + impl ExtractFootprint for OwnedContextImpl { fn try_footprint(&self) -> Option<&Footprint> { self.footprint.as_ref() @@ -341,9 +437,14 @@ impl ExtractAnimationTime for OwnedContextImpl { self.animation_time } } -impl ExtractPointer for OwnedContextImpl { - fn try_pointer(&self) -> Option { - self.pointer +impl ExtractPointerPosition for OwnedContextImpl { + fn try_pointer_position(&self) -> Option { + self.pointer_position + } +} +impl ExtractPosition for OwnedContextImpl { + fn try_position(&self) -> Option> { + self.position.clone().map(|x| x.into_iter()) } } impl ExtractIndex for OwnedContextImpl { @@ -393,32 +494,37 @@ impl CloneVarArgs for Arc { } } +// ====================================== +// TYPES `Context` AND `OwnedContextImpl` +// ====================================== + pub type Context<'a> = Option>; type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynBox = Box; #[derive(dyn_any::DynAny)] pub struct OwnedContextImpl { - footprint: Option, - varargs: Option>, parent: Option>, - // This could be converted into a single enum to save extra bytes - index: Option>, + footprint: Option, real_time: Option, animation_time: Option, - pointer: Option, + pointer_position: Option, + position: Option>, + // This could be converted into a single enum to save extra bytes + index: Option>, + varargs: Option>, } impl std::fmt::Debug for OwnedContextImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("OwnedContextImpl") - .field("footprint", &self.footprint) - .field("varargs_len", &self.varargs.as_ref().map(|x| x.len())) .field("parent", &self.parent.as_ref().map(|_| "")) - .field("index", &self.index) + .field("footprint", &self.footprint) .field("real_time", &self.real_time) .field("animation_time", &self.animation_time) - .field("pointer", &self.pointer) + .field("pointer_position", &self.pointer_position) + .field("index", &self.index) + .field("varargs_len", &self.varargs.as_ref().map(|x| x.len())) .finish() } } @@ -433,11 +539,12 @@ impl Default for OwnedContextImpl { impl Hash for OwnedContextImpl { fn hash(&self, state: &mut H) { self.footprint.hash(state); - self.hash_varargs(state); - self.index.hash(state); self.real_time.map(|x| x.to_bits()).hash(state); self.animation_time.map(|x| x.to_bits()).hash(state); - self.pointer.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state); + self.pointer_position.map(|v| (v.x.to_bits(), v.y.to_bits())).hash(state); + self.position.iter().flat_map(|x| x.iter()).map(|v| (v.x.to_bits(), v.y.to_bits())).for_each(|v| v.hash(state)); + self.index.hash(state); + self.hash_varargs(state); } } @@ -449,11 +556,6 @@ impl OwnedContextImpl { #[track_caller] pub fn from_flags(value: T, bitflags: ContextFeatures) -> Self { - let footprint = bitflags.contains(ContextFeatures::FOOTPRINT).then(|| value.try_footprint().copied()).flatten(); - let index = bitflags.contains(ContextFeatures::INDEX).then(|| value.try_index()).flatten(); - let real_time = bitflags.contains(ContextFeatures::REAL_TIME).then(|| value.try_real_time()).flatten(); - let animation_time = bitflags.contains(ContextFeatures::ANIMATION_TIME).then(|| value.try_animation_time()).flatten(); - let pointer = bitflags.contains(ContextFeatures::POINTER).then(|| value.try_pointer()).flatten(); let parent = bitflags .contains(ContextFeatures::VARARGS) .then(|| match value.varargs_len() { @@ -461,27 +563,35 @@ impl OwnedContextImpl { _ => None, }) .flatten(); + let footprint = bitflags.contains(ContextFeatures::FOOTPRINT).then(|| value.try_footprint().copied()).flatten(); + let real_time = bitflags.contains(ContextFeatures::REAL_TIME).then(|| value.try_real_time()).flatten(); + let animation_time = bitflags.contains(ContextFeatures::ANIMATION_TIME).then(|| value.try_animation_time()).flatten(); + let pointer_position = bitflags.contains(ContextFeatures::POINTER_POSITION).then(|| value.try_pointer_position()).flatten(); + let position = bitflags.contains(ContextFeatures::POSITION).then(|| value.try_position()).flatten().map(|x| x.collect()); + let index = bitflags.contains(ContextFeatures::INDEX).then(|| value.try_index()).flatten().map(|x| x.collect()); OwnedContextImpl { - footprint, - varargs: None, parent, - index: index.map(|x| x.collect()), + footprint, real_time, animation_time, - pointer, + pointer_position, + position, + index, + varargs: None, } } pub const fn empty() -> Self { OwnedContextImpl { - footprint: None, - varargs: None, parent: None, - index: None, + footprint: None, real_time: None, animation_time: None, - pointer: None, + pointer_position: None, + position: None, + index: None, + varargs: None, } } } @@ -514,6 +624,7 @@ impl OwnedContextImpl { pub fn set_footprint(&mut self, footprint: Footprint) { self.footprint = Some(footprint); } + pub fn with_footprint(mut self, footprint: Footprint) -> Self { self.footprint = Some(footprint); self @@ -526,13 +637,16 @@ impl OwnedContextImpl { self.animation_time = Some(animation_time); self } - pub fn with_pointer(mut self, pointer: DVec2) -> Self { - self.pointer = Some(pointer); + pub fn with_pointer_position(mut self, pointer_position: DVec2) -> Self { + self.pointer_position = Some(pointer_position); self } - pub fn with_vararg(mut self, value: Box) -> Self { - assert!(self.varargs.is_none_or(|value| value.is_empty())); - self.varargs = Some(Arc::new([value])); + pub fn with_position(mut self, position: DVec2) -> Self { + if let Some(current_position) = &mut self.position { + current_position.insert(0, position); + } else { + self.position = Some(vec![position]); + } self } pub fn with_index(mut self, index: usize) -> Self { @@ -543,6 +657,11 @@ impl OwnedContextImpl { } self } + pub fn with_vararg(mut self, value: Box) -> Self { + assert!(self.varargs.is_none_or(|value| value.is_empty())); + self.varargs = Some(Arc::new([value])); + self + } pub fn into_context(self) -> Option> { Some(Arc::new(self)) } @@ -555,9 +674,10 @@ impl OwnedContextImpl { #[derive(Default, Clone, dyn_any::DynAny)] pub struct ContextImpl<'a> { pub(crate) footprint: Option<&'a Footprint>, - varargs: Option<&'a [DynRef<'a>]>, - index: Option>, // This could be converted into a single enum to save extra bytes real_time: Option, + position: Option>, // This could be converted into a single enum to save extra bytes + index: Option>, // This could be converted into a single enum to save extra bytes + varargs: Option<&'a [DynRef<'a>]>, } impl<'a> ContextImpl<'a> { @@ -567,9 +687,16 @@ impl<'a> ContextImpl<'a> { { ContextImpl { footprint: Some(new_footprint), - varargs: varargs.map(|x| x.borrow()), + position: self.position.clone(), index: self.index.clone(), + varargs: varargs.map(|x| x.borrow()), ..*self } } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VarArgsResult { + IndexOutOfBounds, + NoVarArgs, +} diff --git a/node-graph/nodes/gcore/src/animation.rs b/node-graph/nodes/gcore/src/animation.rs index b2aca80802..7404a4b610 100644 --- a/node-graph/nodes/gcore/src/animation.rs +++ b/node-graph/nodes/gcore/src/animation.rs @@ -1,4 +1,4 @@ -use core_types::{Ctx, ExtractAnimationTime, ExtractPointer, ExtractRealTime}; +use core_types::{Ctx, ExtractAnimationTime, ExtractPointerPosition, ExtractRealTime}; use glam::DVec2; const DAY: f64 = 1000. * 3600. * 24.; @@ -50,8 +50,8 @@ fn animation_time(ctx: impl Ctx + ExtractAnimationTime) -> f64 { /// Produces the current position of the user's pointer within the document canvas. #[node_macro::node(category("Animation"))] -fn pointer_position(ctx: impl Ctx + ExtractPointer) -> DVec2 { - ctx.try_pointer().unwrap_or_default() +fn pointer_position(ctx: impl Ctx + ExtractPointerPosition) -> DVec2 { + ctx.try_pointer_position().unwrap_or_default() } // TODO: These nodes require more sophisticated algorithms for giving the correct result diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 6f09b8835c..8665ab7d2f 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -114,7 +114,7 @@ async fn create_context<'a: 'n>( .with_footprint(footprint) .with_real_time(render_config.time.time) .with_animation_time(render_config.time.animation_time.as_secs_f64()) - .with_pointer(render_config.pointer) + .with_pointer_position(render_config.pointer) .with_vararg(Box::new(render_params)) .into_context(); diff --git a/node-graph/nodes/vector/src/instance.rs b/node-graph/nodes/vector/src/instance.rs index e420946d15..7e277bff6c 100644 --- a/node-graph/nodes/vector/src/instance.rs +++ b/node-graph/nodes/vector/src/instance.rs @@ -1,14 +1,12 @@ use core_types::Color; use core_types::table::{Table, TableRowRef}; -use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, InjectVarArgs, OwnedContextImpl}; +use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractPosition, InjectVarArgs, OwnedContextImpl}; use glam::DVec2; use graphic_types::Graphic; use graphic_types::Vector; use graphic_types::raster_types::{CPU, Raster}; use vector_types::GradientStops; -use log::*; - #[repr(transparent)] #[derive(dyn_any::DynAny)] struct HashableDVec2(DVec2); @@ -97,13 +95,23 @@ async fn instance_repeat + Default + Send + Clone + 'static>( } #[node_macro::node(category("Instancing"), path(core_types::vector))] -async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 { - match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::()) { - Ok(Some(position)) => return position.0, - Ok(_) => warn!("Extracted value of incorrect type"), - Err(e) => warn!("Cannot extract position vararg: {e:?}"), +async fn instance_position( + ctx: impl Ctx + ExtractPosition, + _primary: (), + /// The number of nested loops to traverse outwards (from the innermost loop) to get the position from. The most upstream loop is level 0, and downstream loops add levels. + /// + /// In programming terms: inside the double loop `i { j { ... } }`, *Loop Level* 0 = `j` and 1 = `i`. After inserting a third loop `k { ... }`, inside it, levels would be 0 = `k`, 1 = `j`, and 2 = `i`. + loop_level: u32, +) -> DVec2 { + let Some(position_iter) = ctx.try_position() else { return DVec2::ZERO }; + let mut last = DVec2::ZERO; + for (i, position) in position_iter.enumerate() { + if i == loop_level as usize { + return position; + } + last = position; } - Default::default() + last } // TODO: Return u32, u64, or usize instead of f64 after #1621 is resolved and has allowed us to implement automatic type conversion in the node graph for nodes with generic type inputs.