From e2a8420fee629866d139be9c706f4b0568db7015 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jul 2026 19:09:49 +0200 Subject: [PATCH] Fix ExtractFootprint annotations causing cache invalidation --- node-graph/libraries/core-types/src/arena.rs | 6 ++++++ node-graph/libraries/core-types/src/context.rs | 16 +++++++++------- node-graph/nodes/gcore/src/memo.rs | 8 ++------ node-graph/nodes/gcore/src/ops.rs | 5 +++-- node-graph/nodes/graphic/src/artboard.rs | 4 ++-- node-graph/nodes/graphic/src/graphic.rs | 2 +- .../nodes/transform/src/transform_nodes.rs | 6 +++--- node-graph/rfcs/fine-grained-context-caching.md | 6 ++++++ 8 files changed, 32 insertions(+), 21 deletions(-) diff --git a/node-graph/libraries/core-types/src/arena.rs b/node-graph/libraries/core-types/src/arena.rs index a303a62c4a..ec23bbb6eb 100644 --- a/node-graph/libraries/core-types/src/arena.rs +++ b/node-graph/libraries/core-types/src/arena.rs @@ -11,6 +11,12 @@ pub struct Arena { drops: Mutex>, } +impl std::fmt::Debug for Arena { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Arena").field("generation", &self.generation).field("size", &self.buf.len()).finish() + } +} + struct DropEntry { offset: usize, drop_fn: unsafe fn(*mut u8), diff --git a/node-graph/libraries/core-types/src/context.rs b/node-graph/libraries/core-types/src/context.rs index b5824fd5ae..837b3e9e02 100644 --- a/node-graph/libraries/core-types/src/context.rs +++ b/node-graph/libraries/core-types/src/context.rs @@ -288,11 +288,7 @@ impl From<&[ContextFeature]> for ContextDependencies { _ => ContextFeatures::empty(), }; } - Self { - extract, - inject, - sources: Vec::new(), - } + Self { extract, inject, sources: Vec::new() } } } @@ -792,7 +788,13 @@ pub struct VarArgLink<'a> { pub outer: Option<&'a VarArgLink<'a>>, } -#[derive(Clone, Copy)] +impl<'a> std::fmt::Debug for VarArgLink<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VarArgLink").field("args_len", &self.args.len()).field("outer", &self.outer).finish() + } +} + +#[derive(Clone, Copy, Debug)] pub struct EvalScope<'a> { real_time: Option, animation_time: Option, @@ -1084,7 +1086,7 @@ impl CtxFamily for ContextImplFamily { type Ctx<'s> = ContextImpl<'s>; } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct ContextImpl<'a> { index: IndexLink<'a>, position: Option<&'a PositionLink<'a>>, diff --git a/node-graph/nodes/gcore/src/memo.rs b/node-graph/nodes/gcore/src/memo.rs index 9095f30463..eaa0cfebe3 100644 --- a/node-graph/nodes/gcore/src/memo.rs +++ b/node-graph/nodes/gcore/src/memo.rs @@ -41,11 +41,7 @@ where } #[node_macro::node(category(""), path(graphene_core::memo), skip_impl, extent(frame_memo_extent))] -fn frame_memo<'e, T: Clone + 'static>( - ctx: impl Ctx + CacheHash + ExtractArena<'e>, - #[data] cell: ArenaCell>, - content: impl Node, Output = T>, -) -> GPoll<&'e T> { +fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<'e>, #[data] cell: ArenaCell>, content: impl Node, Output = T>) -> GPoll<&'e T> { let arena = ctx.arena(); let table = match cell.load(arena) { Some(table) => table, @@ -130,10 +126,10 @@ fn serialize_monitor(io: &MonitorValue) -> mod tests { use super::*; use core_types::SourceId; + use core_types::Type; use core_types::concrete; use core_types::context::{ContextImpl, EvalScope}; use core_types::registry::{EdgeHandle, ErasedGNode, ErasedLendGNode}; - use core_types::Type; use std::sync::atomic::{AtomicU32, Ordering}; struct CountingNode(AtomicU32); diff --git a/node-graph/nodes/gcore/src/ops.rs b/node-graph/nodes/gcore/src/ops.rs index 0e8d1ba883..4c48ae146a 100644 --- a/node-graph/nodes/gcore/src/ops.rs +++ b/node-graph/nodes/gcore/src/ops.rs @@ -1,3 +1,4 @@ +use core_types::ExtractAll; use core_types::runtime::SourceFuture; use core_types::{Ctx, ExtractFootprint, ops::Convert, ops::ConvertAsync, transform::Footprint}; use std::marker::PhantomData; @@ -17,12 +18,12 @@ fn into, O: Send>(_: impl Ctx, value: T, #[data] _out_ty: Phan } #[node_macro::node(category(""), skip_impl)] -fn convert, O: Send, C: Send>(ctx: impl Ctx + ExtractFootprint, value: T, converter: C, #[data] _out_ty: PhantomData) -> O { +fn convert, O: Send, C: Send>(ctx: impl Ctx + ExtractAll, value: T, converter: C, #[data] _out_ty: PhantomData) -> O { value.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter) } #[node_macro::node(category(""), skip_impl)] -fn convert_async, O: Send + 'static, C: Send>(ctx: impl Ctx + ExtractFootprint, value: T, converter: C, #[data] _out_ty: PhantomData) -> SourceFuture { +fn convert_async, O: Send + 'static, C: Send>(ctx: impl Ctx + ExtractAll, value: T, converter: C, #[data] _out_ty: PhantomData) -> SourceFuture { value.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter) } diff --git a/node-graph/nodes/graphic/src/artboard.rs b/node-graph/nodes/graphic/src/artboard.rs index d4a9a28afc..b93b4afc81 100644 --- a/node-graph/nodes/graphic/src/artboard.rs +++ b/node-graph/nodes/graphic/src/artboard.rs @@ -1,7 +1,7 @@ use core_types::gpoll::Interrupt; use core_types::list::{Item, List}; use core_types::transform::TransformMut; -use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color, Context, Ctx, DeriveCtx, ExtractFootprint}; +use core_types::{ATTR_BACKGROUND, ATTR_CLIP, ATTR_DIMENSIONS, ATTR_LOCATION, Color, Context, Ctx, DeriveCtx, ModifyFootprint}; use glam::{DAffine2, DVec2}; use graphic_types::graphic::{Graphic, IntoGraphicList}; use graphic_types::{Artboard, Vector}; @@ -11,7 +11,7 @@ use vector_types::GradientStops; /// Constructs a single-element `Artboard[]` with the given content and metadata stored as row attributes. #[node_macro::node(category(""))] pub fn create_artboard( - ctx: impl Ctx + ExtractFootprint + DeriveCtx, + ctx: impl Ctx + DeriveCtx + ModifyFootprint, /// Graphics to include within the artboard. #[implementations( Context -> List, diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 9625f38dae..67ae51f400 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -1,8 +1,8 @@ use core_types::bounds::{BoundingBox, RenderBoundingBox}; +use core_types::gpoll::Interrupt; use core_types::list::{AttributeDyn, AttributeValueDyn, Item, List, ListDyn}; use core_types::registry::types::{Angle, SignedInteger}; use core_types::uuid::NodeId; -use core_types::gpoll::Interrupt; use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, Color, Context, Ctx, DeriveCtx}; use glam::{DAffine2, DVec2}; use graphic_types::graphic::{Graphic, IntoGraphicList}; diff --git a/node-graph/nodes/transform/src/transform_nodes.rs b/node-graph/nodes/transform/src/transform_nodes.rs index 7439f7bfa7..40930110fd 100644 --- a/node-graph/nodes/transform/src/transform_nodes.rs +++ b/node-graph/nodes/transform/src/transform_nodes.rs @@ -1,9 +1,9 @@ use core::f64; use core_types::color::Color; +use core_types::gpoll::Interrupt; use core_types::list::{List, ListDyn}; use core_types::transform::{ApplyTransform, ScaleType, Transform}; -use core_types::gpoll::Interrupt; -use core_types::{ATTR_TRANSFORM, Context, Ctx, DeriveCtx, ExtractFootprint, InjectFootprint, ModifyFootprint}; +use core_types::{ATTR_TRANSFORM, Context, Ctx, DeriveCtx, InjectFootprint, ModifyFootprint}; use glam::{DAffine2, DMat2, DVec2}; use graphic_types::Graphic; use graphic_types::Vector; @@ -13,7 +13,7 @@ use vector_types::GradientStops; /// Applies the specified transform to the input value, which may be a graphic type or another transform. #[node_macro::node(category("Math: Transform"))] fn transform( - ctx: impl Ctx + ExtractFootprint + DeriveCtx + ModifyFootprint, + ctx: impl Ctx + DeriveCtx + ModifyFootprint, #[implementations( Context -> DAffine2, Context -> DVec2, diff --git a/node-graph/rfcs/fine-grained-context-caching.md b/node-graph/rfcs/fine-grained-context-caching.md index 13a79f571b..c28b3e5c57 100644 --- a/node-graph/rfcs/fine-grained-context-caching.md +++ b/node-graph/rfcs/fine-grained-context-caching.md @@ -126,6 +126,12 @@ pub trait ModifyIndex: ExtractIndex + InjectIndex {} pub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {} ``` +### Authoring rule: forward with Modify*, consume with Extract* + +Declare a feature via `Modify*` when the node only reads it to compute a new value it injects for its children (a "forward"), and via `Extract*` only when the node genuinely consumes the value for its own output (a "sink"). + +The analysis skips `Modify*` bounds but treats every `Extract*` bound as an *unconditional* requirement. Because `Modify*` is a supertrait of `Extract*`, a `Modify*` bound already grants the read capability (e.g. `modify_footprint`, which is `where Self: ExtractFootprint`), so it is a mistake to list both. Writing `impl Ctx + ExtractFootprint + ModifyFootprint` on a forwarding node re-introduces the feature as a hard dependency at every such node, which propagates up the whole tree and pins upstream memos to a value the node never actually consumes (e.g. a viewport pan invalidating a render cache that renders in local space). Use `impl Ctx + ModifyFootprint` alone. + ### Conditional Context Dependencies Modify* traits represent a special case in context analysis: