diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index d8b2445532..7d329e3c04 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -505,7 +505,13 @@ impl ProtoNetwork { let (extract, inject, own_deps) = { let dependencies = &self.nodes[node_index].1.context_features; - let own_deps = ContextModification::from_sources(dependencies.extract, dependencies.sources()); + // Documents predating the level mask deserialize as all-levels, so a + // node that declares no index read must not contribute one. + let index_levels = match dependencies.extract.contains(core_types::context::ContextFeatures::INDEX) { + true => dependencies.index_levels, + false => core_types::context::IndexLevels::empty(), + }; + let own_deps = ContextModification::from_sources(dependencies.extract, dependencies.sources()).with_index_levels(index_levels); (dependencies.extract, dependencies.inject, own_deps) }; diff --git a/node-graph/libraries/core-types/src/context.rs b/node-graph/libraries/core-types/src/context.rs index 327788d187..7d1fd3d041 100644 --- a/node-graph/libraries/core-types/src/context.rs +++ b/node-graph/libraries/core-types/src/context.rs @@ -34,7 +34,11 @@ pub trait ExtractPointerPosition { pub trait ExtractPosition { fn try_position(&self) -> Option>; } -pub trait ExtractIndex { +/// The level sentinel for a reader whose level is not known at compile time, +/// so every level of the index chain must survive nullification. +pub const ALL_INDEX_LEVELS: u8 = u8::MAX; + +pub trait ExtractIndices { fn try_index(&self) -> Option>; fn innermost_index(&self) -> u64 { self.try_index().and_then(|mut indices| indices.next()).unwrap_or(0) as u64 @@ -52,6 +56,18 @@ pub trait ExtractIndex { } } } + +/// `LEVEL` is the level of the index chain a node reads, counted outwards from +/// the innermost. Declaring it narrows the cache key to that level, and reading +/// through `index` keeps the declaration and the read from drifting apart. +/// Repeat the bound to declare several, then spell the level at each read. +pub trait ExtractIndex: ExtractIndices { + fn index(&self) -> u64 { + self.try_index().and_then(|mut levels| levels.nth(LEVEL as usize)).unwrap_or(0) as u64 + } +} + +impl ExtractIndex for T {} pub trait ExtractVarArgs { // TODO: Consider returning a slice or something like that @@ -154,7 +170,8 @@ pub enum ContextFeature { ExtractAnimationTime, ExtractPointerPosition, ExtractPosition, - ExtractIndex, + /// Carries the declared level, counted outwards from the innermost. + ExtractIndex(u8), ExtractVarArgs, InjectFootprint, InjectRealTime, @@ -187,6 +204,93 @@ impl graphene_hash::CacheHash for ContextFeatures { } } +/// Which levels of the index chain survive nullification, as a mask over levels +/// counted outwards from the innermost. Levels past 31 collapse to "all". +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, dyn_any::DynAny)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct IndexLevels(u32); + +impl IndexLevels { + pub const fn empty() -> Self { + Self(0) + } + + pub const fn all() -> Self { + Self(u32::MAX) + } + + /// The innermost level alone, which is what every reader but `read_index` + /// addresses, so documents predating the mask migrate to it. + pub const fn innermost() -> Self { + Self(1) + } + + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + pub fn with_level(self, level: u8) -> Self { + match level { + ALL_INDEX_LEVELS => Self::all(), + level if (level as u32) < u32::BITS => Self(self.0 | 1 << level), + _ => Self::all(), + } + } + + pub const fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } + + pub const fn is_all(self) -> bool { + self.0 == u32::MAX + } + + /// Levels at or past `u32::BITS` are only readable through the all-levels + /// sentinel, so they count as declared whenever the mask is saturated. + pub const fn contains_level(self, level: usize) -> bool { + match level < u32::BITS as usize { + true => self.0 & 1 << level != 0, + false => self.is_all(), + } + } +} + +/// Zeroes every index level outside `levels`, preserving the chain's depth so +/// outer levels keep their positions for readers that do address them. Returns +/// `None` only when the arena is exhausted. +pub fn nullify_index_levels<'s>(head: IndexLink<'s>, levels: IndexLevels, arena: &'s crate::arena::Arena) -> Option> { + if levels.is_all() { + return Some(head); + } + let indices: Vec = core::iter::successors(Some(&head), |link| link.outer).map(|link| link.index).collect(); + let masked = |level: usize| match levels.contains_level(level) { + true => indices[level], + false => 0, + }; + if (0..indices.len()).all(|level| levels.contains_level(level) || indices[level] == 0) { + return Some(head); + } + // Built outermost inwards so each link can reference the one already placed. + let mut outer: Option<&'s IndexLink<'s>> = None; + for level in (1..indices.len()).rev() { + let link = IndexLink { index: masked(level), outer }; + outer = Some(arena.alloc(link)?.0); + } + Some(IndexLink { index: masked(0), outer }) +} + +impl core::ops::BitOrAssign for IndexLevels { + fn bitor_assign(&mut self, other: Self) { + self.0 |= other.0; + } +} + +impl graphene_hash::CacheHash for IndexLevels { + fn cache_hash(&self, state: &mut H) { + core::hash::Hash::hash(self, state); + } +} + impl ContextFeatures { pub fn name(&self) -> &'static str { match *self { @@ -211,13 +315,31 @@ impl ContextFeatures { pub struct ContextDependencies { pub extract: ContextFeatures, pub inject: ContextFeatures, + /// Which index levels the node reads; empty means it reads none. Documents + /// written before the field existed default to the whole chain. + #[cfg_attr(feature = "serde", serde(default = "IndexLevels::innermost"))] + pub index_levels: IndexLevels, #[cfg_attr(feature = "serde", serde(default, deserialize_with = "deserialize_sorted_sources"))] sources: Vec, } impl ContextDependencies { pub fn new(extract: ContextFeatures, inject: ContextFeatures) -> Self { - Self { extract, inject, sources: Vec::new() } + let index_levels = match extract.contains(ContextFeatures::INDEX) { + true => IndexLevels::innermost(), + false => IndexLevels::empty(), + }; + Self { + extract, + inject, + index_levels, + sources: Vec::new(), + } + } + + pub fn with_index_levels(mut self, index_levels: IndexLevels) -> Self { + self.index_levels = index_levels; + self } pub fn sources(&self) -> &[SourceId] { @@ -239,6 +361,9 @@ impl ContextDependencies { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ContextModification { pub features: ContextFeatures, + /// Which index levels survive; consulted only when `features` keeps `INDEX`. + #[cfg_attr(feature = "serde", serde(default = "IndexLevels::innermost"))] + pub index_levels: IndexLevels, #[cfg_attr(feature = "serde", serde(deserialize_with = "deserialize_sorted_sources"))] sources: Vec, } @@ -253,10 +378,23 @@ impl ContextModification { } pub fn from_sources(features: ContextFeatures, sources: &[SourceId]) -> Self { - let mut modification = Self { features, sources: Vec::new() }; + let index_levels = match features.contains(ContextFeatures::INDEX) { + true => IndexLevels::all(), + false => IndexLevels::empty(), + }; + let mut modification = Self { + features, + index_levels, + sources: Vec::new(), + }; modification.add_sources(sources); modification } + + pub fn with_index_levels(mut self, index_levels: IndexLevels) -> Self { + self.index_levels = index_levels; + self + } } /// Restores the sorted-and-deduplicated invariant that `contains` and `difference` @@ -273,6 +411,7 @@ fn deserialize_sorted_sources<'de, D: serde::Deserializer<'de>>(deserializer: D) impl core::ops::BitOrAssign<&ContextModification> for ContextModification { fn bitor_assign(&mut self, other: &Self) { self.features |= other.features; + self.index_levels |= other.index_levels; merge_sorted_sources(&mut self.sources, &other.sources); } } @@ -298,13 +437,14 @@ impl core::ops::BitAndAssign for ContextModification { impl ContextModification { pub fn contains(&self, other: &Self) -> bool { debug_assert!(self.sources.is_sorted() && other.sources.is_sorted()); - self.features.contains(other.features) && other.sources.iter().all(|id| self.sources.binary_search(id).is_ok()) + self.features.contains(other.features) && self.index_levels.contains(other.index_levels) && other.sources.iter().all(|id| self.sources.binary_search(id).is_ok()) } pub fn difference(&self, other: &Self) -> Self { debug_assert!(other.sources.is_sorted()); Self { features: self.features.difference(other.features), + index_levels: self.index_levels, sources: self.sources.iter().copied().filter(|id| other.sources.binary_search(id).is_err()).collect(), } } @@ -326,14 +466,18 @@ impl From<&[ContextFeature]> for ContextDependencies { fn from(features: &[ContextFeature]) -> Self { let mut extract = ContextFeatures::empty(); let mut inject = ContextFeatures::empty(); + let mut index_levels = IndexLevels::empty(); for feature in features { + if let ContextFeature::ExtractIndex(level) = feature { + index_levels = index_levels.with_level(*level); + } extract |= match feature { ContextFeature::ExtractFootprint => ContextFeatures::FOOTPRINT, ContextFeature::ExtractRealTime => ContextFeatures::REAL_TIME, ContextFeature::ExtractAnimationTime => ContextFeatures::ANIMATION_TIME, ContextFeature::ExtractPointerPosition => ContextFeatures::POINTER_POSITION, ContextFeature::ExtractPosition => ContextFeatures::POSITION, - ContextFeature::ExtractIndex => ContextFeatures::INDEX, + ContextFeature::ExtractIndex(_) => ContextFeatures::INDEX, ContextFeature::ExtractVarArgs => ContextFeatures::VARARGS, _ => ContextFeatures::empty(), }; @@ -348,7 +492,12 @@ impl From<&[ContextFeature]> for ContextDependencies { _ => ContextFeatures::empty(), }; } - Self { extract, inject, sources: Vec::new() } + Self { + extract, + inject, + index_levels, + sources: Vec::new(), + } } } @@ -388,7 +537,7 @@ impl ExtractPosition for Option { self.as_ref().and_then(|x| x.try_position()) } } -impl ExtractIndex for Option { +impl ExtractIndices for Option { fn try_index(&self) -> Option> { self.as_ref().and_then(|x| x.try_index()) } @@ -446,7 +595,7 @@ impl ExtractPosition for Arc { (**self).try_position() } } -impl ExtractIndex for Arc { +impl ExtractIndices for Arc { fn try_index(&self) -> Option> { (**self).try_index() } @@ -739,7 +888,7 @@ pub trait DeriveCtx { fn with_varargs<'s>(&'s self, varargs: &'s VarArgLink<'s>) -> Derived<'s, Self>; fn with_position<'s>(&'s self, position: &'s PositionLink<'s>) -> Derived<'s, Self>; fn with_scope<'s>(&'s self, scope: &'s EvalScope<'s>) -> Derived<'s, Self>; - fn nullified<'s>(&'s self, keep: ContextFeatures, scope: &'s EvalScope<'s>) -> Derived<'s, Self>; + fn nullified<'s>(&'s self, keep: ContextFeatures, index: IndexLink<'s>, scope: &'s EvalScope<'s>) -> Derived<'s, Self>; fn modify_footprint(&self, modify: impl FnOnce(&mut Footprint)) -> ModifiedFootprint<'_, Self> where @@ -850,7 +999,7 @@ impl ExtractPointerPosition for CtxSnapshot { } } -impl ExtractIndex for CtxSnapshot { +impl ExtractIndices for CtxSnapshot { fn try_index(&self) -> Option> { self.index.as_ref().map(|levels| levels.iter().copied()) } @@ -975,15 +1124,12 @@ impl<'a> ContextImpl<'a> { ContextImpl { position: Some(position), ..*self } } - pub fn nullified<'s>(&self, keep: ContextFeatures, scope: &'s EvalScope<'s>) -> ContextImpl<'s> + pub fn nullified<'s>(&self, keep: ContextFeatures, index: IndexLink<'s>, scope: &'s EvalScope<'s>) -> ContextImpl<'s> where 'a: 's, { ContextImpl { - index: match keep.contains(ContextFeatures::INDEX) { - true => self.index, - false => IndexLink { index: 0, outer: None }, - }, + index, position: self.position.filter(|_| keep.contains(ContextFeatures::POSITION)), varargs: self.varargs.filter(|_| keep.contains(ContextFeatures::VARARGS)), footprint: self.footprint.filter(|_| keep.contains(ContextFeatures::FOOTPRINT)), @@ -1060,7 +1206,7 @@ impl ExtractPointerPosition for ContextImpl<'_> { self.scope.pointer_position } } -impl ExtractIndex for ContextImpl<'_> { +impl ExtractIndices for ContextImpl<'_> { fn try_index(&self) -> Option> { Some(std::iter::successors(Some(&self.index), |link| link.outer).map(|link| link.index as usize)) } @@ -1161,8 +1307,8 @@ impl<'a> DeriveCtx for ContextImpl<'a> { ContextImpl::with_scope(self, scope) } - fn nullified<'s>(&'s self, keep: ContextFeatures, scope: &'s EvalScope<'s>) -> ContextImpl<'s> { - ContextImpl::nullified(self, keep, scope) + fn nullified<'s>(&'s self, keep: ContextFeatures, index: IndexLink<'s>, scope: &'s EvalScope<'s>) -> ContextImpl<'s> { + ContextImpl::nullified(self, keep, index, scope) } } diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 291aad8dca..62dee300b5 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -345,7 +345,7 @@ pub(crate) struct Input { pub(crate) pat_ident: PatIdent, pub(crate) ty: Type, pub(crate) implementations: Punctuated, - pub(crate) context_features: Vec, + pub(crate) context_features: Vec, } impl Parse for Implementation { @@ -868,8 +868,46 @@ fn parse_read_tuple(pat_tuple: &syn::PatTuple, ty: &Type, attrs: &[Attribute], i Ok(field) } +/// A declared context feature; `ExtractIndex` carries the index level it reads. +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct ContextFeatureDecl { + pub(crate) ident: Ident, + pub(crate) level: Option, +} + +impl ContextFeatureDecl { + pub(crate) fn new(ident: Ident) -> Self { + Self { ident, level: None } + } +} + +impl quote::ToTokens for ContextFeatureDecl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ident = &self.ident; + match self.level { + Some(level) => tokens.extend(quote::quote!(#ident(#level))), + None => ident.to_tokens(tokens), + } + } +} + +/// The level of an `ExtractIndex` bound, defaulting to the innermost. +fn parse_index_level(segment: &syn::PathSegment) -> u8 { + let syn::PathArguments::AngleBracketed(arguments) = &segment.arguments else { + return 0; + }; + for argument in &arguments.args { + if let syn::GenericArgument::Const(syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Int(int), .. })) = argument + && let Ok(level) = int.base10_parse::() + { + return level; + } + } + 0 +} + /// Parse context feature identifiers from the trait bounds of a context parameter. -fn parse_context_feature_idents(ty: &Type) -> Vec { +fn parse_context_feature_idents(ty: &Type) -> Vec { let mut features = Vec::new(); // Check if this is an impl trait (impl Ctx + ...) @@ -879,12 +917,20 @@ fn parse_context_feature_idents(ty: &Type) -> Vec { // Extract the last segment of the trait path if let Some(segment) = path.segments.last() { match segment.ident.to_string().as_str() { + "ExtractIndex" => features.push(ContextFeatureDecl { + ident: segment.ident.clone(), + level: Some(parse_index_level(segment)), + }), + // Reading the chain without a statically known level keeps every level. + "ExtractIndices" => features.push(ContextFeatureDecl { + ident: format_ident!("ExtractIndex"), + level: Some(u8::MAX), + }), "ExtractFootprint" | "ExtractRealTime" | "ExtractAnimationTime" | "ExtractPointerPosition" | "ExtractPosition" - | "ExtractIndex" | "ExtractVarArgs" | "InjectFootprint" | "InjectRealTime" @@ -892,7 +938,7 @@ fn parse_context_feature_idents(ty: &Type) -> Vec { | "InjectPointerPosition" | "InjectPosition" | "InjectVarArgs" => { - features.push(segment.ident.clone()); + features.push(ContextFeatureDecl::new(segment.ident.clone())); } // InjectIndex stays undeclared: a record node's injection // re-addresses lanes derived from the incoming index, so it @@ -1605,7 +1651,7 @@ mod tests { pat_ident: pat_ident("_"), ty: parse_quote!(impl Ctx + ExtractFootprint), implementations: Punctuated::new(), - context_features: vec![format_ident!("ExtractFootprint")], + context_features: vec![ContextFeatureDecl::new(format_ident!("ExtractFootprint"))], }, output_type: parse_quote!(Vector), output_depth: 0, diff --git a/node-graph/nodes/gcore/src/context.rs b/node-graph/nodes/gcore/src/context.rs index b99bf17b16..0357069b37 100644 --- a/node-graph/nodes/gcore/src/context.rs +++ b/node-graph/nodes/gcore/src/context.rs @@ -1,7 +1,7 @@ use core_types::gpoll::{Extent, GPoll, GraphError, Interrupt}; use core_types::list::List; use core_types::{Color, ExtractVarArgs}; -use core_types::{Ctx, ExtractIndex, ExtractPosition}; +use core_types::{Ctx, ExtractIndex, ExtractIndices, ExtractPosition}; use glam::DVec2; use graphic_types::vector_types::GradientStops; use graphic_types::{Graphic, Vector}; @@ -64,7 +64,7 @@ fn vararg_lanes(ctx: &impl ExtractVarArgs, level: u8) -> GPoll(ctx: &(impl ExtractVarArgs + ExtractIndex)) -> Result { vararg_list::(ctx) - .and_then(|list| list.element(ctx.innermost_index() as usize)) + .and_then(|list| list.element(ctx.index() as usize)) .cloned() .ok_or_else(|| GraphError::new("vararg row addressed past its items").into()) } @@ -138,7 +138,9 @@ fn read_position( /// Nested loops can enable 2D or higher-dimensional iteration by using the *Loop Level* parameter to read the index from outer levels of loops. #[node_macro::node(category("Context"), path(core_types::vector))] fn read_index( - ctx: impl Ctx + ExtractIndex, + // `loop_level` is a runtime input, so no level is statically known and the + // whole chain has to survive nullification. + ctx: impl Ctx + ExtractIndices, _primary: (), /// The number of nested loops to traverse outwards (from the innermost loop) to get the index from. The most upstream loop is level 0, and downstream loops add levels. /// diff --git a/node-graph/nodes/gcore/src/context_modification.rs b/node-graph/nodes/gcore/src/context_modification.rs index ef62f277e2..de501bfe22 100644 --- a/node-graph/nodes/gcore/src/context_modification.rs +++ b/node-graph/nodes/gcore/src/context_modification.rs @@ -1,5 +1,5 @@ -use core_types::context::{ContextModification, Ctx, DeriveCtx}; -use core_types::gpoll::Interrupt; +use core_types::context::{ContextFeatures, ContextModification, Ctx, DeriveCtx, IndexLink, nullify_index_levels}; +use core_types::gpoll::{ErrorKind, GraphError, Interrupt}; /// Filters out what should be unused components of the context based on the specified requirements. /// This node is inserted by the compiler to "zero out" unused context components. @@ -12,5 +12,15 @@ fn context_modification( modification: ContextModification, ) -> Result { let scope = ctx.scope().nullified(modification.features, Some(modification.sources())); - value.eval(&ctx.nullified(modification.features, &scope)) + let exhausted = || { + Interrupt::from(GraphError { + kind: ErrorKind::ArenaExhausted, + trace: Vec::new(), + }) + }; + let index = match modification.features.contains(ContextFeatures::INDEX) { + true => nullify_index_levels(ctx.index_head(), modification.index_levels, scope.arena()).ok_or_else(exhausted)?, + false => IndexLink { index: 0, outer: None }, + }; + value.eval(&ctx.nullified(modification.features, index, &scope)) } diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index 3b1a57b553..29a5ff5af0 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -41,7 +41,7 @@ fn memoize<'e>( // keys with the lane normalized away. let leveled = content.layout().depth > 0; let lane = match leveled { - true => ctx.innermost_index() as usize, + true => ctx.index() as usize, false => 0, }; let key = match leveled { @@ -203,7 +203,7 @@ fn monitor<'e>( // computes lane by lane. Serving THIS lane out of that batch rather than // evaluating the content separately is what keeps the cost linear: the extra // eval would double the work under every enclosing monitor. - if content.layout().depth > 0 && ctx.innermost_index() == 0 { + if content.layout().depth > 0 && ctx.index() == 0 { return match content.materialize_level(ctx, ctx.arena()) { LevelStatus::Batch(batch, finality) => { // SAFETY: the batch came from this edge, so it carries the edge's layout. @@ -235,7 +235,7 @@ fn monitor<'e>( }; } let result = content.eval(&ctx); - if ctx.innermost_index() == 0 + if ctx.index() == 0 && let GPoll::Final(value) | GPoll::Partial(value) = &result { // SAFETY: the value came from this edge, so it carries the edge's layout. diff --git a/node-graph/nodes/gcore/src/record.rs b/node-graph/nodes/gcore/src/record.rs index d838b10292..af9cbcfa2f 100644 --- a/node-graph/nodes/gcore/src/record.rs +++ b/node-graph/nodes/gcore/src/record.rs @@ -7,7 +7,7 @@ use core_types::attribute::{Attr, EditorLayerPath, Opacity, RemoveAttr, Transform}; use glam::DAffine2; -use core_types::context::{DeriveCtx, ExtractIndex, IndexLink, InjectIndex}; +use core_types::context::{DeriveCtx, ExtractIndex, ExtractIndices, IndexLink, InjectIndex}; use core_types::extent::{ExtentIn, LevelIn, ListIn, ValueIn}; use core_types::gpoll::{ErrorKind, Extent, GPoll, GraphError, Interrupt, Level}; use core_types::node::Lane; @@ -58,8 +58,8 @@ fn fade(_: impl Ctx, (element, opacity): (T, Attr), factor: f64) -> /// writes a per-copy opacity indexed by the copy's own index. #[node_macro::node(category("Test"), extent(repeat_opacity_extent))] fn repeat_opacity(ctx: impl Ctx + ExtractIndex, element: f64, count: u32) -> IList<(f64, Attr)> { - debug_assert!(ctx.innermost_index() < count as u64, "repeat addressed past its copy count"); - emit(element, Attr(ctx.innermost_index() as f64)) + debug_assert!(ctx.index() < count as u64, "repeat addressed past its copy count"); + emit(element, Attr(ctx.index() as f64)) } #[node_macro::node(category("Test"))] @@ -155,7 +155,7 @@ fn extend( GPoll::Pending => return Err(Interrupt::Pending), _ => return Err(GraphError::new("extend over a non-exact base extent").into()), }; - let lane = ctx.innermost_index(); + let lane = ctx.index(); match lane < split { true => base.eval(ctx), false => { @@ -210,7 +210,7 @@ fn omit_element(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: i GPoll::Pending => return Err(Interrupt::Pending), _ => return Err(GraphError::new("omit over a non-exact extent").into()), }; - let lane = ctx.innermost_index(); + let lane = ctx.index(); let source = match resolve_index(index, total) { Some(omitted) if lane >= omitted => lane + 1, _ => lane, @@ -270,7 +270,7 @@ fn extract_element(_: impl Ctx + InjectIndex + Copy, list: IList, index: f6 #[node_macro::node(category("Test"), extent(mirror_extent))] fn mirror(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList, keep_original: bool) -> Result)>, Interrupt> { let total = content.len() as u64; - let lane = ctx.innermost_index(); + let lane = ctx.index(); let (source, mirrored) = match (keep_original, lane < total) { (true, true) => (lane, false), (true, false) => (lane - total, true), diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index a5af6102b4..13f73962fb 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -71,7 +71,7 @@ pub fn omit_element( GPoll::Pending => return Err(Interrupt::Pending), _ => return Err(GraphError::new("omit over a non-exact extent").into()), }; - let lane = ctx.innermost_index(); + let lane = ctx.index(); let source = match resolve_index(index, total) { Some(omitted) if lane >= omitted => lane + 1, _ => lane, @@ -115,7 +115,7 @@ fn map( #[implementations(Graphic, Vector, Raster, Color, GradientStops, String)] content: IList, mapped: impl Node, Output = IList>, ) -> Result, Interrupt> { - let mut remaining = ctx.innermost_index(); + let mut remaining = ctx.index(); for row in 0..content.len() { let item = crate::record::vararg_row(content, row); let scoped = ctx.push_vararg(&item); @@ -276,7 +276,7 @@ fn mirror<'e>( mirror_lane( ctx.arena(), legacy_render_list_of(content), - ctx.innermost_index() as usize, + ctx.index() as usize, relative_to_bounds, offset, angle, @@ -334,7 +334,7 @@ fn mirror_vector<'e>( mirror_lane( ctx.arena(), legacy_render_list_of(content), - ctx.innermost_index() as usize, + ctx.index() as usize, relative_to_bounds, offset, angle, @@ -403,7 +403,7 @@ pub fn extend( GPoll::Pending => return Err(Interrupt::Pending), _ => return Err(GraphError::new("extend over a non-exact base extent").into()), }; - let lane = ctx.innermost_index(); + let lane = ctx.index(); match lane < split { true => base.eval(ctx), false => { @@ -586,7 +586,7 @@ pub use _to_graphic_unit_mod::to_graphic_unit_entries; /// Removes a level of nesting from a `Graphic[]`, or all nesting if "Fully Flatten" is enabled. #[node_macro::node(category("General"), extent(flatten_graphic_extent))] pub fn flatten_graphic(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList, fully_flatten: bool) -> Result)>, Interrupt> { - let mut remaining = ctx.innermost_index() as usize; + let mut remaining = ctx.index() as usize; for row in 0..content.len() { let graphic = content.element_ref(row); let count = crate::record::leaf_count(graphic, fully_flatten, 0); diff --git a/node-graph/nodes/graphic/src/record.rs b/node-graph/nodes/graphic/src/record.rs index 0b70291dba..9f10f7f8e9 100644 --- a/node-graph/nodes/graphic/src/record.rs +++ b/node-graph/nodes/graphic/src/record.rs @@ -85,7 +85,7 @@ pub(crate) fn locate(graphic: &Graphic, transform: DAffine2, fully_flatten: bool /// rides as a leaf with its embedded transforms untouched. #[node_macro::node(category("Test"), extent(flatten_extent))] fn flatten(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, content: IList, fully_flatten: bool) -> Result)>, Interrupt> { - let mut remaining = ctx.innermost_index() as usize; + let mut remaining = ctx.index() as usize; for row in 0..content.len() { let graphic = content.element_ref(row); let count = leaf_count(graphic, fully_flatten, 0); @@ -157,7 +157,7 @@ fn map( #[implementations(Graphic, Vector, Raster, Color, GradientStops, String)] content: IList, mapped: impl Node, Output = IList>, ) -> Result>, Interrupt> { - let mut remaining = ctx.innermost_index(); + let mut remaining = ctx.index(); for row in 0..content.len() { let item = vararg_row(content, row); let scoped = ctx.push_vararg(&item); @@ -181,7 +181,7 @@ fn flat_map( #[implementations(Graphic, Vector, Raster, Color, GradientStops, String)] content: IList, mapped: impl Node, Output = IList>, ) -> Result, Interrupt> { - let mut remaining = ctx.innermost_index(); + let mut remaining = ctx.index(); for row in 0..content.len() { let item = vararg_row(content, row); let scoped = ctx.push_vararg(&item); @@ -201,7 +201,7 @@ fn flat_map( #[node_macro::node(category("Test"), extent(flatten_levels_extent))] fn flatten_levels(ctx: impl Ctx + DeriveCtx + ExtractIndex, content: impl Node, Output = IList>>) -> Result, Interrupt> { let head = ctx.index_head(); - content.eval(&ctx.promoted(&head, ctx.innermost_index())) + content.eval(&ctx.promoted(&head, ctx.index())) } /// The collapsed level's extent is the sum of the inner extents across the @@ -237,7 +237,7 @@ mod tests { use core_types::SourceId; use core_types::arena::Arena; use core_types::attribute::Attribute as AttributeMarker; - use core_types::context::{ContextImpl, EvalScope, ExtractArena}; + use core_types::context::{ContextImpl, EvalScope, ExtractArena, ExtractIndices}; use core_types::list::{Item, List}; use core_types::node::Node; use core_types::record::{self, Layout, Rec, RecordSource, RecordValue, stack}; diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 769192dc39..c669b0bc3f 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -811,7 +811,7 @@ fn hsla_to_color(_: impl Ctx, _primary: (), hue: Fraction, #[default(1.)] satura #[node_macro::node(category("Color"), name("Hex to Color"))] fn hex_to_color(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, hex_code: String) -> Result, Interrupt> { // An invalid input serves an empty level: no color - match (core_types::misc::parse_css_color(&hex_code), ctx.innermost_index()) { + match (core_types::misc::parse_css_color(&hex_code), ctx.index()) { (Some(color), 0) => Ok(color), _ => Err(GraphError::past_end().into()), } @@ -839,7 +839,7 @@ fn spread_method(_: impl Ctx, gradient: GradientStops, spread_method: vector_typ #[node_macro::node(category("Color"))] fn sample_gradient(ctx: impl Ctx + ExtractIndex + InjectIndex + Copy, _primary: (), gradient: IList, position: Fraction) -> Result, Interrupt> { // An unwired gradient serves an empty level: no color - if gradient.is_empty() || ctx.innermost_index() != 0 { + if gradient.is_empty() || ctx.index() != 0 { return Err(GraphError::past_end().into()); } @@ -1035,7 +1035,7 @@ mod graphene_test { type Output = f64; fn eval(&self, input: &Input) -> GPoll { - GPoll::Final(input.innermost_index() as f64) + GPoll::Final(input.index() as f64) } } diff --git a/node-graph/nodes/raster/src/image_color_palette.rs b/node-graph/nodes/raster/src/image_color_palette.rs index 3726f9a57c..b00c5dac68 100644 --- a/node-graph/nodes/raster/src/image_color_palette.rs +++ b/node-graph/nodes/raster/src/image_color_palette.rs @@ -59,7 +59,7 @@ fn image_color_palette( }) .collect(); - palette.get(ctx.innermost_index() as usize).copied().ok_or_else(|| GraphError::past_end().into()) + palette.get(ctx.index() as usize).copied().ok_or_else(|| GraphError::past_end().into()) } #[cfg(test)] diff --git a/node-graph/nodes/raster/src/std_nodes.rs b/node-graph/nodes/raster/src/std_nodes.rs index 6dca4d7f86..c7c17f1490 100644 --- a/node-graph/nodes/raster/src/std_nodes.rs +++ b/node-graph/nodes/raster/src/std_nodes.rs @@ -107,7 +107,7 @@ pub fn combine_channels<'e>( )>, Interrupt, > { - let lane = ctx.innermost_index() as usize; + let lane = ctx.index() as usize; let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len()); if lane >= max_len { return Err(GraphError::past_end().into()); diff --git a/node-graph/nodes/repeat/src/repeat_nodes.rs b/node-graph/nodes/repeat/src/repeat_nodes.rs index 359aa53b04..69154be966 100644 --- a/node-graph/nodes/repeat/src/repeat_nodes.rs +++ b/node-graph/nodes/repeat/src/repeat_nodes.rs @@ -231,7 +231,7 @@ mod test { type Output = RecordValue<'e>; fn eval(&self, input: &ContextImpl<'e>) -> GPoll> { - use core_types::context::{ExtractArena, ExtractIndex}; + use core_types::context::{ExtractArena, ExtractIndices}; let (vector, transform) = &self.rows[input.innermost_index() as usize % self.rows.len()]; let dst = stack::push(self.layout.frame_bytes()); // SAFETY: dst is the claimed frame of this layout; offsets are the layout's own. diff --git a/node-graph/nodes/vector/src/vector_modification_nodes.rs b/node-graph/nodes/vector/src/vector_modification_nodes.rs index 968bc87e72..250e225586 100644 --- a/node-graph/nodes/vector/src/vector_modification_nodes.rs +++ b/node-graph/nodes/vector/src/vector_modification_nodes.rs @@ -18,7 +18,7 @@ fn path_modify<'e>( node_path: Vec, ) -> Result<(Vector, Attr<'e, EditorLayerPath>, RemoveAttr), Interrupt> { let mut element = element; - if ctx.innermost_index() == 0 { + if ctx.index() == 0 { modification.apply(&mut element); } diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 57ae33fad5..a8c7925368 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -101,7 +101,7 @@ fn assign_colors<'e>( #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32, ) -> Result, Attr<'e, Fill>, Attr<'e, StrokeAttr>, Attr<'e, EditorLayerPath>)>, Interrupt> { - let lane = ctx.innermost_index() as usize; + let lane = ctx.index() as usize; if lane >= content.len() { return Err(GraphError::past_end().into()); } @@ -173,7 +173,7 @@ fn assign_colors_graphic<'e>( seed: SeedValue, repeat_every: u32, ) -> Result, Attr<'e, EditorLayerPath>)>, Interrupt> { - let lane = ctx.innermost_index() as usize; + let lane = ctx.index() as usize; if lane >= content.len() { return Err(GraphError::past_end().into()); } @@ -1549,7 +1549,7 @@ fn solidify_stroke<'e>( )>, Interrupt, > { - solidify_lane(ctx.arena(), legacy_graphic_list_of(content), ctx.innermost_index() as usize) + solidify_lane(ctx.arena(), legacy_graphic_list_of(content), ctx.index() as usize) } /// A fill-bearing row splits into a fill lane and a solidified stroke lane, @@ -1588,7 +1588,7 @@ fn solidify_stroke_vector<'e>( )>, Interrupt, > { - solidify_lane(ctx.arena(), legacy_graphic_list_of(content), ctx.innermost_index() as usize) + solidify_lane(ctx.arena(), legacy_graphic_list_of(content), ctx.index() as usize) } fn solidify_stroke_vector_extent(content: ListIn<'_, Vector>, level: LevelIn) -> GPoll { @@ -1655,7 +1655,7 @@ fn separate_subpaths<'e>( Interrupt, > { let output = separate_subpaths_core(legacy_vector_list_of(content)); - emit_legacy_lane(ctx.arena(), output, ctx.innermost_index() as usize) + emit_legacy_lane(ctx.arena(), output, ctx.index() as usize) } /// A row splits into one lane per subpath, so the count depends on the @@ -1726,7 +1726,7 @@ fn map_points<'e>( } } - emit_legacy_lane(ctx.arena(), content, ctx.innermost_index() as usize) + emit_legacy_lane(ctx.arena(), content, ctx.index() as usize) } fn map_points_extent(content: ListIn<'_, Vector>, _mapped: ExtentIn<'_>, level: LevelIn) -> GPoll { @@ -2176,7 +2176,7 @@ fn cut_path<'e>( Interrupt, > { let output = cut_path_core(legacy_vector_list_of(content), progression, reverse, parameterized_distance); - emit_legacy_lane(ctx.arena(), output, ctx.innermost_index() as usize) + emit_legacy_lane(ctx.arena(), output, ctx.index() as usize) } fn cut_path_extent(content: ListIn<'_, Vector>, _progression: ValueIn<'_, f64>, _reverse: ValueIn<'_, bool>, _parameterized_distance: ValueIn<'_, bool>, level: LevelIn) -> GPoll {