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

@@ -18,6 +18,7 @@ dealloc_nodes = []
graphene-core-shaders = { workspace = true, features = ["std"] }
# Workspace dependencies
bitflags = { workspace = true }
bytemuck = { workspace = true }
node-macro = { workspace = true }
num-traits = { workspace = true }

View File

@@ -1,4 +1,4 @@
use crate::{Ctx, ExtractAnimationTime, ExtractTime};
use crate::{Ctx, ExtractAnimationTime, ExtractRealTime};
const DAY: f64 = 1000. * 3600. * 24.;
@@ -21,17 +21,17 @@ pub enum AnimationTimeMode {
}
#[node_macro::node(category("Animation"))]
fn real_time(ctx: impl Ctx + ExtractTime, _primary: (), mode: RealTimeMode) -> f64 {
let time = ctx.try_time().unwrap_or_default();
fn real_time(ctx: impl Ctx + ExtractRealTime, _primary: (), mode: RealTimeMode) -> f64 {
let real_time = ctx.try_real_time().unwrap_or_default();
// TODO: Implement proper conversion using and existing time implementation
match mode {
RealTimeMode::Utc => time,
RealTimeMode::Year => (time / DAY / 365.25).floor() + 1970.,
RealTimeMode::Hour => (time / 1000. / 3600.).floor() % 24.,
RealTimeMode::Minute => (time / 1000. / 60.).floor() % 60.,
RealTimeMode::Utc => real_time,
RealTimeMode::Year => (real_time / DAY / 365.25).floor() + 1970.,
RealTimeMode::Hour => (real_time / 1000. / 3600.).floor() % 24., // TODO: Factor in a chosen timezone
RealTimeMode::Minute => (real_time / 1000. / 60.).floor() % 60., // TODO: Factor in a chosen timezone
RealTimeMode::Second => (time / 1000.).floor() % 60.,
RealTimeMode::Millisecond => time % 1000.,
RealTimeMode::Second => (real_time / 1000.).floor() % 60.,
RealTimeMode::Millisecond => real_time % 1000.,
}
}
@@ -40,13 +40,13 @@ fn animation_time(ctx: impl Ctx + ExtractAnimationTime) -> f64 {
ctx.try_animation_time().unwrap_or_default()
}
// These nodes require more sophistcated algorithms for giving the correct result
// These nodes require more sophisticated algorithms for giving the correct result
// #[node_macro::node(category("Animation"))]
// fn month(ctx: impl Ctx + ExtractTime) -> f64 {
// ((ctx.try_time().unwrap_or_default() / DAY / 365.25 % 1.) * 12.).floor()
// fn month(ctx: impl Ctx + ExtractRealTime) -> f64 {
// ((ctx.try_real_time().unwrap_or_default() / DAY / 365.25 % 1.) * 12.).floor()
// }
// #[node_macro::node(category("Animation"))]
// fn day(ctx: impl Ctx + ExtractTime) -> f64 {
// (ctx.try_time().unwrap_or_default() / DAY
// fn day(ctx: impl Ctx + ExtractRealTime) -> f64 {
// (ctx.try_real_time().unwrap_or_default() / DAY
// }

View File

@@ -2,6 +2,7 @@ use crate::transform::Footprint;
pub use graphene_core_shaders::context::{ArcCtx, Ctx};
use std::any::Any;
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::panic::Location;
use std::sync::Arc;
@@ -17,8 +18,8 @@ pub trait ExtractFootprint {
}
}
pub trait ExtractTime {
fn try_time(&self) -> Option<f64>;
pub trait ExtractRealTime {
fn try_real_time(&self) -> Option<f64>;
}
pub trait ExtractAnimationTime {
@@ -31,19 +32,106 @@ pub trait ExtractIndex {
// Consider returning a slice or something like that
pub trait ExtractVarArgs {
// Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult>;
fn varargs_len(&self) -> Result<usize, VarArgsResult>;
fn hash_varargs(&self, hasher: &mut dyn Hasher);
}
// Consider returning a slice or something like that
pub trait CloneVarArgs: ExtractVarArgs {
// fn box_clone(&self) -> Vec<DynBox>;
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
}
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs {}
// Inject* traits for providing context features to downstream nodes
pub trait InjectFootprint {}
pub trait InjectRealTime {}
pub trait InjectAnimationTime {}
pub trait InjectIndex {}
pub trait InjectVarArgs {}
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
// Modify* marker traits for context-transparent nodes
pub trait ModifyFootprint: ExtractFootprint + InjectFootprint {}
pub trait ModifyRealTime: ExtractRealTime + InjectRealTime {}
pub trait ModifyAnimationTime: ExtractAnimationTime + InjectAnimationTime {}
pub trait ModifyIndex: ExtractIndex + InjectIndex {}
pub trait ModifyVarArgs: ExtractVarArgs + InjectVarArgs {}
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs {}
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
impl<T: Ctx> InjectFootprint for T {}
impl<T: Ctx> InjectRealTime for T {}
impl<T: Ctx> InjectIndex for T {}
impl<T: Ctx> InjectAnimationTime for T {}
impl<T: Ctx> InjectVarArgs for T {}
impl<T: Ctx + InjectFootprint + ExtractFootprint> ModifyFootprint for T {}
impl<T: Ctx + InjectRealTime + ExtractRealTime> ModifyRealTime for T {}
impl<T: Ctx + InjectIndex + ExtractIndex> ModifyIndex for T {}
impl<T: Ctx + InjectAnimationTime + ExtractAnimationTime> ModifyAnimationTime for T {}
impl<T: Ctx + InjectVarArgs + ExtractVarArgs> ModifyVarArgs for T {}
// Public enum for flexible node macro codegen
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum ContextFeature {
ExtractFootprint,
ExtractRealTime,
ExtractAnimationTime,
ExtractIndex,
ExtractVarArgs,
InjectFootprint,
InjectRealTime,
InjectAnimationTime,
InjectIndex,
InjectVarArgs,
}
// Internal bitflags for fast compiler analysis
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]
pub struct ContextFeatures: u32 {
const FOOTPRINT = 1 << 0;
const REAL_TIME = 1 << 1;
const ANIMATION_TIME = 1 << 2;
const INDEX = 1 << 3;
const VARARGS = 1 << 4;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, Default)]
pub struct ContextDependencies {
pub extract: ContextFeatures,
pub inject: ContextFeatures,
}
impl From<&[ContextFeature]> for ContextDependencies {
fn from(features: &[ContextFeature]) -> Self {
let mut extract = ContextFeatures::empty();
let mut inject = ContextFeatures::empty();
for feature in features {
extract |= match feature {
ContextFeature::ExtractFootprint => ContextFeatures::FOOTPRINT,
ContextFeature::ExtractRealTime => ContextFeatures::REAL_TIME,
ContextFeature::ExtractAnimationTime => ContextFeatures::ANIMATION_TIME,
ContextFeature::ExtractIndex => ContextFeatures::INDEX,
ContextFeature::ExtractVarArgs => ContextFeatures::VARARGS,
_ => ContextFeatures::empty(),
};
inject |= match feature {
ContextFeature::InjectFootprint => ContextFeatures::FOOTPRINT,
ContextFeature::InjectRealTime => ContextFeatures::REAL_TIME,
ContextFeature::InjectAnimationTime => ContextFeatures::ANIMATION_TIME,
ContextFeature::InjectIndex => ContextFeatures::INDEX,
ContextFeature::InjectVarArgs => ContextFeatures::VARARGS,
_ => ContextFeatures::empty(),
};
}
Self { extract, inject }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VarArgsResult {
@@ -76,9 +164,9 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {
})
}
}
impl<T: ExtractTime + Sync> ExtractTime for Option<T> {
fn try_time(&self) -> Option<f64> {
self.as_ref().and_then(|x| x.try_time())
impl<T: ExtractRealTime + Sync> ExtractRealTime for Option<T> {
fn try_real_time(&self) -> Option<f64> {
self.as_ref().and_then(|x| x.try_real_time())
}
}
impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Option<T> {
@@ -101,15 +189,21 @@ impl<T: ExtractVarArgs + Sync> ExtractVarArgs for Option<T> {
let Some(inner) = self else { return Err(VarArgsResult::NoVarArgs) };
inner.varargs_len()
}
fn hash_varargs(&self, hasher: &mut dyn Hasher) {
if let Some(inner) = self {
inner.hash_varargs(hasher)
}
}
}
impl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {
fn try_footprint(&self) -> Option<&Footprint> {
(**self).try_footprint()
}
}
impl<T: ExtractTime + Sync> ExtractTime for Arc<T> {
fn try_time(&self) -> Option<f64> {
(**self).try_time()
impl<T: ExtractRealTime + Sync> ExtractRealTime for Arc<T> {
fn try_real_time(&self) -> Option<f64> {
(**self).try_real_time()
}
}
impl<T: ExtractAnimationTime + Sync> ExtractAnimationTime for Arc<T> {
@@ -130,6 +224,10 @@ impl<T: ExtractVarArgs + Sync> ExtractVarArgs for Arc<T> {
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
(**self).varargs_len()
}
fn hash_varargs(&self, hasher: &mut dyn Hasher) {
(**self).hash_varargs(hasher)
}
}
impl<T: CloneVarArgs + Sync> CloneVarArgs for Option<T> {
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
@@ -145,6 +243,10 @@ impl<T: ExtractVarArgs + Sync> ExtractVarArgs for &T {
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
(*self).varargs_len()
}
fn hash_varargs(&self, hasher: &mut dyn Hasher) {
(*self).hash_varargs(hasher)
}
}
impl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>> {
@@ -160,9 +262,9 @@ impl ExtractFootprint for ContextImpl<'_> {
self.footprint
}
}
impl ExtractTime for ContextImpl<'_> {
fn try_time(&self) -> Option<f64> {
self.time
impl ExtractRealTime for ContextImpl<'_> {
fn try_real_time(&self) -> Option<f64> {
self.real_time
}
}
impl ExtractIndex for ContextImpl<'_> {
@@ -180,6 +282,10 @@ impl ExtractVarArgs for ContextImpl<'_> {
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
Ok(inner.len())
}
fn hash_varargs(&self, _hasher: &mut dyn Hasher) {
todo!()
}
}
impl ExtractFootprint for OwnedContextImpl {
@@ -187,8 +293,8 @@ impl ExtractFootprint for OwnedContextImpl {
self.footprint.as_ref()
}
}
impl ExtractTime for OwnedContextImpl {
fn try_time(&self) -> Option<f64> {
impl ExtractRealTime for OwnedContextImpl {
fn try_real_time(&self) -> Option<f64> {
self.real_time
}
}
@@ -210,7 +316,7 @@ impl ExtractVarArgs for OwnedContextImpl {
};
return parent.vararg(index);
};
inner.get(index).map(|x| x.as_ref()).ok_or(VarArgsResult::IndexOutOfBounds)
inner.get(index).map(|x| x.as_ref() as DynRef<'_>).ok_or(VarArgsResult::IndexOutOfBounds)
}
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
@@ -222,6 +328,20 @@ impl ExtractVarArgs for OwnedContextImpl {
};
Ok(inner.len())
}
fn hash_varargs(&self, mut hasher: &mut dyn Hasher) {
match (&self.varargs, &self.parent) {
(Some(inner), _) => {
for arg in inner.iter() {
arg.hash(&mut hasher);
}
}
(None, Some(parent)) => {
parent.hash_varargs(hasher);
}
_ => (),
};
}
}
impl CloneVarArgs for Arc<OwnedContextImpl> {
@@ -232,7 +352,7 @@ impl CloneVarArgs for Arc<OwnedContextImpl> {
pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
type DynRef<'a> = &'a (dyn Any + Send + Sync);
type DynBox = Box<dyn Any + Send + Sync>;
type DynBox = Box<dyn AnyHash + Send + Sync>;
#[derive(dyn_any::DynAny)]
pub struct OwnedContextImpl {
@@ -249,7 +369,7 @@ impl std::fmt::Debug for OwnedContextImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OwnedContextImpl")
.field("footprint", &self.footprint)
.field("varargs", &self.varargs)
.field("varargs_len", &self.varargs.as_ref().map(|x| x.len()))
.field("parent", &self.parent.as_ref().map(|_| "<Parent>"))
.field("index", &self.index)
.field("real_time", &self.real_time)
@@ -265,11 +385,10 @@ impl Default for OwnedContextImpl {
}
}
impl std::hash::Hash for OwnedContextImpl {
impl Hash for OwnedContextImpl {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.footprint.hash(state);
self.varargs.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state);
self.parent.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state);
self.hash_varargs(state);
self.index.hash(state);
self.real_time.map(|x| x.to_bits()).hash(state);
self.animation_time.map(|x| x.to_bits()).hash(state);
@@ -279,21 +398,29 @@ impl std::hash::Hash for OwnedContextImpl {
impl OwnedContextImpl {
#[track_caller]
pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {
let footprint = value.try_footprint().copied();
let index = value.try_index();
let time = value.try_time();
let frame_time = value.try_animation_time();
let parent = match value.varargs_len() {
Ok(x) if x > 0 => value.arc_clone(),
_ => None,
};
OwnedContextImpl::from_flags(value, ContextFeatures::all())
}
#[track_caller]
pub fn from_flags<T: ExtractAll + CloneVarArgs>(value: T, bitflags: ContextFeatures) -> Self {
let footprint = bitflags.contains(ContextFeatures::FOOTPRINT).then(|| value.try_footprint().copied()).flatten();
let index = bitflags.contains(ContextFeatures::INDEX).then(|| value.try_index()).flatten();
let real_time = bitflags.contains(ContextFeatures::REAL_TIME).then(|| value.try_real_time()).flatten();
let animation_time = bitflags.contains(ContextFeatures::ANIMATION_TIME).then(|| value.try_animation_time()).flatten();
let parent = bitflags
.contains(ContextFeatures::VARARGS)
.then(|| match value.varargs_len() {
Ok(x) if x > 0 => value.arc_clone(),
_ => None,
})
.flatten();
OwnedContextImpl {
footprint,
varargs: None,
parent,
index,
real_time: time,
animation_time: frame_time,
real_time,
animation_time,
}
}
pub const fn empty() -> Self {
@@ -308,6 +435,30 @@ impl OwnedContextImpl {
}
}
pub trait DynHash {
fn dyn_hash(&self, state: &mut dyn Hasher);
}
impl<H: Hash + ?Sized> DynHash for H {
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
}
impl Hash for dyn AnyHash {
fn hash<H: Hasher>(&self, state: &mut H) {
self.dyn_hash(state);
}
}
impl Hash for Box<dyn AnyHash + Send + Sync> {
fn hash<H: Hasher>(&self, state: &mut H) {
(**self).dyn_hash(state);
}
}
pub trait AnyHash: DynHash + Any {}
impl<T: DynHash + Any> AnyHash for T {}
impl OwnedContextImpl {
pub fn set_footprint(&mut self, footprint: Footprint) {
self.footprint = Some(footprint);
@@ -316,15 +467,15 @@ impl OwnedContextImpl {
self.footprint = Some(footprint);
self
}
pub fn with_real_time(mut self, time: f64) -> Self {
self.real_time = Some(time);
pub fn with_real_time(mut self, real_time: f64) -> Self {
self.real_time = Some(real_time);
self
}
pub fn with_animation_time(mut self, animation_time: f64) -> Self {
self.animation_time = Some(animation_time);
self
}
pub fn with_vararg(mut self, value: Box<dyn Any + Send + Sync>) -> Self {
pub fn with_vararg(mut self, value: Box<dyn AnyHash + Send + Sync>) -> Self {
assert!(self.varargs.is_none_or(|value| value.is_empty()));
self.varargs = Some(Arc::new([value]));
self
@@ -350,9 +501,8 @@ impl OwnedContextImpl {
pub struct ContextImpl<'a> {
pub(crate) footprint: Option<&'a Footprint>,
varargs: Option<&'a [DynRef<'a>]>,
// This could be converted into a single enum to save extra bytes
index: Option<Vec<usize>>,
time: Option<f64>,
index: Option<Vec<usize>>, // This could be converted into a single enum to save extra bytes
real_time: Option<f64>,
}
impl<'a> ContextImpl<'a> {

View File

@@ -0,0 +1,121 @@
use crate::Artboard;
use crate::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll};
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::uuid::NodeId;
use crate::vector::Vector;
use crate::{Graphic, OwnedContextImpl};
use core::f64;
use glam::{DAffine2, DVec2};
use graphene_core_shaders::color::Color;
/// Node for filtering context features based on requirements
/// This node is inserted by the compiler to "zero out" unused context parts
#[node_macro::node(category("Internal"))]
async fn context_modification<T>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> (),
Context -> bool,
Context -> u32,
Context -> u64,
Context -> f32,
Context -> f64,
Context -> String,
Context -> DAffine2,
Context -> DVec2,
Context -> Vec<DVec2>,
Context -> Vec<NodeId>,
Context -> Vec<f64>,
Context -> Vec<f32>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<Artboard>,
Context -> Table<GradientStops>,
Context -> GradientStops,
)]
value: impl Node<Context<'static>, Output = T>,
features_to_keep: ContextFeatures,
) -> T {
let new_context = OwnedContextImpl::from_flags(ctx, features_to_keep);
value.eval(Some(new_context.into())).await
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transform::Footprint;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
/// Test that the hash of a nullified context remains stable even when nullified inputs change
#[test]
fn test_nullified_context_hash_stability() {
use crate::Context;
use std::sync::Arc;
// Create original contexts using the Context type (Option<Arc<OwnedContextImpl>>)
let original_ctx: Context = Some(Arc::new(
OwnedContextImpl::empty()
.with_footprint(Footprint::default())
.with_index(1)
.with_real_time(10.5)
.with_vararg(Box::new("test"))
.with_animation_time(20.25),
));
// Test nullifying different features - hash should remain stable for each nullification
let features_to_keep = ContextFeatures::empty(); // Nullify everything
// Create nullified context - this should only keep features specified in features_to_keep
let nullified_ctx = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), features_to_keep);
// Calculate hash of nullified context
let mut hasher1 = DefaultHasher::new();
nullified_ctx.hash(&mut hasher1);
let hash1 = hasher1.finish();
// Create a different original context with changed values
let changed_ctx: Context = Some(Arc::new(
OwnedContextImpl::empty()
.with_footprint(Footprint::default()) // Same footprint
.with_index(2)
.with_real_time(999.9) // Different real time
.with_vararg(Box::new("test"))
.with_animation_time(888.8), // Different animation time
));
// Create nullified context from the changed original - should have same hash since everything is nullified
let nullified_changed_ctx = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), features_to_keep);
let mut hasher2 = DefaultHasher::new();
nullified_changed_ctx.hash(&mut hasher2);
let hash2 = hasher2.finish();
// Hash should be the same because all features were nullified
assert_eq!(hash1, hash2, "Hash of nullified context should remain stable regardless of input changes when features are nullified");
// Test partial nullification - keep only footprint
let partial_features = ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS;
let partial_nullified1 = OwnedContextImpl::from_flags(original_ctx.clone().unwrap(), partial_features);
let partial_nullified2 = OwnedContextImpl::from_flags(changed_ctx.clone().unwrap(), partial_features);
let mut hasher3 = DefaultHasher::new();
partial_nullified1.hash(&mut hasher3);
let hash3 = hasher3.finish();
let mut hasher4 = DefaultHasher::new();
partial_nullified2.hash(&mut hasher4);
let hash4 = hasher4.finish();
// These should be the same because both have the same footprint (Footprint::default()) and varargs
// and other features are nullified
assert_eq!(hash3, hash4, "Hash should be stable when keeping only footprint and footprint values are the same");
}
}

View File

@@ -7,6 +7,7 @@ pub mod blending_nodes;
pub mod bounds;
pub mod consts;
pub mod context;
pub mod context_modification;
pub mod debug;
pub mod extract_xy;
pub mod generic;

View File

@@ -1,4 +1,4 @@
use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use crate::{ContextFeature, Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType};
use std::collections::HashMap;
use std::marker::PhantomData;
@@ -16,6 +16,7 @@ pub struct NodeMetadata {
pub fields: Vec<FieldMetadata>,
pub description: &'static str,
pub properties: Option<&'static str>,
pub context_features: Vec<ContextFeature>,
}
// Translation struct between macro and definition

View File

@@ -20,7 +20,7 @@ impl Default for Font {
}
}
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, DynAny)]
#[derive(Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, DynAny)]
pub struct FontCache {
/// Actual font file data used for rendering a font
font_file_data: HashMap<Font, Vec<u8>>,
@@ -28,6 +28,15 @@ pub struct FontCache {
preview_urls: HashMap<Font, String>,
}
impl std::fmt::Debug for FontCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FontCache")
.field("font_file_data", &self.font_file_data.keys().collect::<Vec<_>>())
.field("preview_urls", &self.preview_urls)
.finish()
}
}
impl FontCache {
/// Returns the font family name if the font is cached, otherwise returns the fallback font family name if that is cached
pub fn resolve_font<'a>(&'a self, font: &'a Font) -> Option<&'a Font> {

View File

@@ -1,16 +1,16 @@
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform};
use crate::transform::{ApplyTransform, Transform};
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, InjectFootprint, ModifyFootprint, OwnedContextImpl};
use core::f64;
use glam::{DAffine2, DVec2};
use graphene_core_shaders::color::Color;
#[node_macro::node(category(""))]
async fn transform<T: ApplyTransform + 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
ctx: impl Ctx + CloneVarArgs + ExtractAll + ModifyFootprint,
#[implementations(
Context -> DAffine2,
Context -> DVec2,
@@ -46,7 +46,7 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[node_macro::node(category(""))]
fn replace_transform<Data, TransformInput: Transform>(
_: impl Ctx,
_: impl Ctx + InjectFootprint,
#[implementations(Table<Vector>, Table<Raster<CPU>>, Table<Graphic>, Table<Color>, Table<GradientStops>)] mut data: Table<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Table<Data> {
@@ -91,43 +91,3 @@ fn decompose_rotation(_: impl Ctx, transform: DAffine2) -> f64 {
fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
transform.decompose_scale()
}
#[node_macro::node(category("Debug"))]
async fn boundless_footprint<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> String,
Context -> f64,
)]
transform_target: impl Node<Context<'static>, Output = T>,
) -> T {
let ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::BOUNDLESS);
transform_target.eval(ctx.into_context()).await
}
#[node_macro::node(category("Debug"))]
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> Table<Color>,
Context -> Table<GradientStops>,
Context -> String,
Context -> f64,
)]
transform_target: impl Node<Context<'static>, Output = T>,
) -> T {
let ctx = OwnedContextImpl::from(ctx).with_real_time(0.);
transform_target.eval(ctx.into_context()).await
}

View File

@@ -2,13 +2,24 @@ use crate::gradient::GradientStops;
use crate::raster_types::{CPU, Raster};
use crate::table::{Table, TableRowRef};
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, OwnedContextImpl};
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, InjectIndex, InjectVarArgs, OwnedContextImpl};
use glam::DVec2;
use graphene_core_shaders::color::Color;
#[repr(transparent)]
#[derive(dyn_any::DynAny)]
struct HashableDVec2(DVec2);
impl std::hash::Hash for HashableDVec2 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.x.to_bits().hash(state);
self.0.y.to_bits().hash(state);
}
}
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx + InjectIndex + InjectVarArgs,
points: Table<Vector>,
#[implementations(
Context -> Table<Graphic>,
@@ -26,7 +37,7 @@ async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(HashableDVec2(transformed_point)));
let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut generated_row in generated_instance.into_iter() {
@@ -52,7 +63,7 @@ async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
ctx: impl ExtractAll + CloneVarArgs + Ctx + InjectIndex,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<Vector>,
@@ -84,8 +95,8 @@ async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<DVec2>()) {
Ok(Some(position)) => return *position,
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<HashableDVec2>()) {
Ok(Some(position)) => return position.0,
Ok(_) => warn!("Extracted value of incorrect type"),
Err(e) => warn!("Cannot extract position vararg: {e:?}"),
}