Graphene: Fine-grained context caching (#2500)

* RFC: Fine Grained Context Caching

* Fix typos

* Fix label

* Add description of inject traits

* Explicitly support context modification

* Start implementation of context invalidation

* Add inject trait variants
* Route Extract / Inject traits to the proto nodes

* Implement context dependency analysis

* Implement context modification node insertion

* Fix erronous force graph run message

* Fix Extract* Inject* annotations in the nodes

* Require Hash implementation for VarArgs

* Fix nullification node insertion

* Cross of done items unresolved questions section

* Update Cargo.lock

* Fix context features propagation

* Update demo artwork

* Remove BondlessFootprint and FreezeRealTime nodes

* Fix migration

* Add migrations for adding context features to old networks

* Always update real time regardless of animation state

* Cargo fmt

* Fix tests

* Readd sed command to hopefully fix profile result parsing

* Add debug output to profiling pr

* Use new totals instead of summaries for for iai results

* Even more debugging

* Use correct debug metrics (hopefully)

* Add more MemoNode implementations

* Add context features annotation to shader node macro

* Cleanup

* Time -> RealTime

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2025-09-05 13:44:26 +02:00
committed by GitHub
parent c081d0a9de
commit acd7ba38cc
39 changed files with 869 additions and 328 deletions

View File

@@ -7,8 +7,8 @@ use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::{Comma, RArrow};
use syn::{
AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, Visibility,
WhereClause, parse_quote,
AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, TraitBound, Type, TypeImplTrait,
TypeParam, TypeParamBound, Visibility, WhereClause, parse_quote,
};
use crate::codegen::generate_node_code;
@@ -149,6 +149,7 @@ pub(crate) struct Input {
pub(crate) pat_ident: PatIdent,
pub(crate) ty: Type,
pub(crate) implementations: Punctuated<Type, Comma>,
pub(crate) context_features: Vec<Ident>,
}
impl Parse for Implementation {
@@ -384,10 +385,12 @@ fn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<Pa
.map(|attr| parse_implementations(attr, &pat_ident.ident))
.transpose()?
.unwrap_or_default();
let context_features = parse_context_feature_idents(ty);
input = Some(Input {
pat_ident,
ty: (**ty).clone(),
implementations,
context_features,
});
} else if let Pat::Ident(pat_ident) = &**pat {
let field = parse_field(pat_ident.clone(), (**ty).clone(), attrs).map_err(|e| Error::new_spanned(pat_ident, format!("Failed to parse argument '{}': {}", pat_ident.ident, e)))?;
@@ -404,6 +407,41 @@ fn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<Pa
Ok((input, fields))
}
/// Parse context feature identifiers from the trait bounds of a context parameter.
fn parse_context_feature_idents(ty: &Type) -> Vec<Ident> {
let mut features = Vec::new();
// Check if this is an impl trait (impl Ctx + ...)
if let Type::ImplTrait(TypeImplTrait { bounds, .. }) = ty {
for bound in bounds {
if let TypeParamBound::Trait(TraitBound { path, .. }) = bound {
// Extract the last segment of the trait path
if let Some(segment) = path.segments.last() {
match segment.ident.to_string().as_str() {
"ExtractFootprint"
| "ExtractRealTime"
| "ExtractAnimationTime"
| "ExtractIndex"
| "ExtractVarArgs"
| "InjectFootprint"
| "InjectRealTime"
| "InjectAnimationTime"
| "InjectIndex"
| "InjectVarArgs" => {
features.push(segment.ident.clone());
}
// Skip Modify* traits as they don't affect usage tracking
// Also ignore other traits like Ctx, ExtractAll, etc.
_ => {}
}
}
}
}
}
features
}
fn parse_implementations(attr: &Attribute, name: &Ident) -> syn::Result<Punctuated<Type, Comma>> {
let content: TokenStream2 = attr.parse_args()?;
let parser = Punctuated::<Type, Comma>::parse_terminated;
@@ -817,6 +855,7 @@ mod tests {
pat_ident: pat_ident("a"),
ty: parse_quote!(f64),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(f64),
is_async: false,
@@ -883,6 +922,7 @@ mod tests {
pat_ident: pat_ident("footprint"),
ty: parse_quote!(Footprint),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(T),
is_async: false,
@@ -936,7 +976,7 @@ mod tests {
let attr = quote!(category("Vector: Shape"));
let input = quote!(
/// Test
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> Vector {
fn circle(_: impl Ctx + ExtractFootprint, #[default(50.)] radius: f64) -> Vector {
// Implementation details...
}
);
@@ -960,8 +1000,9 @@ mod tests {
where_clause: None,
input: Input {
pat_ident: pat_ident("_"),
ty: parse_quote!(impl Ctx),
ty: parse_quote!(impl Ctx + ExtractFootprint),
implementations: Punctuated::new(),
context_features: vec![format_ident!("ExtractFootprint")],
},
output_type: parse_quote!(Vector),
is_async: false,
@@ -1024,6 +1065,7 @@ mod tests {
pat_ident: pat_ident("image"),
ty: parse_quote!(Table<Raster<P>>),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(Table<Raster<P>>),
is_async: false,
@@ -1098,6 +1140,7 @@ mod tests {
pat_ident: pat_ident("a"),
ty: parse_quote!(f64),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(f64),
is_async: false,
@@ -1160,6 +1203,7 @@ mod tests {
pat_ident: pat_ident("api"),
ty: parse_quote!(&WasmEditorApi),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(Table<Raster<CPU>>),
is_async: true,
@@ -1222,6 +1266,7 @@ mod tests {
pat_ident: pat_ident("input"),
ty: parse_quote!(i32),
implementations: Punctuated::new(),
context_features: vec![],
},
output_type: parse_quote!(i32),
is_async: false,