Fix ExtractFootprint annotations causing cache invalidation

This commit is contained in:
Dennis Kobert
2026-07-30 19:09:49 +02:00
parent f4247986f0
commit e2a8420fee
8 changed files with 32 additions and 21 deletions
@@ -11,6 +11,12 @@ pub struct Arena {
drops: Mutex<Vec<DropEntry>>, drops: Mutex<Vec<DropEntry>>,
} }
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 { struct DropEntry {
offset: usize, offset: usize,
drop_fn: unsafe fn(*mut u8), drop_fn: unsafe fn(*mut u8),
@@ -288,11 +288,7 @@ impl From<&[ContextFeature]> for ContextDependencies {
_ => ContextFeatures::empty(), _ => ContextFeatures::empty(),
}; };
} }
Self { Self { extract, inject, sources: Vec::new() }
extract,
inject,
sources: Vec::new(),
}
} }
} }
@@ -792,7 +788,13 @@ pub struct VarArgLink<'a> {
pub outer: Option<&'a 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> { pub struct EvalScope<'a> {
real_time: Option<f64>, real_time: Option<f64>,
animation_time: Option<f64>, animation_time: Option<f64>,
@@ -1084,7 +1086,7 @@ impl CtxFamily for ContextImplFamily {
type Ctx<'s> = ContextImpl<'s>; type Ctx<'s> = ContextImpl<'s>;
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
pub struct ContextImpl<'a> { pub struct ContextImpl<'a> {
index: IndexLink<'a>, index: IndexLink<'a>,
position: Option<&'a PositionLink<'a>>, position: Option<&'a PositionLink<'a>>,
+2 -6
View File
@@ -41,11 +41,7 @@ where
} }
#[node_macro::node(category(""), path(graphene_core::memo), skip_impl, extent(frame_memo_extent))] #[node_macro::node(category(""), path(graphene_core::memo), skip_impl, extent(frame_memo_extent))]
fn frame_memo<'e, T: Clone + 'static>( fn frame_memo<'e, T: Clone + 'static>(ctx: impl Ctx + CacheHash + ExtractArena<'e>, #[data] cell: ArenaCell<FrameTable<T, 32>>, content: impl Node<Context<'_>, Output = T>) -> GPoll<&'e T> {
ctx: impl Ctx + CacheHash + ExtractArena<'e>,
#[data] cell: ArenaCell<FrameTable<T, 32>>,
content: impl Node<Context<'_>, Output = T>,
) -> GPoll<&'e T> {
let arena = ctx.arena(); let arena = ctx.arena();
let table = match cell.load(arena) { let table = match cell.load(arena) {
Some(table) => table, Some(table) => table,
@@ -130,10 +126,10 @@ fn serialize_monitor<T: Clone + 'static + Send + Sync>(io: &MonitorValue<T>) ->
mod tests { mod tests {
use super::*; use super::*;
use core_types::SourceId; use core_types::SourceId;
use core_types::Type;
use core_types::concrete; use core_types::concrete;
use core_types::context::{ContextImpl, EvalScope}; use core_types::context::{ContextImpl, EvalScope};
use core_types::registry::{EdgeHandle, ErasedGNode, ErasedLendGNode}; use core_types::registry::{EdgeHandle, ErasedGNode, ErasedLendGNode};
use core_types::Type;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
struct CountingNode(AtomicU32); struct CountingNode(AtomicU32);
+3 -2
View File
@@ -1,3 +1,4 @@
use core_types::ExtractAll;
use core_types::runtime::SourceFuture; use core_types::runtime::SourceFuture;
use core_types::{Ctx, ExtractFootprint, ops::Convert, ops::ConvertAsync, transform::Footprint}; use core_types::{Ctx, ExtractFootprint, ops::Convert, ops::ConvertAsync, transform::Footprint};
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -17,12 +18,12 @@ fn into<T: Send + Into<O>, O: Send>(_: impl Ctx, value: T, #[data] _out_ty: Phan
} }
#[node_macro::node(category(""), skip_impl)] #[node_macro::node(category(""), skip_impl)]
fn convert<T: Send + Convert<O, C>, O: Send, C: Send>(ctx: impl Ctx + ExtractFootprint, value: T, converter: C, #[data] _out_ty: PhantomData<O>) -> O { fn convert<T: Send + Convert<O, C>, O: Send, C: Send>(ctx: impl Ctx + ExtractAll, value: T, converter: C, #[data] _out_ty: PhantomData<O>) -> O {
value.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter) value.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter)
} }
#[node_macro::node(category(""), skip_impl)] #[node_macro::node(category(""), skip_impl)]
fn convert_async<T: Send + ConvertAsync<O, C>, O: Send + 'static, C: Send>(ctx: impl Ctx + ExtractFootprint, value: T, converter: C, #[data] _out_ty: PhantomData<O>) -> SourceFuture<O> { fn convert_async<T: Send + ConvertAsync<O, C>, O: Send + 'static, C: Send>(ctx: impl Ctx + ExtractAll, value: T, converter: C, #[data] _out_ty: PhantomData<O>) -> SourceFuture<O> {
value.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter) value.convert(*ctx.try_footprint().unwrap_or(&Footprint::DEFAULT), converter)
} }
+2 -2
View File
@@ -1,7 +1,7 @@
use core_types::gpoll::Interrupt; use core_types::gpoll::Interrupt;
use core_types::list::{Item, List}; use core_types::list::{Item, List};
use core_types::transform::TransformMut; 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 glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicList}; use graphic_types::graphic::{Graphic, IntoGraphicList};
use graphic_types::{Artboard, Vector}; 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. /// Constructs a single-element `Artboard[]` with the given content and metadata stored as row attributes.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub fn create_artboard<T: IntoGraphicList>( pub fn create_artboard<T: IntoGraphicList>(
ctx: impl Ctx + ExtractFootprint + DeriveCtx, ctx: impl Ctx + DeriveCtx + ModifyFootprint,
/// Graphics to include within the artboard. /// Graphics to include within the artboard.
#[implementations( #[implementations(
Context -> List<Graphic>, Context -> List<Graphic>,
+1 -1
View File
@@ -1,8 +1,8 @@
use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::bounds::{BoundingBox, RenderBoundingBox};
use core_types::gpoll::Interrupt;
use core_types::list::{AttributeDyn, AttributeValueDyn, Item, List, ListDyn}; use core_types::list::{AttributeDyn, AttributeValueDyn, Item, List, ListDyn};
use core_types::registry::types::{Angle, SignedInteger}; use core_types::registry::types::{Angle, SignedInteger};
use core_types::uuid::NodeId; 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 core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AnyHash, BlendMode, CacheHash, Color, Context, Ctx, DeriveCtx};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicList}; use graphic_types::graphic::{Graphic, IntoGraphicList};
@@ -1,9 +1,9 @@
use core::f64; use core::f64;
use core_types::color::Color; use core_types::color::Color;
use core_types::gpoll::Interrupt;
use core_types::list::{List, ListDyn}; use core_types::list::{List, ListDyn};
use core_types::transform::{ApplyTransform, ScaleType, Transform}; use core_types::transform::{ApplyTransform, ScaleType, Transform};
use core_types::gpoll::Interrupt; use core_types::{ATTR_TRANSFORM, Context, Ctx, DeriveCtx, InjectFootprint, ModifyFootprint};
use core_types::{ATTR_TRANSFORM, Context, Ctx, DeriveCtx, ExtractFootprint, InjectFootprint, ModifyFootprint};
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
use graphic_types::Graphic; use graphic_types::Graphic;
use graphic_types::Vector; 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. /// Applies the specified transform to the input value, which may be a graphic type or another transform.
#[node_macro::node(category("Math: Transform"))] #[node_macro::node(category("Math: Transform"))]
fn transform<T: ApplyTransform + 'static>( fn transform<T: ApplyTransform + 'static>(
ctx: impl Ctx + ExtractFootprint + DeriveCtx + ModifyFootprint, ctx: impl Ctx + DeriveCtx + ModifyFootprint,
#[implementations( #[implementations(
Context -> DAffine2, Context -> DAffine2,
Context -> DVec2, Context -> DVec2,
@@ -126,6 +126,12 @@ pub trait ModifyIndex: ExtractIndex + InjectIndex {}
pub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {} 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 ### Conditional Context Dependencies
Modify* traits represent a special case in context analysis: Modify* traits represent a special case in context analysis: