mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
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:
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
// }
|
||||
|
||||
@@ -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> {
|
||||
|
||||
121
node-graph/gcore/src/context_modification.rs
Normal file
121
node-graph/gcore/src/context_modification.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:?}"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user