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

View File

@@ -11,6 +11,12 @@ pub struct Arena {
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 {
offset: usize,
drop_fn: unsafe fn(*mut u8),

View File

@@ -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<f64>,
animation_time: Option<f64>,
@@ -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>>,

View File

@@ -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<FrameTable<T, 32>>,
content: impl Node<Context<'_>, Output = T>,
) -> GPoll<&'e T> {
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> {
let arena = ctx.arena();
let table = match cell.load(arena) {
Some(table) => table,
@@ -130,10 +126,10 @@ fn serialize_monitor<T: Clone + 'static + Send + Sync>(io: &MonitorValue<T>) ->
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);

View File

@@ -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<T: Send + Into<O>, O: Send>(_: impl Ctx, value: T, #[data] _out_ty: Phan
}
#[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)
}
#[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)
}

View File

@@ -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<T: IntoGraphicList>(
ctx: impl Ctx + ExtractFootprint + DeriveCtx,
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
/// Graphics to include within the artboard.
#[implementations(
Context -> List<Graphic>,

View File

@@ -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};

View File

@@ -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<T: ApplyTransform + 'static>(
ctx: impl Ctx + ExtractFootprint + DeriveCtx + ModifyFootprint,
ctx: impl Ctx + DeriveCtx + ModifyFootprint,
#[implementations(
Context -> DAffine2,
Context -> DVec2,

View File

@@ -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: