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; pub trait ExtractFootprint { #[track_caller] fn try_footprint(&self) -> Option<&Footprint>; #[track_caller] fn footprint(&self) -> &Footprint { self.try_footprint().unwrap_or_else(|| { log::error!("Context did not have a footprint, called from: {}", Location::caller()); &Footprint::DEFAULT }) } } pub trait ExtractRealTime { fn try_real_time(&self) -> Option; } pub trait ExtractAnimationTime { fn try_animation_time(&self) -> Option; } pub trait ExtractIndex { fn try_index(&self) -> Option>; } // Consider returning a slice or something like that pub trait ExtractVarArgs { fn vararg(&self, index: usize) -> Result, VarArgsResult>; fn varargs_len(&self) -> Result; 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; fn arc_clone(&self) -> Option>; } // Inject* traits for providing context features to downstream nodes pub trait InjectFootprint {} pub trait InjectRealTime {} pub trait InjectAnimationTime {} pub trait InjectIndex {} pub trait InjectVarArgs {} // 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 ExtractAll for T {} impl InjectFootprint for T {} impl InjectRealTime for T {} impl InjectIndex for T {} impl InjectAnimationTime for T {} impl InjectVarArgs for T {} impl ModifyFootprint for T {} impl ModifyRealTime for T {} impl ModifyIndex for T {} impl ModifyAnimationTime for T {} impl 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 { IndexOutOfBounds, NoVarArgs, } impl Ctx for Footprint {} impl ExtractFootprint for () { fn try_footprint(&self) -> Option<&Footprint> { log::error!("tried to extract footprint form (), {}", Location::caller()); None } } impl ExtractFootprint for &T { fn try_footprint(&self) -> Option<&Footprint> { (*self).try_footprint() } } impl ExtractFootprint for Option { fn try_footprint(&self) -> Option<&Footprint> { self.as_ref().and_then(|x| x.try_footprint()) } #[track_caller] fn footprint(&self) -> &Footprint { self.try_footprint().unwrap_or_else(|| { log::warn!("trying to extract footprint from context None {} ", Location::caller()); &Footprint::DEFAULT }) } } impl ExtractRealTime for Option { fn try_real_time(&self) -> Option { self.as_ref().and_then(|x| x.try_real_time()) } } impl ExtractAnimationTime for Option { fn try_animation_time(&self) -> Option { self.as_ref().and_then(|x| x.try_animation_time()) } } impl ExtractIndex for Option { fn try_index(&self) -> Option> { self.as_ref().and_then(|x| x.try_index()) } } impl ExtractVarArgs for Option { fn vararg(&self, index: usize) -> Result, VarArgsResult> { let Some(inner) = self else { return Err(VarArgsResult::NoVarArgs) }; inner.vararg(index) } fn varargs_len(&self) -> Result { 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 ExtractFootprint for Arc { fn try_footprint(&self) -> Option<&Footprint> { (**self).try_footprint() } } impl ExtractRealTime for Arc { fn try_real_time(&self) -> Option { (**self).try_real_time() } } impl ExtractAnimationTime for Arc { fn try_animation_time(&self) -> Option { (**self).try_animation_time() } } impl ExtractIndex for Arc { fn try_index(&self) -> Option> { (**self).try_index() } } impl ExtractVarArgs for Arc { fn vararg(&self, index: usize) -> Result, VarArgsResult> { (**self).vararg(index) } fn varargs_len(&self) -> Result { (**self).varargs_len() } fn hash_varargs(&self, hasher: &mut dyn Hasher) { (**self).hash_varargs(hasher) } } impl CloneVarArgs for Option { fn arc_clone(&self) -> Option> { self.as_ref().and_then(CloneVarArgs::arc_clone) } } impl ExtractVarArgs for &T { fn vararg(&self, index: usize) -> Result, VarArgsResult> { (*self).vararg(index) } fn varargs_len(&self) -> Result { (*self).varargs_len() } fn hash_varargs(&self, hasher: &mut dyn Hasher) { (*self).hash_varargs(hasher) } } impl CloneVarArgs for Arc { fn arc_clone(&self) -> Option> { (**self).arc_clone() } } impl Ctx for ContextImpl<'_> {} impl ArcCtx for OwnedContextImpl {} impl ExtractFootprint for ContextImpl<'_> { fn try_footprint(&self) -> Option<&Footprint> { self.footprint } } impl ExtractRealTime for ContextImpl<'_> { fn try_real_time(&self) -> Option { self.real_time } } impl ExtractIndex for ContextImpl<'_> { fn try_index(&self) -> Option> { self.index.clone() } } impl ExtractVarArgs for ContextImpl<'_> { fn vararg(&self, index: usize) -> Result, VarArgsResult> { let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied() } fn varargs_len(&self) -> Result { 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 { fn try_footprint(&self) -> Option<&Footprint> { self.footprint.as_ref() } } impl ExtractRealTime for OwnedContextImpl { fn try_real_time(&self) -> Option { self.real_time } } impl ExtractAnimationTime for OwnedContextImpl { fn try_animation_time(&self) -> Option { self.animation_time } } impl ExtractIndex for OwnedContextImpl { fn try_index(&self) -> Option> { self.index.clone() } } impl ExtractVarArgs for OwnedContextImpl { fn vararg(&self, index: usize) -> Result, VarArgsResult> { let Some(ref inner) = self.varargs else { let Some(ref parent) = self.parent else { return Err(VarArgsResult::NoVarArgs); }; return parent.vararg(index); }; inner.get(index).map(|x| x.as_ref() as DynRef<'_>).ok_or(VarArgsResult::IndexOutOfBounds) } fn varargs_len(&self) -> Result { let Some(ref inner) = self.varargs else { let Some(ref parent) = self.parent else { return Err(VarArgsResult::NoVarArgs); }; return parent.varargs_len(); }; 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 { fn arc_clone(&self) -> Option> { Some(self.clone()) } } pub type Context<'a> = Option>; type DynRef<'a> = &'a (dyn Any + Send + Sync); type DynBox = Box; #[derive(dyn_any::DynAny)] pub struct OwnedContextImpl { footprint: Option, varargs: Option>, parent: Option>, // This could be converted into a single enum to save extra bytes index: Option>, real_time: Option, animation_time: Option, } 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_len", &self.varargs.as_ref().map(|x| x.len())) .field("parent", &self.parent.as_ref().map(|_| "")) .field("index", &self.index) .field("real_time", &self.real_time) .field("animation_time", &self.animation_time) .finish() } } impl Default for OwnedContextImpl { #[track_caller] fn default() -> Self { Self::empty() } } impl Hash for OwnedContextImpl { fn hash(&self, state: &mut H) { self.footprint.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); } } impl OwnedContextImpl { #[track_caller] pub fn from(value: T) -> Self { OwnedContextImpl::from_flags(value, ContextFeatures::all()) } #[track_caller] pub fn from_flags(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, animation_time, } } pub const fn empty() -> Self { OwnedContextImpl { footprint: None, varargs: None, parent: None, index: None, real_time: None, animation_time: None, } } } pub trait DynHash { fn dyn_hash(&self, state: &mut dyn Hasher); } impl DynHash for H { fn dyn_hash(&self, mut state: &mut dyn Hasher) { self.hash(&mut state); } } impl Hash for dyn AnyHash { fn hash(&self, state: &mut H) { self.dyn_hash(state); } } impl Hash for Box { fn hash(&self, state: &mut H) { (**self).dyn_hash(state); } } pub trait AnyHash: DynHash + Any {} impl AnyHash for T {} impl OwnedContextImpl { pub fn set_footprint(&mut self, footprint: Footprint) { self.footprint = Some(footprint); } pub fn with_footprint(mut self, footprint: Footprint) -> Self { self.footprint = Some(footprint); self } 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) -> Self { assert!(self.varargs.is_none_or(|value| value.is_empty())); self.varargs = Some(Arc::new([value])); self } pub fn with_index(mut self, index: usize) -> Self { if let Some(current_index) = &mut self.index { current_index.push(index); } else { self.index = Some(vec![index]); } self } pub fn into_context(self) -> Option> { Some(Arc::new(self)) } pub fn erase_parent(mut self) -> Self { self.parent = None; self } } #[derive(Default, Clone, dyn_any::DynAny)] pub struct ContextImpl<'a> { pub(crate) footprint: Option<&'a Footprint>, varargs: Option<&'a [DynRef<'a>]>, index: Option>, // This could be converted into a single enum to save extra bytes real_time: Option, } impl<'a> ContextImpl<'a> { pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f> where 'a: 'f, { ContextImpl { footprint: Some(new_footprint), varargs: varargs.map(|x| x.borrow()), index: self.index.clone(), ..*self } } }