mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 07:18:04 +08:00
Context nullification, cached monitor nodes
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||
use glam::{DAffine2, UVec2};
|
||||
use graphene_core::text::FontCache;
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
use std::fmt::Debug;
|
||||
@@ -236,69 +235,23 @@ pub struct RenderConfig {
|
||||
pub for_export: bool,
|
||||
}
|
||||
|
||||
struct Logger;
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct ApplicationIoValue<Io>(pub Option<Arc<Io>>);
|
||||
|
||||
impl NodeGraphUpdateSender for Logger {
|
||||
fn send(&self, message: NodeGraphUpdateMessage) {
|
||||
log::warn!("dispatching message with fallback node graph update sender {:?}", message);
|
||||
}
|
||||
unsafe impl<T: StaticTypeSized> StaticType for ApplicationIoValue<T> {
|
||||
type Static = ApplicationIoValue<T::Static>;
|
||||
}
|
||||
|
||||
struct DummyPreferences;
|
||||
impl<T> Eq for ApplicationIoValue<T> {}
|
||||
|
||||
impl GetEditorPreferences for DummyPreferences {
|
||||
fn use_vello(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditorApi<Io> {
|
||||
/// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`].
|
||||
pub font_cache: FontCache,
|
||||
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
|
||||
pub application_io: Option<Arc<Io>>,
|
||||
pub node_graph_message_sender: Box<dyn NodeGraphUpdateSender + Send + Sync>,
|
||||
/// Editor preferences made available to the graph through the [`WasmEditorApi`].
|
||||
pub editor_preferences: Box<dyn GetEditorPreferences + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<Io> Eq for EditorApi<Io> {}
|
||||
|
||||
impl<Io: Default> Default for EditorApi<Io> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_cache: FontCache::default(),
|
||||
application_io: None,
|
||||
node_graph_message_sender: Box::new(Logger),
|
||||
editor_preferences: Box::new(DummyPreferences),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io> Hash for EditorApi<Io> {
|
||||
impl<T> Hash for ApplicationIoValue<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.font_cache.hash(state);
|
||||
self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
|
||||
(self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state);
|
||||
(self.editor_preferences.as_ref() as *const dyn GetEditorPreferences).hash(state);
|
||||
self.0.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io> PartialEq for EditorApi<Io> {
|
||||
impl<T> PartialEq for ApplicationIoValue<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.font_cache == other.font_cache
|
||||
&& self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize)
|
||||
&& std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _)
|
||||
&& std::ptr::eq(self.editor_preferences.as_ref() as *const _, other.editor_preferences.as_ref() as *const _)
|
||||
self.0.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.0.as_ref().map_or(0, |io| addr_of!(io) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for EditorApi<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("EditorApi").field("font_cache", &self.font_cache).finish()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<T> {
|
||||
type Static = EditorApi<T::Static>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Ctx, ExtractAnimationTime, ExtractTime};
|
||||
use crate::{Ctx, ExtractAnimationTime, ExtractRealTime};
|
||||
|
||||
const DAY: f64 = 1000. * 3600. * 24.;
|
||||
|
||||
@@ -21,8 +21,8 @@ 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 time = ctx.try_real_time().unwrap_or_default();
|
||||
// TODO: Implement proper conversion using and existing time implementation
|
||||
match mode {
|
||||
RealTimeMode::Utc => time,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use glam::{DAffine2, UVec2};
|
||||
|
||||
use crate::transform::Footprint;
|
||||
use std::any::Any;
|
||||
use std::borrow::Borrow;
|
||||
use std::panic::Location;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait Ctx: Clone + Send {}
|
||||
|
||||
pub trait ExtractFootprint {
|
||||
#[track_caller]
|
||||
fn try_footprint(&self) -> Option<&Footprint>;
|
||||
#[track_caller]
|
||||
fn footprint(&self) -> &Footprint {
|
||||
@@ -18,8 +18,12 @@ pub trait ExtractFootprint {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ExtractTime {
|
||||
fn try_time(&self) -> Option<f64>;
|
||||
pub trait ExtractDownstreamTransform {
|
||||
fn try_downstream_transform(&self) -> Option<&DAffine2>;
|
||||
}
|
||||
|
||||
pub trait ExtractRealTime {
|
||||
fn try_real_time(&self) -> Option<f64>;
|
||||
}
|
||||
|
||||
pub trait ExtractAnimationTime {
|
||||
@@ -42,9 +46,32 @@ pub trait CloneVarArgs: ExtractVarArgs {
|
||||
fn arc_clone(&self) -> Option<Arc<dyn ExtractVarArgs + Send + Sync>>;
|
||||
}
|
||||
|
||||
pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs {}
|
||||
pub trait ExtractAll: ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs {}
|
||||
|
||||
impl<T: ?Sized + ExtractFootprint + ExtractIndex + ExtractTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
|
||||
impl<T: ?Sized + ExtractFootprint + ExtractDownstreamTransform + ExtractIndex + ExtractRealTime + ExtractAnimationTime + ExtractVarArgs> ExtractAll for T {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ContextDependency {
|
||||
ExtractFootprint,
|
||||
// Can be used by cull nodes to check if the final output would be outside the footprint viewport
|
||||
ExtractDownstreamTransform,
|
||||
ExtractRealTime,
|
||||
ExtractAnimationTime,
|
||||
ExtractIndex,
|
||||
ExtractVarArgs,
|
||||
}
|
||||
|
||||
pub fn all_context_dependencies() -> Vec<ContextDependency> {
|
||||
vec![
|
||||
ContextDependency::ExtractFootprint,
|
||||
// Can be used by cull nodes to check if the final output would be outside the footprint viewport
|
||||
ContextDependency::ExtractDownstreamTransform,
|
||||
ContextDependency::ExtractRealTime,
|
||||
ContextDependency::ExtractAnimationTime,
|
||||
ContextDependency::ExtractIndex,
|
||||
ContextDependency::ExtractVarArgs,
|
||||
]
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum VarArgsResult {
|
||||
@@ -72,17 +99,30 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Option<T> {
|
||||
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 ExtractDownstreamTransform for () {
|
||||
fn try_downstream_transform(&self) -> Option<&DAffine2> {
|
||||
log::error!("tried to extract downstream transform form (), {}", Location::caller());
|
||||
None
|
||||
}
|
||||
}
|
||||
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: ExtractDownstreamTransform + Ctx + Sync + Send> ExtractDownstreamTransform for &T {
|
||||
fn try_downstream_transform(&self) -> Option<&DAffine2> {
|
||||
(*self).try_downstream_transform()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Option<T> {
|
||||
fn try_downstream_transform(&self) -> Option<&DAffine2> {
|
||||
self.as_ref().and_then(|x| x.try_downstream_transform())
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -111,9 +151,16 @@ impl<T: ExtractFootprint + Sync> ExtractFootprint for Arc<T> {
|
||||
(**self).try_footprint()
|
||||
}
|
||||
}
|
||||
impl<T: ExtractTime + Sync> ExtractTime for Arc<T> {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
(**self).try_time()
|
||||
|
||||
impl<T: ExtractDownstreamTransform + Sync> ExtractDownstreamTransform for Arc<T> {
|
||||
fn try_downstream_transform(&self) -> Option<&DAffine2> {
|
||||
(**self).try_downstream_transform()
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -156,43 +203,22 @@ impl<T: CloneVarArgs + Sync> CloneVarArgs for Arc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ctx for ContextImpl<'_> {}
|
||||
impl Ctx for Arc<OwnedContextImpl> {}
|
||||
|
||||
impl ExtractFootprint for ContextImpl<'_> {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
self.footprint
|
||||
}
|
||||
}
|
||||
impl ExtractTime for ContextImpl<'_> {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
self.time
|
||||
}
|
||||
}
|
||||
impl ExtractIndex for ContextImpl<'_> {
|
||||
fn try_index(&self) -> Option<Vec<usize>> {
|
||||
self.index.clone()
|
||||
}
|
||||
}
|
||||
impl ExtractVarArgs for ContextImpl<'_> {
|
||||
fn vararg(&self, index: usize) -> Result<DynRef<'_>, VarArgsResult> {
|
||||
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
|
||||
inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied()
|
||||
}
|
||||
|
||||
fn varargs_len(&self) -> Result<usize, VarArgsResult> {
|
||||
let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) };
|
||||
Ok(inner.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractFootprint for OwnedContextImpl {
|
||||
fn try_footprint(&self) -> Option<&Footprint> {
|
||||
self.footprint.as_ref()
|
||||
}
|
||||
}
|
||||
impl ExtractTime for OwnedContextImpl {
|
||||
fn try_time(&self) -> Option<f64> {
|
||||
|
||||
impl ExtractDownstreamTransform for OwnedContextImpl {
|
||||
fn try_downstream_transform(&self) -> Option<&DAffine2> {
|
||||
self.downstream_transform.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractRealTime for OwnedContextImpl {
|
||||
fn try_real_time(&self) -> Option<f64> {
|
||||
self.real_time
|
||||
}
|
||||
}
|
||||
@@ -234,20 +260,26 @@ impl CloneVarArgs for Arc<OwnedContextImpl> {
|
||||
}
|
||||
}
|
||||
|
||||
// Lifetime isnt necessary?
|
||||
pub type Context<'a> = Option<Arc<OwnedContextImpl>>;
|
||||
type DynRef<'a> = &'a (dyn Any + Send + Sync);
|
||||
type DynBox = Box<dyn Any + Send + Sync>;
|
||||
|
||||
#[derive(dyn_any::DynAny)]
|
||||
pub struct OwnedContextImpl {
|
||||
// The footprint represents the document to viewport render metadata
|
||||
footprint: Option<Footprint>,
|
||||
varargs: Option<Arc<[DynBox]>>,
|
||||
parent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,
|
||||
// This could be converted into a single enum to save extra bytes
|
||||
index: Option<Vec<usize>>,
|
||||
// The transform node does not modify the document to viewport, it instead modifies this,
|
||||
// which can be used to transform the evaluated data from the node and check if it is within the
|
||||
// document to viewport transform.
|
||||
downstream_transform: Option<DAffine2>,
|
||||
index: Option<usize>,
|
||||
real_time: Option<f64>,
|
||||
animation_time: Option<f64>,
|
||||
|
||||
// varargs: Option<(Vec<String>, Arc<[DynBox]>)>,
|
||||
varargs: Option<Arc<[DynBox]>>,
|
||||
|
||||
parent: Option<Arc<dyn ExtractVarArgs + Sync + Send>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for OwnedContextImpl {
|
||||
@@ -285,8 +317,9 @@ impl OwnedContextImpl {
|
||||
#[track_caller]
|
||||
pub fn from<T: ExtractAll + CloneVarArgs>(value: T) -> Self {
|
||||
let footprint = value.try_footprint().copied();
|
||||
let downstream_transform = value.try_downstream_transform().copied();
|
||||
let index = value.try_index();
|
||||
let time = value.try_time();
|
||||
let time = value.try_real_time();
|
||||
let frame_time = value.try_animation_time();
|
||||
let parent = match value.varargs_len() {
|
||||
Ok(x) if x > 0 => value.arc_clone(),
|
||||
@@ -294,21 +327,40 @@ impl OwnedContextImpl {
|
||||
};
|
||||
OwnedContextImpl {
|
||||
footprint,
|
||||
varargs: None,
|
||||
parent,
|
||||
downstream_transform,
|
||||
index,
|
||||
real_time: time,
|
||||
animation_time: frame_time,
|
||||
varargs: None,
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
OwnedContextImpl {
|
||||
footprint: None,
|
||||
varargs: None,
|
||||
parent: None,
|
||||
downstream_transform: None,
|
||||
index: None,
|
||||
real_time: None,
|
||||
animation_time: None,
|
||||
varargs: None,
|
||||
parent: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nullify(&mut self, nullify: &Vec<ContextDependency>) {
|
||||
for context_dependency in nullify {
|
||||
match context_dependency {
|
||||
ContextDependency::ExtractFootprint => self.footprint = None,
|
||||
ContextDependency::ExtractDownstreamTransform => self.downstream_transform = None,
|
||||
ContextDependency::ExtractRealTime => self.real_time = None,
|
||||
ContextDependency::ExtractAnimationTime => self.animation_time = None,
|
||||
ContextDependency::ExtractIndex => self.index = None,
|
||||
ContextDependency::ExtractVarArgs => {
|
||||
self.varargs = None;
|
||||
self.parent = None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,10 +369,31 @@ impl OwnedContextImpl {
|
||||
pub fn set_footprint(&mut self, footprint: Footprint) {
|
||||
self.footprint = Some(footprint);
|
||||
}
|
||||
pub fn set_downstream_transform(&mut self, transform: DAffine2) {
|
||||
self.downstream_transform = Some(transform);
|
||||
}
|
||||
pub fn try_apply_downstream_transform(&mut self, transform: DAffine2) {
|
||||
if let Some(downstream_transform) = self.downstream_transform {
|
||||
self.downstream_transform = Some(downstream_transform * transform);
|
||||
}
|
||||
}
|
||||
pub fn set_real_time(&mut self, time: f64) {
|
||||
self.real_time = Some(time);
|
||||
}
|
||||
pub fn set_animation_time(&mut self, animation_time: f64) {
|
||||
self.animation_time = Some(animation_time);
|
||||
}
|
||||
pub fn set_index(&mut self, index: usize) {
|
||||
self.index = Some(index);
|
||||
}
|
||||
pub fn with_footprint(mut self, footprint: Footprint) -> Self {
|
||||
self.footprint = Some(footprint);
|
||||
self
|
||||
}
|
||||
pub fn with_downstream_transform(mut self, downstream_transform: DAffine2) -> Self {
|
||||
self.downstream_transform = Some(downstream_transform);
|
||||
self
|
||||
}
|
||||
pub fn with_real_time(mut self, time: f64) -> Self {
|
||||
self.real_time = Some(time);
|
||||
self
|
||||
@@ -329,11 +402,6 @@ impl OwnedContextImpl {
|
||||
self.animation_time = Some(animation_time);
|
||||
self
|
||||
}
|
||||
pub fn with_vararg(mut self, value: Box<dyn Any + Send + Sync>) -> 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);
|
||||
@@ -345,31 +413,193 @@ impl OwnedContextImpl {
|
||||
pub fn into_context(self) -> Option<Arc<Self>> {
|
||||
Some(Arc::new(self))
|
||||
}
|
||||
pub fn add_vararg(mut self, _variable_name: String, value: Box<dyn Any + Send + Sync>) -> Self {
|
||||
assert!(self.varargs.is_none_or(|value| value.is_empty()));
|
||||
// self.varargs = Some((vec![variable_name], Arc::new([value])));
|
||||
self.varargs = Some(Arc::new([value]));
|
||||
|
||||
self
|
||||
}
|
||||
pub fn set_varargs(&mut self, var_args: (Vec<String>, Arc<[DynBox]>)) {
|
||||
self.varargs = Some(var_args.1)
|
||||
}
|
||||
pub fn with_vararg(mut self, var_args: (impl Into<String>, DynBox)) -> Self {
|
||||
self.varargs = Some(Arc::new([var_args.1]));
|
||||
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>]>,
|
||||
// This could be converted into a single enum to save extra bytes
|
||||
index: Option<Vec<usize>>,
|
||||
time: Option<f64>,
|
||||
// #[derive(Default, Clone, Copy, dyn_any::DynAny)]
|
||||
// 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<usize>,
|
||||
// time: Option<f64>,
|
||||
// }
|
||||
|
||||
// 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()),
|
||||
// ..*self
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_footprint(ctx: impl Ctx + ExtractFootprint) -> Option<Footprint> {
|
||||
ctx.try_footprint().copied()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_document_to_viewport(ctx: impl Ctx + ExtractFootprint) -> Option<DAffine2> {
|
||||
ctx.try_footprint().map(|footprint| footprint.transform.clone())
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_resolution(ctx: impl Ctx + ExtractFootprint) -> Option<UVec2> {
|
||||
ctx.try_footprint().map(|footprint| footprint.resolution.clone())
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_downstream_transform(ctx: impl Ctx + ExtractDownstreamTransform) -> Option<DAffine2> {
|
||||
ctx.try_downstream_transform().copied()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_real_time(ctx: impl Ctx + ExtractRealTime) -> Option<f64> {
|
||||
ctx.try_real_time()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_animation_time(ctx: impl Ctx + ExtractAnimationTime) -> Option<f64> {
|
||||
ctx.try_animation_time()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Context Getter"))]
|
||||
fn get_index(ctx: impl Ctx + ExtractIndex) -> Option<u32> {
|
||||
ctx.try_index().map(|index| index as u32)
|
||||
}
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// async fn loop_node<T: Default>(
|
||||
// ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
// #[implementations(
|
||||
// Context -> Option<u32>,
|
||||
// )]
|
||||
// return_if_some: impl Node<Context<'static>, Output = Option<T>>,
|
||||
// #[implementations(
|
||||
// Context -> (),
|
||||
// )]
|
||||
// run_if_none: impl Node<Context<'static>, Output = ()>,
|
||||
// ) -> T {
|
||||
// let mut context = OwnedContextImpl::from(ctx.clone());
|
||||
// context.arc_mutex = Some(Arc::new(Mutex::new(None)));
|
||||
// loop {
|
||||
// if let Some(return_value) = return_if_some.eval(context.clone().into_context()).await {
|
||||
// return return_value;
|
||||
// }
|
||||
// run_if_none.eval(context.clone().into_context()).await;
|
||||
// let Some(context_after_loop) = context.arc_mutex.unwrap().lock().unwrap().take() else {
|
||||
// log::error!("Loop context was not set, breaking loop to avoid infinite loop");
|
||||
// return T::default();
|
||||
// };
|
||||
// context = context_after_loop;
|
||||
// context.arc_mutex = Some(Arc::new(Mutex::new(None)));
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// async fn update_loop_node_context(ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync) -> () {
|
||||
// let mut context = OwnedContextImpl::from(ctx.clone());
|
||||
// let context_after_loop = OwnedContextImpl::from(ctx.clone());
|
||||
// if let Some(arc_mutex) = context.arc_mutex.as_ref() {
|
||||
// *arc_mutex.lock().unwrap() = Some(context_after_loop);
|
||||
// }
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Loop"))]
|
||||
async fn set_index<T: 'n + 'static>(
|
||||
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
Context -> u32,
|
||||
Context -> (),
|
||||
)]
|
||||
input: impl Node<Context<'static>, Output = T>,
|
||||
number: u32,
|
||||
) -> T {
|
||||
let mut new_context = OwnedContextImpl::from(ctx);
|
||||
new_context.index = Some(number.try_into().unwrap());
|
||||
input.eval(new_context.into_context()).await
|
||||
}
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// fn create_arc_mutex(_ctx: impl Ctx) -> Arc<Mutex<Option<OwnedContextImpl>>> {
|
||||
// Arc::new(Mutex::new(0))
|
||||
// }
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// fn get_arc_mutex(ctx: impl Ctx + ExtractArcMutex) -> Option<Arc<Mutex<Option<OwnedContextImpl>>>> {
|
||||
// ctx.try_arc_mutex()
|
||||
// }
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// async fn set_arc_mutex<T: 'n + 'static>(
|
||||
// ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
|
||||
// // Auto generate for each tagged value type
|
||||
// #[expose]
|
||||
// #[implementations(
|
||||
// Context -> u32,
|
||||
// )]
|
||||
// input: impl Node<Context<'static>, Output = T>,
|
||||
// arc_mutex: Arc<Mutex<Option<OwnedContextImpl>>>,
|
||||
// ) -> T {
|
||||
// let mut new_context = OwnedContextImpl::from(ctx);
|
||||
// new_context.arc_mutex = Some(arc_mutex);
|
||||
// input.eval(new_context.into_context()).await
|
||||
// }
|
||||
|
||||
// // TODO: Discard node + return () for loop if none branch
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// fn set_arc_mutex_value<T>(_ctx: impl Ctx, #[implementations(Arc<Mutex<u32>>)] arc_mutex: Arc<Mutex<T>>, #[implementations(u32)] value: T) -> () {
|
||||
// let mut guard = arc_mutex.lock().unwrap(); // lock the mutex
|
||||
// *guard = value;
|
||||
// }
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// fn get_context(ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync) -> OwnedContextImpl {
|
||||
// OwnedContextImpl::from(ctx)
|
||||
// }
|
||||
|
||||
// #[node_macro::node(category("Loop"))]
|
||||
// fn discard(_ctx: impl Ctx, _input: u32) -> () {}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn is_none<T>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>)] input: Option<T>) -> bool {
|
||||
input.is_none()
|
||||
}
|
||||
|
||||
// #[node_macro::node(category("Debug"))]
|
||||
// fn unwrap<T>(_: impl Ctx, #[implementations(Option<f64>, Option<f32>, Option<u32>, Option<u64>, Option<String>, Option<Vec<u32>>)] input: Option<T>) -> T {
|
||||
// input.unwrap()
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn to_option<T>(_: impl Ctx, boolean: bool, #[implementations(u32)] input: T) -> Option<T> {
|
||||
boolean.then(|| input)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
fn to_usize(_: impl Ctx, u32: u32) -> usize {
|
||||
u32.try_into().unwrap()
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ pub trait Node<'i, Input> {
|
||||
|
||||
/// Get the call argument or output data for the monitor node on the next evaluation after set_introspect_input
|
||||
/// Also returns a boolean of whether the node was evaluated
|
||||
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<Box<dyn std::any::Any + Send + Sync>> {
|
||||
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
log::warn!("Node::introspect not implemented for {}", std::any::type_name::<Self>());
|
||||
None
|
||||
}
|
||||
|
||||
@@ -7,6 +7,88 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// Caches the output of a given Node and acts as a proxy
|
||||
#[derive(Default)]
|
||||
pub struct MonitorMemoNode<T, CachedNode> {
|
||||
// Introspection cache, uses the hash of the nullified context with default var args
|
||||
// cache: Arc<Mutex<std::collections::HashMap<u64, Arc<T>>>>,
|
||||
// Return value cache,
|
||||
cache: Arc<Mutex<Option<(u64, Arc<T>)>>>,
|
||||
node: CachedNode,
|
||||
changed_since_last_eval: Arc<Mutex<bool>>,
|
||||
}
|
||||
impl<'i, I: Hash + 'i + std::fmt::Debug, T: 'static + Clone + Send + Sync, CachedNode: 'i> Node<'i, I> for MonitorMemoNode<T, CachedNode>
|
||||
where
|
||||
CachedNode: for<'any_input> Node<'any_input, I>,
|
||||
for<'a> <CachedNode as Node<'a, I>>::Output: Future<Output = T> + WasmNotSend,
|
||||
{
|
||||
// TODO: This should return a reference to the cached cached_value
|
||||
// but that requires a lot of lifetime magic <- This was suggested by copilot but is pretty accurate xD
|
||||
type Output = DynFuture<'i, T>;
|
||||
// fn eval(&'i self, input: I) -> Self::Output {
|
||||
// let mut hasher = DefaultHasher::new();
|
||||
// input.hash(&mut hasher);
|
||||
// let hash = hasher.finish();
|
||||
|
||||
// if let Some(data) = self.cache.lock().unwrap().get(&hash) {
|
||||
// let cloned_data = (**data).clone();
|
||||
// Box::pin(async move { cloned_data })
|
||||
// } else {
|
||||
// let fut = self.node.eval(input);
|
||||
// let cache = self.cache.clone();
|
||||
// Box::pin(async move {
|
||||
// let value = fut.await;
|
||||
// cache.lock().unwrap().insert(hash, Arc::new(value.clone()));
|
||||
// value
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
// let mut hasher = DefaultHasher::new();
|
||||
// OwnedContextImpl::default().into_context().hash(&mut hasher);
|
||||
// let hash = hasher.finish();
|
||||
// self.cache.lock().unwrap().get(&hash).map(|data| (*data).clone() as Arc<dyn std::any::Any + Send + Sync>)
|
||||
// }
|
||||
fn eval(&'i self, input: I) -> Self::Output {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
input.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
if let Some(data) = self.cache.lock().as_ref().unwrap().as_ref().and_then(|data| (data.0 == hash).then_some(data.1.clone())) {
|
||||
let cloned_data = (*data).clone();
|
||||
Box::pin(async move { cloned_data })
|
||||
} else {
|
||||
let fut = self.node.eval(input);
|
||||
let cache = self.cache.clone();
|
||||
*self.changed_since_last_eval.lock().unwrap() = true;
|
||||
Box::pin(async move {
|
||||
let value = fut.await;
|
||||
*cache.lock().unwrap() = Some((hash, Arc::new(value.clone())));
|
||||
value
|
||||
})
|
||||
}
|
||||
}
|
||||
fn introspect(&self, _introspect_mode: IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
if *self.changed_since_last_eval.lock().unwrap() {
|
||||
*self.changed_since_last_eval.lock().unwrap() = false;
|
||||
Some(self.cache.lock().unwrap().as_ref().expect("Cached data should always be evaluated before introspection").1.clone() as Arc<dyn std::any::Any + Send + Sync>)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, CachedNode> MonitorMemoNode<T, CachedNode> {
|
||||
pub fn new(node: CachedNode) -> MonitorMemoNode<T, CachedNode> {
|
||||
MonitorMemoNode {
|
||||
cache: Default::default(),
|
||||
node,
|
||||
changed_since_last_eval: Arc::new(Mutex::new(true)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Caches the output of a given Node and acts as a proxy
|
||||
#[derive(Default)]
|
||||
pub struct MemoNode<T, CachedNode> {
|
||||
@@ -107,7 +189,7 @@ pub mod impure_memo {
|
||||
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum IntrospectMode {
|
||||
Input,
|
||||
Data,
|
||||
@@ -117,8 +199,8 @@ pub enum IntrospectMode {
|
||||
#[derive(Default)]
|
||||
pub struct MonitorNode<I, O, N> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
input: Arc<Mutex<Option<Box<I>>>>,
|
||||
output: Arc<Mutex<Option<Box<O>>>>,
|
||||
input: Arc<Mutex<Option<Arc<I>>>>,
|
||||
output: Arc<Mutex<Option<Arc<O>>>>,
|
||||
// Gets set to true by the editor when before evaluating the network, then reset when the monitor node is evaluated
|
||||
introspect_input: Arc<Mutex<bool>>,
|
||||
introspect_output: Arc<Mutex<bool>>,
|
||||
@@ -137,12 +219,12 @@ where
|
||||
let output = self.node.eval(input.clone()).await;
|
||||
let mut introspect_input = self.introspect_input.lock().unwrap();
|
||||
if *introspect_input {
|
||||
*self.input.lock().unwrap() = Some(Box::new(input));
|
||||
*self.input.lock().unwrap() = Some(Arc::new(input));
|
||||
*introspect_input = false;
|
||||
}
|
||||
let mut introspect_output = self.introspect_output.lock().unwrap();
|
||||
if *introspect_output {
|
||||
*self.output.lock().unwrap() = Some(Box::new(output.clone()));
|
||||
*self.output.lock().unwrap() = Some(Arc::new(output.clone()));
|
||||
*introspect_output = false;
|
||||
}
|
||||
output
|
||||
@@ -150,10 +232,10 @@ where
|
||||
}
|
||||
|
||||
// After introspecting, the input/output get set to None because the Arc is moved to the editor where it can be directly accessed.
|
||||
fn introspect(&self, introspect_mode: IntrospectMode) -> Option<Box<dyn std::any::Any + Send + Sync>> {
|
||||
fn introspect(&self, introspect_mode: IntrospectMode) -> Option<Arc<dyn std::any::Any + Send + Sync>> {
|
||||
match introspect_mode {
|
||||
IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Box<dyn std::any::Any + Send + Sync>),
|
||||
IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Box<dyn std::any::Any + Send + Sync>),
|
||||
IntrospectMode::Input => self.input.lock().unwrap().take().map(|input| input as Arc<dyn std::any::Any + Send + Sync>),
|
||||
IntrospectMode::Data => self.output.lock().unwrap().take().map(|output| output as Arc<dyn std::any::Any + Send + Sync>),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,8 @@ pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::ne
|
||||
|
||||
pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
pub static NODE_CONTEXT_DEPENDENCY: LazyLock<Mutex<HashMap<String, Vec<crate::ContextDependency>>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -132,7 +134,7 @@ pub type TypeErasedPinned<'n> = Pin<Box<TypeErasedNode<'n>>>;
|
||||
pub type SharedNodeContainer = std::sync::Arc<NodeContainer>;
|
||||
|
||||
pub type NodeConstructor = fn(Vec<SharedNodeContainer>) -> DynFuture<'static, TypeErasedBox<'static>>;
|
||||
pub type MonitorConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>;
|
||||
pub type CacheConstructor = fn(SharedNodeContainer) -> TypeErasedBox<'static>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeContainer {
|
||||
@@ -277,17 +279,24 @@ where
|
||||
type Output = FutureAny<'input>;
|
||||
#[inline]
|
||||
fn eval(&'input self, input: Any<'input>) -> Self::Output {
|
||||
let node_name = std::any::type_name::<N>();
|
||||
let output = |input| {
|
||||
let result = self.node.eval(input);
|
||||
async move { Box::new(result.await) as Any<'input> }
|
||||
};
|
||||
match dyn_any::downcast(input) {
|
||||
Ok(input) => Box::pin(output(*input)),
|
||||
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
|
||||
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, std::any::type_name::<N>()),
|
||||
}
|
||||
}
|
||||
|
||||
fn introspect(&self, introspect_mode: crate::IntrospectMode) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
|
||||
self.node.introspect(introspect_mode)
|
||||
}
|
||||
|
||||
fn set_introspect(&self, introspect_mode: crate::IntrospectMode) {
|
||||
self.node.set_introspect(introspect_mode);
|
||||
}
|
||||
|
||||
fn reset(&self) {
|
||||
self.node.reset();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::registry::Node;
|
||||
use crate::Node;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// This is how we can generically define composition of two nodes.
|
||||
|
||||
@@ -91,5 +91,5 @@ pub type SNI = NodeId;
|
||||
// An input of a compiled protonode, used to reference thumbnails, which are stored on a per input basis
|
||||
pub type CompiledProtonodeInput = (NodeId, usize);
|
||||
|
||||
// Path to the protonode in the document network
|
||||
pub type ProtonodePath = Box<[NodeId]>;
|
||||
// Path to the protonode in the wrapped network (document network prefixed with NodeId(0))
|
||||
pub type ProtonodePath = Vec<NodeId>;
|
||||
|
||||
@@ -22,7 +22,7 @@ async fn instance_on_points<T: Into<GraphicElement> + Default + Send + Clone + '
|
||||
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(("Transformed point", Box::new(transformed_point)));
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for mut instanced in generated_instance.instance_iter() {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
pub mod value;
|
||||
|
||||
use crate::document::value::TaggedValue;
|
||||
use crate::proto::{ConstructionArgs, NodeConstructionArgs, OriginalLocation, ProtoNode};
|
||||
use crate::proto::{ConstructionArgs, NodeConstructionArgs, NodeValueArgs, ProtoNetwork, ProtoNode, UpstreamInputMetadata};
|
||||
use dyn_any::DynAny;
|
||||
use glam::IVec2;
|
||||
use graphene_core::memo::MemoHashGuard;
|
||||
use graphene_core::registry::NODE_CONTEXT_DEPENDENCY;
|
||||
pub use graphene_core::uuid::generate_uuid;
|
||||
use graphene_core::uuid::{CompiledProtonodeInput, NodeId, ProtonodePath, SNI};
|
||||
use graphene_core::{Context, Cow, MemoHash, ProtoNodeIdentifier, Type};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// Utility function for providing a default boolean value to serde.
|
||||
@@ -509,94 +510,170 @@ impl NodeNetwork {
|
||||
|
||||
/// Functions for compiling the network
|
||||
impl NodeNetwork {
|
||||
// Returns a topologically sorted vec of protonodes, as well as metadata extracted during compilation
|
||||
// Returns a topologically sorted vec of vec of protonodes, as well as metadata extracted during compilation
|
||||
// The first index represents the greatest distance to the export
|
||||
// Compiles a network with one export where any scope injections are added the top level network, and the network to run is implemented as a DocumentNodeImplementation::Network
|
||||
// The traversal input is the node which calls the network to be flattened. If it is None, then start from the export.
|
||||
// Every value protonode stores the connector which directly called it, which is used to map the value input to the protonode caller.
|
||||
// Every value input connector is mapped to its caller, and every protonode is mapped to its caller. If there are multiple, then they are compared to ensure it is the same between compilations
|
||||
pub fn flatten(&mut self) -> Result<(Vec<ProtoNode>, Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>, Vec<(ProtonodePath, CompiledProtonodeInput)>), String> {
|
||||
pub fn flatten(
|
||||
&mut self,
|
||||
) -> Result<
|
||||
(
|
||||
ProtoNetwork,
|
||||
Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>,
|
||||
Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
// These three arrays are stored in parallel
|
||||
let mut protonetwork = Vec::new();
|
||||
let mut value_connectors = Vec::new();
|
||||
let mut protonode_paths = Vec::new();
|
||||
let mut calling_protonodes = HashMap::new();
|
||||
|
||||
// This function creates a flattened network with populated original location fields but unmapped inputs
|
||||
// This function creates a topologically flattened network with populated original location fields but unmapped inputs
|
||||
// The input to flattened protonode hashmap is used to map the inputs
|
||||
self.traverse_input(
|
||||
&mut protonetwork,
|
||||
&mut value_connectors,
|
||||
&mut protonode_paths,
|
||||
&mut calling_protonodes,
|
||||
&mut HashMap::new(),
|
||||
AbsoluteInputConnector::traversal_start(),
|
||||
(0, 0),
|
||||
);
|
||||
let mut protonode_indices = HashMap::new();
|
||||
self.traverse_input(&mut protonetwork, &mut HashMap::new(), &mut protonode_indices, AbsoluteInputConnector::traversal_start(), None);
|
||||
|
||||
let mut generated_snis = HashSet::new();
|
||||
// If a node with the same sni is reached, then its original location metadata must be added to the one at the higher vec index
|
||||
// The index will always be a ProtonodeEntry::Protonode
|
||||
let mut generated_snis_to_index = HashMap::new();
|
||||
// Generate SNI's. This gets called after all node inputs are replaced with their indices
|
||||
for protonode_index in (0..protonetwork.len()).rev() {
|
||||
let protonode = protonetwork.get_mut(protonode_index).unwrap();
|
||||
if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs: input_snis, .. }) = &protonode.construction_args {
|
||||
for input_sni in input_snis {
|
||||
assert_ne!(
|
||||
*input_sni,
|
||||
NodeId(0),
|
||||
"All inputs should be mapped to a stable node index, and the calling nodes inputs should be updated"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = rustc_hash::FxHasher::default();
|
||||
protonode.construction_args.hash(&mut hasher);
|
||||
let mut stable_node_id = NodeId(hasher.finish());
|
||||
// The stable node index must be unique for every protonode. If it has the same hash as another protonode, continue hashing itself
|
||||
// For example two cache nodes connected to a Context getter node have two cache different values, even though the stable node id is the same.
|
||||
while !generated_snis.insert(stable_node_id) {
|
||||
stable_node_id.hash(&mut hasher);
|
||||
stable_node_id = NodeId(hasher.finish());
|
||||
}
|
||||
|
||||
protonode.stable_node_id = stable_node_id;
|
||||
for (calling_node_index, input_index) in calling_protonodes.get(&protonode_index).unwrap() {
|
||||
match &mut protonetwork.get_mut(*calling_node_index).unwrap().construction_args {
|
||||
ConstructionArgs::Nodes(nodes) => {
|
||||
*nodes.inputs.get_mut(*input_index).unwrap() = stable_node_id;
|
||||
for protonode_index in 0..protonetwork.len() {
|
||||
let ProtonodeEntry::Protonode(protonode) = protonetwork.get_mut(protonode_index).unwrap() else {
|
||||
panic!("No protonode can be deduplicated during flattening");
|
||||
};
|
||||
// Generate context dependencies. If None, then it is a value node and does not require nullification
|
||||
let mut protonode_context_dependencies = None;
|
||||
if let ConstructionArgs::Nodes(NodeConstructionArgs { inputs, context_dependencies, .. }) = &mut protonode.construction_args {
|
||||
for upstream_metadata in inputs.iter() {
|
||||
let Some(upstream_metadata) = upstream_metadata else {
|
||||
panic!("All inputs should be when the upstream SNI was generated");
|
||||
};
|
||||
for upstream_dependency in upstream_metadata.context_dependencies.iter().flatten() {
|
||||
if !context_dependencies.contains(upstream_dependency) {
|
||||
context_dependencies.push(upstream_dependency.clone());
|
||||
}
|
||||
}
|
||||
// TODO: Implement for extract
|
||||
_ => unreachable!(),
|
||||
}
|
||||
// The context_dependencies are now the union of all inputs and the dependencies of the protonode. Set the dependencies of each input to the difference, which represents the data to nullify
|
||||
for upstream_metadata in inputs.iter_mut() {
|
||||
let Some(upstream_metadata) = upstream_metadata else {
|
||||
panic!("All inputs should be when the upstream SNI was generated");
|
||||
};
|
||||
match upstream_metadata.context_dependencies.as_ref() {
|
||||
Some(upstream_dependencies) => {
|
||||
upstream_metadata.context_dependencies = Some(
|
||||
context_dependencies
|
||||
.iter()
|
||||
.filter(|protonode_dependency| !upstream_dependencies.contains(protonode_dependency))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
// If none then the upstream node is a Value node, so do not nullify the context
|
||||
None => upstream_metadata.context_dependencies = Some(Vec::new()),
|
||||
}
|
||||
}
|
||||
protonode_context_dependencies = Some(context_dependencies.clone());
|
||||
}
|
||||
|
||||
protonode.generate_stable_node_id();
|
||||
let current_stable_node_id = protonode.stable_node_id;
|
||||
|
||||
// If the stable node id is the same as a previous node, then deduplicate
|
||||
let callers = if let Some(upstream_index) = generated_snis_to_index.get(&protonode.stable_node_id) {
|
||||
let ProtonodeEntry::Protonode(deduplicated_protonode) = std::mem::replace(&mut protonetwork[protonode_index], ProtonodeEntry::Deduplicated(*upstream_index)) else {
|
||||
panic!("Reached protonode must not be deduplicated");
|
||||
};
|
||||
let ProtonodeEntry::Protonode(upstream_protonode) = &mut protonetwork[*upstream_index] else {
|
||||
panic!("Upstream protonode must not be deduplicated");
|
||||
};
|
||||
match deduplicated_protonode.construction_args {
|
||||
ConstructionArgs::Value(node_value_args) => {
|
||||
let ConstructionArgs::Value(upstream_value_args) = &mut upstream_protonode.construction_args else {
|
||||
panic!("Upstream protonode must match current protonode construction args");
|
||||
};
|
||||
upstream_value_args.connector_paths.extend(node_value_args.connector_paths);
|
||||
}
|
||||
ConstructionArgs::Nodes(node_construction_args) => {
|
||||
let ConstructionArgs::Nodes(upstream_value_args) = &mut upstream_protonode.construction_args else {
|
||||
panic!("Upstream protonode must match current protonode construction args");
|
||||
};
|
||||
upstream_value_args.node_paths.extend(node_construction_args.node_paths);
|
||||
// The dependencies of the deduplicated node and the upstream node are the same because all inputs are the same
|
||||
}
|
||||
ConstructionArgs::Inline(_) => todo!(),
|
||||
}
|
||||
// Set the caller of the upstream node to be the minimum of all deduplicated nodes and itself
|
||||
upstream_protonode.caller = deduplicated_protonode.callers.iter().chain(upstream_protonode.caller.iter()).min().cloned();
|
||||
deduplicated_protonode.callers
|
||||
} else {
|
||||
generated_snis_to_index.insert(protonode.stable_node_id, protonode_index);
|
||||
protonode.caller = protonode.callers.iter().min().cloned();
|
||||
std::mem::take(&mut protonode.callers)
|
||||
};
|
||||
|
||||
// This runs for all protonodes
|
||||
for (caller_path, input_index) in callers {
|
||||
let caller_index = protonode_indices[&caller_path];
|
||||
let ProtonodeEntry::Protonode(caller_protonode) = &mut protonetwork[caller_index] else {
|
||||
panic!("Downstream caller cannot be deduplicated");
|
||||
};
|
||||
match &mut caller_protonode.construction_args {
|
||||
ConstructionArgs::Nodes(nodes) => {
|
||||
assert!(caller_index > protonode_index, "Caller index must be higher than current index");
|
||||
let input_metadata: &mut Option<UpstreamInputMetadata> = &mut nodes.inputs[input_index];
|
||||
if input_metadata.is_none() {
|
||||
*input_metadata = Some(UpstreamInputMetadata {
|
||||
input_sni: current_stable_node_id,
|
||||
context_dependencies: protonode_context_dependencies.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Value node cannot be a caller
|
||||
ConstructionArgs::Value(_) => unreachable!(),
|
||||
ConstructionArgs::Inline(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do another traversal now that the caller SNI have been generated to collect metadata for the editor
|
||||
// Do another traversal now that the metadata has been accumulated after deduplication
|
||||
// This includes the caller of all absolute value connections which have a NodeInput::Value, as well as the caller for each protonode
|
||||
let mut value_connector_callers = Vec::new();
|
||||
let mut protonode_callers = Vec::new();
|
||||
// Collect caller ids into a separate vec so that the pronetwork can be mutably iterated over to take the connector/node paths rather than cloning
|
||||
let calling_protonode_ids = protonetwork
|
||||
.iter()
|
||||
.map(|entry| match entry {
|
||||
ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id,
|
||||
ProtonodeEntry::Deduplicated(upstream_protonode_index) => {
|
||||
let ProtonodeEntry::Protonode(proto_node) = &protonetwork[*upstream_protonode_index] else {
|
||||
panic!("Upstream protonode index must not be dedeuplicated");
|
||||
};
|
||||
proto_node.stable_node_id
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (protonode_index, (value_connector, protonode_path)) in value_connectors.iter_mut().zip(protonode_paths.iter_mut()).enumerate().rev() {
|
||||
let callers = calling_protonodes.get(&protonode_index).unwrap();
|
||||
|
||||
let &(min_protonode_index, input_index) = callers.iter().min().unwrap();
|
||||
|
||||
let protonode_id = protonetwork[min_protonode_index].stable_node_id;
|
||||
|
||||
if let Some(value_connector) = value_connector.take() {
|
||||
value_connector_callers.push((value_connector, (protonode_id, input_index)));
|
||||
}
|
||||
|
||||
if let Some(protonode_path) = protonode_path.take() {
|
||||
protonode_callers.push((protonode_path, (protonode_id, input_index)));
|
||||
for protonode_entry in &mut protonetwork {
|
||||
if let ProtonodeEntry::Protonode(protonode) = protonode_entry {
|
||||
if let Some((caller_path, caller_input_index)) = protonode.caller.as_ref() {
|
||||
let caller_index = protonode_indices[caller_path];
|
||||
match &mut protonode.construction_args {
|
||||
ConstructionArgs::Value(node_value_args) => {
|
||||
value_connector_callers.push((std::mem::take(&mut node_value_args.connector_paths), (calling_protonode_ids[caller_index], *caller_input_index)))
|
||||
}
|
||||
ConstructionArgs::Nodes(node_construction_args) => {
|
||||
protonode_callers.push((std::mem::take(&mut node_construction_args.node_paths), (calling_protonode_ids[caller_index], *caller_input_index)))
|
||||
}
|
||||
ConstructionArgs::Inline(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut existing_ids = HashSet::new();
|
||||
// Value nodes can be deduplicated if they share the same hash, since they do not depend on the input
|
||||
let protonetwork = protonetwork
|
||||
.into_iter()
|
||||
.filter(|protonode| !(matches!(protonode.construction_args, ConstructionArgs::Value(_)) && !existing_ids.insert(protonode.stable_node_id)))
|
||||
.collect();
|
||||
Ok((protonetwork, value_connector_callers, protonode_callers))
|
||||
log::debug!("protonetwork: {:?}", protonetwork);
|
||||
Ok((ProtoNetwork::from_vec(protonetwork), value_connector_callers, protonode_callers))
|
||||
}
|
||||
|
||||
fn get_input_from_absolute_connector(&mut self, traversal_input: &AbsoluteInputConnector) -> Option<&mut NodeInput> {
|
||||
@@ -632,39 +709,32 @@ impl NodeNetwork {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Performs a recursive graph traversal starting from all protonode inputs and the root export until reaching the next protonode or value input.
|
||||
// Automatically inserts value nodes by moving the value from the current network
|
||||
|
||||
// Performs a recursive graph traversal starting from the root export across all node inputs
|
||||
// Inserts values into the protonetwork by moving the value from the current network
|
||||
//
|
||||
// protonetwork - The topologically sorted flattened protonetwork. The caller of each protonode is at a lower index. The output of the network is the first protonode
|
||||
//
|
||||
// calling protonodes - anytime a protonode is reached, the caller is added as a value with (caller protonetwork index, caller input index).
|
||||
// This is necessary so the calling protonodes input can be looked up and mapped when generating SNI's
|
||||
// None indicates that the caller is the traversal start, which is skipped
|
||||
//
|
||||
// Protonode indices - mapping of protonode path to its index in the protonetwork, updated when inserting a protonode
|
||||
//
|
||||
// Traversal input - current connector to traverse over. added to downstream_calling_inputs every time the function is called.
|
||||
//
|
||||
// downstream_calling_inputs - tracks all inputs reached during traversal
|
||||
//
|
||||
// any_input_to_downstream_protonode_input - used by the runtime/javascript to get the calling protonode input from any input connector.
|
||||
// When a protonode is reached, each input connector in downstream_calling_inputs, is looked up in `any_input_to_downstream_protonode_input`. If there is an entry,
|
||||
// Then the paths are compared, and the greater one is chosen using stable ordering.
|
||||
// This is to ensure a constant mapping, since an export for instance can have multiple calling nodes in the parent network
|
||||
//
|
||||
// any_input_to_upstream_protonode - used by the runtime to get the node to evaluate for any given input connector.
|
||||
// Each input connector is inserted into any_input_to_upstream_protonode with the value being the path to the reached protonode.
|
||||
// It doesnt matter if its overwritten since it must have previously pointed to the same protonode anyways
|
||||
//
|
||||
pub fn traverse_input(
|
||||
&mut self,
|
||||
protonetwork: &mut Vec<ProtoNode>, // Flattened node id to protonode, stable node ids can only be generated once the network is fully flattened, since it runs in reverse
|
||||
value_connector: &mut Vec<Option<AbsoluteInputConnector>>,
|
||||
protonode_path: &mut Vec<Option<ProtonodePath>>,
|
||||
calling_protonodes: &mut HashMap<usize, Vec<(usize, usize)>>, // A mapping of protonode path to all (flattened network indices, their input index) that called the protonode, used during SNI generation to remap inputs
|
||||
protonode_indices: &mut HashMap<Vec<SNI>, usize>, // Mapping of protonode path to its index in the flattened protonetwork
|
||||
protonetwork: &mut Vec<ProtonodeEntry>, // None represents a deduplicated value node
|
||||
// Every time a value input is reached, it is added to a mapping so if it reached again, it can be moved to the end of the protonetwork
|
||||
value_protonode_indices: &mut HashMap<AbsoluteInputConnector, usize>,
|
||||
// Every time a protonode is reached, is it added to a mapping so if it reached again, it can be moved to the end of the protonetwork
|
||||
protonode_indices: &mut HashMap<ProtonodePath, usize>,
|
||||
// The original location of the current traversal
|
||||
traversal_input: AbsoluteInputConnector,
|
||||
// Protonode index, input index
|
||||
traversal_start: (usize, usize),
|
||||
// The protnode input which started the traversal. None if it is called from the root export
|
||||
traversal_start: Option<(ProtonodePath, usize)>,
|
||||
) {
|
||||
let network_path = &traversal_input.network_path;
|
||||
|
||||
@@ -730,90 +800,111 @@ impl NodeNetwork {
|
||||
network_path: upstream_node_path.clone(),
|
||||
connector: InputConnector::Export(output_index),
|
||||
};
|
||||
self.traverse_input(protonetwork, value_connector, protonode_path, calling_protonodes, protonode_indices, traversal_input, traversal_start);
|
||||
self.traverse_input(protonetwork, value_protonode_indices, protonode_indices, traversal_input, traversal_start);
|
||||
}
|
||||
DocumentNodeImplementation::ProtoNode(protonode_id) => {
|
||||
// Only insert the protonode if it has not previously been inserted
|
||||
// Do not insert the protonode into the proto network or traverse over inputs if its already visited
|
||||
let reached_protonode_index = match protonode_indices.get(&upstream_node_path) {
|
||||
// The protonode has already been inserted, return its index
|
||||
Some(reached_protonode_index) => *reached_protonode_index,
|
||||
// Insert the protonode and traverse over inputs
|
||||
None => {
|
||||
let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs {
|
||||
identifier: protonode_id.clone(),
|
||||
inputs: vec![NodeId(0); upstream_document_node.inputs.len()],
|
||||
});
|
||||
let protonode = ProtoNode {
|
||||
construction_args,
|
||||
// All protonodes take Context by default
|
||||
input: concrete!(Context),
|
||||
original_location: OriginalLocation {
|
||||
protonode_path: upstream_node_path.clone().into(),
|
||||
send_types_to_editor: true,
|
||||
},
|
||||
stable_node_id: NodeId(0),
|
||||
// Check if the protonode has already been reached
|
||||
let reached_protonode = match protonode_indices.get(&upstream_node_path) {
|
||||
// The protonode has already been inserted, add the caller and node path to its metadata
|
||||
Some(previous_protonode_index) => {
|
||||
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[*previous_protonode_index] else {
|
||||
panic!("Previously inserted protonode must exist at mapped protonode index");
|
||||
};
|
||||
let new_protonode_index = protonetwork.len();
|
||||
protonode_indices.insert(upstream_node_path.clone(), new_protonode_index);
|
||||
protonetwork.push(protonode);
|
||||
value_connector.push(None);
|
||||
protonode_path.push(Some(upstream_node_path.into_boxed_slice()));
|
||||
// Iterate over all upstream inputs, which will map the inputs to the index of the connected protonode
|
||||
protonode
|
||||
}
|
||||
// Construct the protonode and traverse over inputs
|
||||
None => {
|
||||
let number_of_inputs = upstream_document_node.inputs.len();
|
||||
let identifier = protonode_id.clone();
|
||||
for input_index in 0..upstream_document_node.inputs.len() {
|
||||
self.traverse_input(
|
||||
protonetwork,
|
||||
value_connector,
|
||||
protonode_path,
|
||||
calling_protonodes,
|
||||
value_protonode_indices,
|
||||
protonode_indices,
|
||||
AbsoluteInputConnector {
|
||||
network_path: network_path.clone(),
|
||||
connector: InputConnector::node(upstream_node_id, input_index),
|
||||
},
|
||||
(new_protonode_index, input_index),
|
||||
Some((upstream_node_path.clone(), input_index)),
|
||||
);
|
||||
}
|
||||
new_protonode_index
|
||||
let context_dependencies = NODE_CONTEXT_DEPENDENCY.lock().unwrap().get(identifier.name.as_ref()).cloned().unwrap_or_default();
|
||||
let construction_args = ConstructionArgs::Nodes(NodeConstructionArgs {
|
||||
identifier,
|
||||
inputs: vec![None; number_of_inputs],
|
||||
context_dependencies,
|
||||
node_paths: Vec::new(),
|
||||
});
|
||||
let protonode = ProtoNode {
|
||||
construction_args,
|
||||
// All protonodes take Context by default
|
||||
input: concrete!(Context),
|
||||
stable_node_id: NodeId(0),
|
||||
callers: Vec::new(),
|
||||
caller: None,
|
||||
};
|
||||
let new_protonode_index = protonetwork.len();
|
||||
protonetwork.push(ProtonodeEntry::Protonode(protonode));
|
||||
protonode_indices.insert(upstream_node_path.clone(), new_protonode_index);
|
||||
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
|
||||
panic!("Inserted protonode must exist at new_protonode_index");
|
||||
};
|
||||
protonode
|
||||
}
|
||||
};
|
||||
calling_protonodes.entry(reached_protonode_index).or_insert_with(Vec::new).push(traversal_start);
|
||||
// Only add the traversal start if it is not the root export
|
||||
if let Some(traversal_start) = traversal_start {
|
||||
reached_protonode.callers.push(traversal_start);
|
||||
}
|
||||
let ConstructionArgs::Nodes(args) = &mut reached_protonode.construction_args else {
|
||||
panic!("Reached protonode must have Nodes construction args");
|
||||
};
|
||||
args.node_paths.push(upstream_node_path);
|
||||
}
|
||||
DocumentNodeImplementation::Extract => todo!(),
|
||||
}
|
||||
}
|
||||
NodeInput::Value { tagged_value, .. } => {
|
||||
// Deduplication of value nodes based on their tagged value, since they do not depend on the Context
|
||||
//
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = rustc_hash::FxHasher::default();
|
||||
tagged_value.hash(&mut hasher);
|
||||
let value_node_path = vec![NodeId(hasher.finish())];
|
||||
|
||||
// Only insert the value protonode if it has not previously been inserted
|
||||
let value_protonode_index = match protonode_indices.get(&value_node_path) {
|
||||
// The value input has already been inserted, return it the existing value nodes index
|
||||
Some(value_protonode_index) => *value_protonode_index,
|
||||
// Check if the protonode has already been reached
|
||||
let reached_protonode = match value_protonode_indices.get(&traversal_input) {
|
||||
// The protonode has already been inserted, add the caller and node path to its metadata
|
||||
Some(previous_protonode_index) => {
|
||||
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[*previous_protonode_index] else {
|
||||
panic!("Previously inserted protonode must exist at mapped protonode index");
|
||||
};
|
||||
protonode
|
||||
}
|
||||
// Insert the protonode and traverse over inputs
|
||||
None => {
|
||||
let protonode = ProtoNode {
|
||||
construction_args: ConstructionArgs::Value(std::mem::replace(tagged_value, TaggedValue::None.into())),
|
||||
let value_protonode = ProtoNode {
|
||||
construction_args: ConstructionArgs::Value(NodeValueArgs {
|
||||
value: std::mem::replace(tagged_value, TaggedValue::None.into()),
|
||||
connector_paths: Vec::new(),
|
||||
}),
|
||||
input: concrete!(Context), // Could be ()
|
||||
original_location: OriginalLocation {
|
||||
protonode_path: Vec::new().into(),
|
||||
send_types_to_editor: false,
|
||||
},
|
||||
stable_node_id: NodeId(0),
|
||||
callers: Vec::new(),
|
||||
caller: None,
|
||||
};
|
||||
let new_protonode_index = protonetwork.len();
|
||||
protonode_indices.insert(value_node_path.clone(), new_protonode_index);
|
||||
protonetwork.push(protonode);
|
||||
value_connector.push(Some(traversal_input));
|
||||
protonode_path.push(None);
|
||||
new_protonode_index
|
||||
protonetwork.push(ProtonodeEntry::Protonode(value_protonode));
|
||||
value_protonode_indices.insert(traversal_input.clone(), new_protonode_index);
|
||||
|
||||
let ProtonodeEntry::Protonode(protonode) = &mut protonetwork[new_protonode_index] else {
|
||||
panic!("Previously inserted protonode must exist at mapped protonode index");
|
||||
};
|
||||
protonode
|
||||
}
|
||||
};
|
||||
calling_protonodes.entry(value_protonode_index).or_insert_with(Vec::new).push(traversal_start);
|
||||
|
||||
// Only add the traversal start if it is not the root export
|
||||
if let Some(traversal_start) = traversal_start {
|
||||
reached_protonode.callers.push(traversal_start);
|
||||
}
|
||||
let ConstructionArgs::Value(args) = &mut reached_protonode.construction_args else {
|
||||
panic!("Reached protonode must have Nodes construction args");
|
||||
};
|
||||
args.connector_paths.push(traversal_input);
|
||||
}
|
||||
// Continue traversal
|
||||
NodeInput::Network { import_index, .. } => {
|
||||
@@ -823,35 +914,14 @@ impl NodeNetwork {
|
||||
network_path: encapsulating_network_path,
|
||||
connector: InputConnector::node(node_id, *import_index),
|
||||
};
|
||||
self.traverse_input(protonetwork, value_connector, protonode_path, calling_protonodes, protonode_indices, traversal_input, traversal_start);
|
||||
self.traverse_input(protonetwork, value_protonode_indices, protonode_indices, traversal_input, traversal_start);
|
||||
}
|
||||
NodeInput::Scope(_cow) => unreachable!(),
|
||||
NodeInput::Reflection(_document_node_metadata) => unreachable!(),
|
||||
NodeInput::Inline(_inline_rust) => todo!(),
|
||||
NodeInput::Scope(_) => unreachable!(),
|
||||
NodeInput::Reflection(_) => unreachable!(),
|
||||
NodeInput::Inline(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn collect_downstream_metadata(
|
||||
// reached_protonode_index: usize,
|
||||
// calling_protonodes: &mut HashMap<usize, Vec<(usize, usize)>>,
|
||||
// protonode_indices: &mut HashMap<Vec<SNI>, usize>,
|
||||
// downstream_calling_inputs: Vec<AbsoluteInputConnector>,
|
||||
// ) {
|
||||
// // Map the first downstream calling node input (which is traversed for every node input) to the reached protonode
|
||||
// let downstream_protonode_caller = downstream_calling_inputs[0].clone();
|
||||
|
||||
// match &downstream_protonode_caller.connector {
|
||||
// InputConnector::Node { node_id, input_index } => {
|
||||
// // The calling protonode has already been added to the flattened network, so it can be looked up by index and the reached node can be mapped to it
|
||||
// let mut calling_protonode_path = downstream_protonode_caller.network_path.clone();
|
||||
// calling_protonode_path.push(*node_id);
|
||||
// let calling_protonode_index = protonode_indices[&calling_protonode_path];
|
||||
|
||||
// }
|
||||
// InputConnector::Export(_) => {}
|
||||
// }
|
||||
// }
|
||||
|
||||
/// Converts the `DocumentNode`s with a `DocumentNodeImplementation::Extract` into a `ClonedNode` that returns
|
||||
/// the `DocumentNode` specified by the single `NodeInput::Node`.
|
||||
/// The referenced node is removed from the network, and any `NodeInput::Node`s used by the referenced node are replaced with a generically typed network input.
|
||||
@@ -898,11 +968,17 @@ impl NodeNetwork {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProtonodeEntry {
|
||||
Protonode(ProtoNode),
|
||||
// If deduplicated, then any upstream node which this node previously called needs to map to the new protonode
|
||||
Deduplicated(usize),
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CompilationMetadata {
|
||||
// Stored for every value input in the compiled network
|
||||
pub protonode_callers_for_value: Vec<(AbsoluteInputConnector, CompiledProtonodeInput)>,
|
||||
pub protonode_caller_for_values: Vec<(Vec<AbsoluteInputConnector>, CompiledProtonodeInput)>,
|
||||
// Stored for every protonode in the compiled network
|
||||
pub protonode_callers_for_node: Vec<(ProtonodePath, CompiledProtonodeInput)>,
|
||||
pub protonode_caller_for_nodes: Vec<(Vec<ProtonodePath>, CompiledProtonodeInput)>,
|
||||
pub types_to_add: Vec<(SNI, Vec<Type>)>,
|
||||
pub types_to_remove: Vec<(SNI, usize)>,
|
||||
}
|
||||
@@ -970,7 +1046,7 @@ pub struct AbsoluteOutputConnector {
|
||||
}
|
||||
|
||||
/// Represents an output connector
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum OutputConnector {
|
||||
#[serde(rename = "node")]
|
||||
Node {
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use super::DocumentNode;
|
||||
use crate::proto::{Any as DAny, FutureAny};
|
||||
use crate::wasm_application_io::WasmEditorApi;
|
||||
use crate::wasm_application_io::WasmApplicationIoValue;
|
||||
use dyn_any::DynAny;
|
||||
pub use dyn_any::StaticType;
|
||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use graphene_application_io::SurfaceFrame;
|
||||
use graphene_brush::brush_cache::BrushCache;
|
||||
use graphene_brush::brush_stroke::BrushStroke;
|
||||
use graphene_core::raster_types::CPU;
|
||||
use graphene_core::raster_types::{CPU, GPU};
|
||||
use graphene_core::transform::ReferencePoint;
|
||||
use graphene_core::uuid::NodeId;
|
||||
use graphene_core::vector::style::Fill;
|
||||
use graphene_core::{Color, MemoHash, Node, Type};
|
||||
use graphene_svg_renderer::RenderMetadata;
|
||||
use graphene_svg_renderer::{GraphicElementRendered, RenderMetadata};
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
@@ -32,10 +32,9 @@ macro_rules! tagged_value {
|
||||
$( $(#[$meta] ) *$identifier( $ty ), )*
|
||||
RenderOutput(RenderOutput),
|
||||
SurfaceFrame(SurfaceFrame),
|
||||
#[serde(skip)]
|
||||
EditorApi(Arc<WasmEditorApi>)
|
||||
}
|
||||
|
||||
|
||||
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
impl Hash for TaggedValue {
|
||||
@@ -46,7 +45,6 @@ macro_rules! tagged_value {
|
||||
$( Self::$identifier(x) => {x.hash(state)}),*
|
||||
Self::RenderOutput(x) => x.hash(state),
|
||||
Self::SurfaceFrame(x) => x.hash(state),
|
||||
Self::EditorApi(x) => x.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +56,6 @@ macro_rules! tagged_value {
|
||||
$( Self::$identifier(x) => Box::new(x), )*
|
||||
Self::RenderOutput(x) => Box::new(x),
|
||||
Self::SurfaceFrame(x) => Box::new(x),
|
||||
Self::EditorApi(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
/// Converts to a Arc<dyn Any + Send + Sync + 'static>
|
||||
@@ -68,7 +65,6 @@ macro_rules! tagged_value {
|
||||
$( Self::$identifier(x) => Arc::new(x), )*
|
||||
Self::RenderOutput(x) => Arc::new(x),
|
||||
Self::SurfaceFrame(x) => Arc::new(x),
|
||||
Self::EditorApi(x) => Arc::new(x),
|
||||
}
|
||||
}
|
||||
/// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
|
||||
@@ -78,7 +74,6 @@ macro_rules! tagged_value {
|
||||
$( Self::$identifier(_) => concrete!($ty), )*
|
||||
Self::RenderOutput(_) => concrete!(RenderOutput),
|
||||
Self::SurfaceFrame(_) => concrete!(SurfaceFrame),
|
||||
Self::EditorApi(_) => concrete!(&WasmEditorApi)
|
||||
}
|
||||
}
|
||||
/// Attempts to downcast the dynamic type to a tagged value
|
||||
@@ -115,7 +110,6 @@ macro_rules! tagged_value {
|
||||
$(TaggedValue::$identifier(value) => {any.downcast_ref::<$ty>().map_or(false, |v| v==value)}, )*
|
||||
TaggedValue::RenderOutput(value) => any.downcast_ref::<RenderOutput>().map_or(false, |v| v==value),
|
||||
TaggedValue::SurfaceFrame(value) => any.downcast_ref::<SurfaceFrame>().map_or(false, |v| v==value),
|
||||
TaggedValue::EditorApi(value) => any.downcast_ref::<Arc<WasmEditorApi>>().map_or(false, |v| v==value),
|
||||
}
|
||||
}
|
||||
pub fn from_type(input: &Type) -> Option<Self> {
|
||||
@@ -258,6 +252,9 @@ tagged_value! {
|
||||
ReferencePoint(graphene_core::transform::ReferencePoint),
|
||||
CentroidType(graphene_core::vector::misc::CentroidType),
|
||||
BooleanOperation(graphene_path_bool::BooleanOperation),
|
||||
EditorMetadata(EditorMetadata),
|
||||
#[serde(skip)]
|
||||
ApplicationIo(Arc<WasmApplicationIoValue>),
|
||||
}
|
||||
|
||||
impl TaggedValue {
|
||||
@@ -382,18 +379,6 @@ impl TaggedValue {
|
||||
_ => panic!("Passed value is not of type u32"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_renderable<'a>(value: &'a TaggedValue) -> Option<&'a dyn graphene_svg_renderer::GraphicElementRendered> {
|
||||
match value {
|
||||
TaggedValue::VectorData(v) => Some(v),
|
||||
TaggedValue::RasterData(r) => Some(r),
|
||||
TaggedValue::GraphicElement(e) => Some(e),
|
||||
TaggedValue::GraphicGroup(g) => Some(g),
|
||||
TaggedValue::ArtboardGroup(a) => Some(a),
|
||||
TaggedValue::Artboard(a) => Some(a),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TaggedValue {
|
||||
@@ -441,6 +426,8 @@ impl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RenderOutput {
|
||||
pub data: RenderOutputType,
|
||||
@@ -460,6 +447,47 @@ impl Hash for RenderOutput {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderOutput {
|
||||
fn default() -> Self {
|
||||
RenderOutput {
|
||||
data: RenderOutputType::Image(Vec::new()),
|
||||
metadata: RenderMetadata::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Passed as a scope input
|
||||
#[derive(Clone, Debug, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct EditorMetadata {
|
||||
// pub imaginate_hostname: String,
|
||||
pub use_vello: bool,
|
||||
pub hide_artboards: bool,
|
||||
// If exporting, hide the artboard name and do not collect metadata
|
||||
pub for_export: bool,
|
||||
pub view_mode: graphene_core::vector::style::ViewMode,
|
||||
pub transform_to_viewport: bool,
|
||||
}
|
||||
|
||||
unsafe impl dyn_any::StaticType for EditorMetadata {
|
||||
type Static = EditorMetadata;
|
||||
}
|
||||
|
||||
impl Default for EditorMetadata {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// imaginate_hostname: "http://localhost:7860/".into(),
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use_vello: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use_vello: true,
|
||||
hide_artboards: false,
|
||||
for_export: false,
|
||||
view_mode: graphene_core::vector::style::ViewMode::Normal,
|
||||
transform_to_viewport: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)
|
||||
trait FakeHash {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
|
||||
@@ -509,3 +537,47 @@ mod fake_hash {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! thumbnail_render {
|
||||
( $( $ty:ty ),* $(,)? ) => {
|
||||
pub fn render_thumbnail_if_change(new_value: &Arc<dyn std::any::Any + Send + Sync>, old_value: Option<&Arc<dyn std::any::Any + Send + Sync>>) -> ThumbnailRenderResult {
|
||||
$(
|
||||
if let Some(new_value) = new_value.downcast_ref::<$ty>() {
|
||||
match old_value {
|
||||
None => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail()),
|
||||
Some(old_value) => {
|
||||
if let Some(old_value) = old_value.downcast_ref::<$ty>() {
|
||||
match new_value == old_value {
|
||||
true => return ThumbnailRenderResult::NoChange,
|
||||
false => return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail())
|
||||
}
|
||||
} else {
|
||||
return ThumbnailRenderResult::UpdateThumbnail(new_value.render_thumbnail())
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
)*
|
||||
return ThumbnailRenderResult::ClearThumbnail;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
thumbnail_render! {
|
||||
graphene_core::GraphicGroupTable,
|
||||
graphene_core::vector::VectorDataTable,
|
||||
graphene_core::Artboard,
|
||||
graphene_core::ArtboardGroupTable,
|
||||
graphene_core::raster_types::RasterDataTable<CPU>,
|
||||
graphene_core::raster_types::RasterDataTable<GPU>,
|
||||
graphene_core::GraphicElement,
|
||||
Option<Color>,
|
||||
Vec<Color>,
|
||||
}
|
||||
|
||||
pub enum ThumbnailRenderResult {
|
||||
NoChange,
|
||||
// Cleared if there is an error or the data could not be rendered
|
||||
ClearThumbnail,
|
||||
UpdateThumbnail(String),
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::document::{InlineRust, value};
|
||||
use crate::document::{AbsoluteInputConnector, InlineRust, ProtonodeEntry, value};
|
||||
pub use graphene_core::registry::*;
|
||||
use graphene_core::uuid::{NodeId, ProtonodePath, SNI};
|
||||
use graphene_core::*;
|
||||
@@ -6,18 +6,43 @@ use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
|
||||
// #[derive(Debug, Default, PartialEq, Clone, Hash, Eq, serde::Serialize, serde::Deserialize)]
|
||||
// /// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network.
|
||||
// pub struct ProtoNetwork {
|
||||
// // TODO: remove this since it seems to be unused?
|
||||
// // Should a proto Network even allow inputs? Don't think so
|
||||
// pub inputs: Vec<NodeId>,
|
||||
// /// The node ID that provides the output. This node is then responsible for calling the rest of the graph.
|
||||
// pub output: NodeId,
|
||||
// /// A list of nodes stored in a Vec to allow for sorting.
|
||||
// pub nodes: Vec<(NodeId, ProtoNode)>,
|
||||
// }
|
||||
#[derive(Debug, Default)]
|
||||
/// A list of [`ProtoNode`]s, which is an intermediate step between the [`crate::document::NodeNetwork`] and the `BorrowTree` containing a single flattened network.
|
||||
pub struct ProtoNetwork {
|
||||
/// A list of nodes stored in a Vec to allow for sorting.
|
||||
nodes: Vec<ProtonodeEntry>,
|
||||
/// The most downstream node in the protonetwork
|
||||
pub output: NodeId,
|
||||
}
|
||||
|
||||
impl ProtoNetwork {
|
||||
pub fn from_vec(nodes: Vec<ProtonodeEntry>) -> Self {
|
||||
let last_entry = nodes.last().expect("Cannot compile empty protonetwork");
|
||||
let output = match last_entry {
|
||||
ProtonodeEntry::Protonode(proto_node) => proto_node.stable_node_id,
|
||||
ProtonodeEntry::Deduplicated(deduplicated_index) => {
|
||||
let ProtonodeEntry::Protonode(protonode) = &nodes[*deduplicated_index] else {
|
||||
panic!("Deduplicated protonode must point to valid protonode");
|
||||
};
|
||||
protonode.stable_node_id
|
||||
}
|
||||
};
|
||||
ProtoNetwork { nodes, output }
|
||||
}
|
||||
|
||||
pub fn nodes(&self) -> impl Iterator<Item = &ProtoNode> {
|
||||
self.nodes
|
||||
.iter()
|
||||
.filter_map(|entry| if let ProtonodeEntry::Protonode(protonode) = entry { Some(protonode) } else { None })
|
||||
}
|
||||
pub fn into_nodes(self) -> impl Iterator<Item = ProtoNode> {
|
||||
self.nodes
|
||||
.into_iter()
|
||||
.filter_map(|entry| if let ProtonodeEntry::Protonode(protonode) = entry { Some(protonode) } else { None })
|
||||
}
|
||||
}
|
||||
|
||||
// impl core::fmt::Display for ProtoNetwork {
|
||||
// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
@@ -69,59 +94,57 @@ use std::hash::Hash;
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash, Eq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UpstreamInputMetadata {
|
||||
pub input_sni: SNI,
|
||||
// Context dependencies are accumulated during compilation, then replaced with whatever needs to be nullified
|
||||
// If None, then the upstream node is a value node, so replace with an empty vec
|
||||
pub context_dependencies: Option<Vec<ContextDependency>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeConstructionArgs {
|
||||
// Used to get the constructor from the function in `node_registry.rs`.
|
||||
pub identifier: ProtoNodeIdentifier,
|
||||
/// A list of stable node ids used as inputs to the constructor
|
||||
pub inputs: Vec<SNI>,
|
||||
// A node is dependent on whatever is marked in its implementation, as well as all inputs
|
||||
// If a node is dependent on more than its input, then a context nullification node is placed on the input
|
||||
// Starts as None, and is populated during stable node id generation
|
||||
pub inputs: Vec<Option<UpstreamInputMetadata>>,
|
||||
// The union of all input context dependencies and the nodes context dependency. Used to generate the context nullification for the editor entry point
|
||||
pub context_dependencies: Vec<ContextDependency>,
|
||||
// Stores the path of document nodes which correspond to it
|
||||
pub node_paths: Vec<ProtonodePath>,
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeValueArgs {
|
||||
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||
/// Also stores its caller inputs, which is used to map the rendered thumbnail to the wire input
|
||||
pub value: MemoHash<value::TaggedValue>,
|
||||
// Stores all absolute input connectors which correspond to this value.
|
||||
pub connector_paths: Vec<AbsoluteInputConnector>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Defines the arguments used to construct the boxed node struct. This is used to call the constructor function in the `node_registry.rs` file - which is hidden behind a wall of macros.
|
||||
pub enum ConstructionArgs {
|
||||
/// A value of a type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||
Value(MemoHash<value::TaggedValue>),
|
||||
Value(NodeValueArgs),
|
||||
Nodes(NodeConstructionArgs),
|
||||
/// Used for GPU computation to work around the limitations of rust-gpu.
|
||||
Inline(InlineRust),
|
||||
}
|
||||
|
||||
impl Eq for ConstructionArgs {}
|
||||
|
||||
impl Hash for ConstructionArgs {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
core::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
Self::Nodes(nodes) => {
|
||||
for node in &nodes.inputs {
|
||||
node.hash(state);
|
||||
}
|
||||
}
|
||||
Self::Value(value) => value.hash(state),
|
||||
Self::Inline(inline) => inline.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstructionArgs {
|
||||
// TODO: what? Used in the gpu_compiler crate for something.
|
||||
pub fn new_function_args(&self) -> Vec<String> {
|
||||
match self {
|
||||
ConstructionArgs::Nodes(nodes) => nodes.inputs.iter().map(|n| format!("n{:0x}", n.0)).collect(),
|
||||
ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
|
||||
ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct OriginalLocation {
|
||||
/// The original location to the document node - e.g. [grandparent_id, parent_id, node_id].
|
||||
pub protonode_path: ProtonodePath,
|
||||
// // Types should not be sent for autogenerated nodes or value nodes, which are not visible and inserted during compilation
|
||||
pub send_types_to_editor: bool,
|
||||
}
|
||||
// impl ConstructionArgs {
|
||||
// // TODO: what? Used in the gpu_compiler crate for something.
|
||||
// pub fn new_function_args(&self) -> Vec<String> {
|
||||
// match self {
|
||||
// ConstructionArgs::Nodes(nodes) => nodes.inputs.iter().map(|n| format!("n{:0x}", n.0)).collect(),
|
||||
// ConstructionArgs::Value(value) => vec![value.to_primitive_string()],
|
||||
// ConstructionArgs::Inline(inline) => vec![inline.expr.clone()],
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A proto node is an intermediate step between the `DocumentNode` and the boxed struct that actually runs the node (found in the [`BorrowTree`]).
|
||||
@@ -130,43 +153,61 @@ pub struct OriginalLocation {
|
||||
pub struct ProtoNode {
|
||||
pub construction_args: ConstructionArgs,
|
||||
pub input: Type,
|
||||
pub original_location: OriginalLocation,
|
||||
pub stable_node_id: SNI,
|
||||
// Each protonode stores the path and input index of the protonodes which called it
|
||||
pub callers: Vec<(ProtonodePath, usize)>,
|
||||
// Each protonode will finally store a single caller (the minimum of all callers), used by the editor
|
||||
pub caller: Option<(ProtonodePath, usize)>,
|
||||
}
|
||||
|
||||
impl Default for ProtoNode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),
|
||||
construction_args: ConstructionArgs::Value(NodeValueArgs {
|
||||
value: value::TaggedValue::U32(0).into(),
|
||||
connector_paths: Vec::new(),
|
||||
}),
|
||||
input: concrete!(Context),
|
||||
original_location: Default::default(),
|
||||
stable_node_id: NodeId(0),
|
||||
callers: Vec::new(),
|
||||
caller: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoNode {
|
||||
/// Construct a new [`ProtoNode`] with the specified construction args and a `ClonedNode` implementation.
|
||||
pub fn value(value: ConstructionArgs, path: Vec<NodeId>, stable_node_id: SNI) -> Self {
|
||||
let inputs_exposed = match &value {
|
||||
ConstructionArgs::Nodes(nodes) => nodes.inputs.len() + 1,
|
||||
_ => 2,
|
||||
};
|
||||
pub fn value(value: ConstructionArgs, stable_node_id: SNI) -> Self {
|
||||
Self {
|
||||
construction_args: value,
|
||||
input: concrete!(Context),
|
||||
original_location: OriginalLocation {
|
||||
protonode_path: path.into(),
|
||||
send_types_to_editor: false,
|
||||
},
|
||||
stable_node_id,
|
||||
callers: Vec::new(),
|
||||
caller: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Hashes the inputs and implementation of non value nodes, and the value for value nodes
|
||||
pub fn generate_stable_node_id(&mut self) {
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = rustc_hash::FxHasher::default();
|
||||
match &self.construction_args {
|
||||
ConstructionArgs::Nodes(nodes) => {
|
||||
for upstream_input in &nodes.inputs {
|
||||
upstream_input.as_ref().unwrap().input_sni.hash(&mut hasher);
|
||||
}
|
||||
nodes.identifier.hash(&mut hasher);
|
||||
}
|
||||
ConstructionArgs::Value(value) => value.value.hash(&mut hasher),
|
||||
ConstructionArgs::Inline(_) => todo!(),
|
||||
}
|
||||
|
||||
self.stable_node_id = NodeId(hasher.finish());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphErrorType {
|
||||
NodeNotFound(NodeId),
|
||||
InputNodeNotFound(NodeId),
|
||||
UnexpectedGenerics { index: usize, inputs: Vec<Type> },
|
||||
NoImplementations,
|
||||
@@ -178,7 +219,6 @@ impl Debug for GraphErrorType {
|
||||
// TODO: format with the document graph context so the input index is the same as in the graph UI.
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphErrorType::NodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"),
|
||||
GraphErrorType::InputNodeNotFound(id) => write!(f, "Input node {id} is not present in the typing context"),
|
||||
GraphErrorType::UnexpectedGenerics { index, inputs } => write!(f, "Generic inputs should not exist but found at {index}: {inputs:?}"),
|
||||
GraphErrorType::NoImplementations => write!(f, "No implementations found"),
|
||||
@@ -222,7 +262,7 @@ impl Debug for GraphErrorType {
|
||||
}
|
||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GraphError {
|
||||
pub node_path: Vec<NodeId>,
|
||||
pub stable_node_id: SNI,
|
||||
pub identifier: Cow<'static, str>,
|
||||
pub error: GraphErrorType,
|
||||
}
|
||||
@@ -231,11 +271,11 @@ impl GraphError {
|
||||
let identifier = match &node.construction_args {
|
||||
ConstructionArgs::Nodes(node_construction_args) => node_construction_args.identifier.name.clone(),
|
||||
// Values are inserted into upcast nodes
|
||||
ConstructionArgs::Value(memo_hash) => "Value Node".into(),
|
||||
ConstructionArgs::Inline(inline_rust) => "Inline".into(),
|
||||
ConstructionArgs::Value(node_value_args) => format!("{:?} Value Node", node_value_args.value.deref().ty()).into(),
|
||||
ConstructionArgs::Inline(_) => "Inline".into(),
|
||||
};
|
||||
Self {
|
||||
node_path: node.original_location.protonode_path.to_vec(),
|
||||
stable_node_id: node.stable_node_id,
|
||||
identifier,
|
||||
error: text.into(),
|
||||
}
|
||||
@@ -243,11 +283,7 @@ impl GraphError {
|
||||
}
|
||||
impl Debug for GraphError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("NodeGraphError")
|
||||
.field("path", &self.node_path.iter().map(|id| id.0).collect::<Vec<_>>())
|
||||
.field("identifier", &self.identifier.to_string())
|
||||
.field("error", &self.error)
|
||||
.finish()
|
||||
f.debug_struct("NodeGraphError").field("identifier", &self.identifier.to_string()).field("error", &self.error).finish()
|
||||
}
|
||||
}
|
||||
pub type GraphErrors = Vec<GraphError>;
|
||||
@@ -256,17 +292,17 @@ pub type GraphErrors = Vec<GraphError>;
|
||||
#[derive(Default, Clone, dyn_any::DynAny)]
|
||||
pub struct TypingContext {
|
||||
lookup: Cow<'static, HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
|
||||
monitor_lookup: Cow<'static, HashMap<Type, MonitorConstructor>>,
|
||||
cache_lookup: Cow<'static, HashMap<Type, CacheConstructor>>,
|
||||
inferred: HashMap<NodeId, NodeIOTypes>,
|
||||
constructor: HashMap<NodeId, NodeConstructor>,
|
||||
}
|
||||
|
||||
impl TypingContext {
|
||||
/// Creates a new `TypingContext` with the given lookup table.
|
||||
pub fn new(lookup: &'static HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>, monitor_lookup: &'static HashMap<Type, MonitorConstructor>) -> Self {
|
||||
pub fn new(lookup: &'static HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>, cache_lookup: &'static HashMap<Type, CacheConstructor>) -> Self {
|
||||
Self {
|
||||
lookup: Cow::Borrowed(lookup),
|
||||
monitor_lookup: Cow::Borrowed(monitor_lookup),
|
||||
cache_lookup: Cow::Borrowed(cache_lookup),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -274,9 +310,9 @@ impl TypingContext {
|
||||
/// Updates the `TypingContext` with a given proto network. This will infer the types of the nodes
|
||||
/// and store them in the `inferred` field. The proto network has to be topologically sorted
|
||||
/// and contain fully resolved stable node ids.
|
||||
pub fn update(&mut self, network: &Vec<ProtoNode>) -> Result<(), GraphErrors> {
|
||||
pub fn update(&mut self, network: &ProtoNetwork) -> Result<(), GraphErrors> {
|
||||
// Update types from the most upstream nodes first
|
||||
for node in network.iter().rev() {
|
||||
for node in network.nodes() {
|
||||
self.infer(node.stable_node_id, node)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -292,9 +328,9 @@ impl TypingContext {
|
||||
self.constructor.get(&node_id).copied()
|
||||
}
|
||||
|
||||
// Returns the monitor node constructor for a given type {
|
||||
pub fn monitor_constructor(&self, monitor_type: &Type) -> Option<MonitorConstructor> {
|
||||
self.monitor_lookup.get(monitor_type).copied()
|
||||
// Returns the cache node constructor for a given type {
|
||||
pub fn cache_constructor(&self, cache_type: &Type) -> Option<CacheConstructor> {
|
||||
self.cache_lookup.get(cache_type).copied()
|
||||
}
|
||||
|
||||
/// Returns the type of a given node id if it exists
|
||||
@@ -314,7 +350,7 @@ impl TypingContext {
|
||||
ConstructionArgs::Value(ref v) => {
|
||||
// assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context)));
|
||||
// TODO: This should return a reference to the value
|
||||
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
|
||||
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.value.ty())), vec![]);
|
||||
self.inferred.insert(node_id, types.clone());
|
||||
return Ok(types);
|
||||
}
|
||||
@@ -323,10 +359,11 @@ impl TypingContext {
|
||||
let inputs = construction_args
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|id| id.as_ref().unwrap().input_sni)
|
||||
.map(|id| {
|
||||
self.inferred
|
||||
.get(id)
|
||||
.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NodeNotFound(*id))])
|
||||
.get(&id)
|
||||
.ok_or_else(|| vec![GraphError::new(node, GraphErrorType::InputNodeNotFound(id))])
|
||||
.map(|node| node.ty())
|
||||
})
|
||||
.collect::<Result<Vec<Type>, GraphErrors>>()?;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use dyn_any::StaticType;
|
||||
use graphene_application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId};
|
||||
use graphene_application_io::{ApplicationError, ApplicationIo, ApplicationIoValue, ResourceFuture, SurfaceHandle, SurfaceId};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use js_sys::{Object, Reflect};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use std::sync::atomic::AtomicU64;
|
||||
@@ -56,6 +57,8 @@ unsafe impl Sync for WindowWrapper {}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
pub type WasmApplicationIoValue = ApplicationIoValue<WasmApplicationIo>;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WasmApplicationIo {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -156,20 +159,12 @@ unsafe impl StaticType for WasmApplicationIo {
|
||||
type Static = WasmApplicationIo;
|
||||
}
|
||||
|
||||
impl<'a> From<&'a WasmEditorApi> for &'a WasmApplicationIo {
|
||||
fn from(editor_api: &'a WasmEditorApi) -> Self {
|
||||
editor_api.application_io.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wgpu")]
|
||||
impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor {
|
||||
fn from(app_io: &'a WasmApplicationIo) -> Self {
|
||||
app_io.gpu_executor.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub type WasmEditorApi = graphene_application_io::EditorApi<WasmApplicationIo>;
|
||||
|
||||
impl ApplicationIo for WasmApplicationIo {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type Surface = HtmlCanvasElement;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
use futures::executor::block_on;
|
||||
use graph_craft::document::value::EditorMetadata;
|
||||
use graph_craft::document::*;
|
||||
use graph_craft::graphene_compiler::{Compiler, Executor};
|
||||
use graph_craft::proto::{ProtoNetwork, ProtoNode};
|
||||
use graph_craft::util::load_network;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
use graph_craft::wasm_application_io::{EditorPreferences, WasmApplicationIoValue};
|
||||
use graphene_core::text::FontCache;
|
||||
use graphene_std::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use graphene_std::application_io::{ApplicationIo, ApplicationIoValue, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_std::wasm_application_io::WasmApplicationIo;
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
use interpreted_executor::util::wrap_network_in_scope;
|
||||
use std::error::Error;
|
||||
@@ -92,14 +93,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
use_vello: true,
|
||||
..Default::default()
|
||||
};
|
||||
let editor_api = Arc::new(WasmEditorApi {
|
||||
font_cache: FontCache::default(),
|
||||
application_io: Some(application_io.into()),
|
||||
node_graph_message_sender: Box::new(UpdateLogger {}),
|
||||
editor_preferences: Box::new(preferences),
|
||||
});
|
||||
let application_io = Arc::new(ApplicationIoValue(Some(Arc::new(application_io))));
|
||||
|
||||
let proto_graph = compile_graph(document_string, editor_api)?;
|
||||
let proto_graph = compile_graph(document_string, application_io)?;
|
||||
|
||||
match app.command {
|
||||
Command::Compile { print_proto, .. } => {
|
||||
@@ -180,17 +176,16 @@ fn fix_nodes(network: &mut NodeNetwork) {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn compile_graph(document_string: String, editor_api: Arc<WasmEditorApi>) -> Result<Vec<ProtoNode>, Box<dyn Error>> {
|
||||
fn compile_graph(document_string: String, application_io: Arc<WasmApplicationIoValue>) -> Result<ProtoNetwork, Box<dyn Error>> {
|
||||
let mut network = load_network(&document_string);
|
||||
fix_nodes(&mut network);
|
||||
|
||||
let substitutions = preprocessor::generate_node_substitutions();
|
||||
let substitutions: std::collections::HashMap<String, DocumentNode> = preprocessor::generate_node_substitutions();
|
||||
preprocessor::expand_network(&mut network, &substitutions);
|
||||
|
||||
let mut wrapped_network = wrap_network_in_scope(network.clone(), editor_api);
|
||||
let mut wrapped_network = wrap_network_in_scope(network, Arc::new(FontCache::default()), EditorMetadata::default(), application_io);
|
||||
|
||||
let compiler = Compiler {};
|
||||
wrapped_network.flatten().map(|result|result.0).map_err(|x| x.into())
|
||||
wrapped_network.flatten().map(|result| result.0).map_err(|x| x.into())
|
||||
}
|
||||
|
||||
fn create_executor(proto_network: ProtoNetwork) -> Result<DynamicExecutor, Box<dyn Error>> {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
|
||||
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
|
||||
use graphene_core::Context;
|
||||
use graphene_core::ContextDependency;
|
||||
use graphene_core::NodeIO;
|
||||
use graphene_core::OwnedContextImpl;
|
||||
use graphene_core::WasmNotSend;
|
||||
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
|
||||
use graphene_core::transform::Footprint;
|
||||
pub use graphene_core::{Node, generic, ops};
|
||||
|
||||
pub trait IntoTypeErasedNode<'n> {
|
||||
@@ -46,3 +51,115 @@ pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(),
|
||||
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
|
||||
DowncastBothNode::new(n)
|
||||
}
|
||||
|
||||
pub struct EditorContextToContext {
|
||||
first: SharedNodeContainer,
|
||||
}
|
||||
|
||||
impl<'i> Node<'i, Any<'i>> for EditorContextToContext {
|
||||
type Output = DynFuture<'i, Any<'i>>;
|
||||
fn eval(&'i self, input: Any<'i>) -> Self::Output {
|
||||
Box::pin(async move {
|
||||
let editor_context = dyn_any::downcast::<EditorContext>(input).unwrap();
|
||||
log::debug!("evaluating with context: {:?}", editor_context.to_context());
|
||||
self.first.eval(Box::new(editor_context.to_context())).await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EditorContextToContext {
|
||||
pub const fn new(first: SharedNodeContainer) -> Self {
|
||||
EditorContextToContext { first }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EditorContext {
|
||||
pub footprint: Option<Footprint>,
|
||||
pub downstream_transform: Option<DAffine2>,
|
||||
pub real_time: Option<f64>,
|
||||
pub animation_time: Option<f64>,
|
||||
pub index: Option<usize>,
|
||||
// #[serde(skip)]
|
||||
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
|
||||
}
|
||||
|
||||
unsafe impl StaticType for EditorContext {
|
||||
type Static = EditorContext;
|
||||
}
|
||||
|
||||
// impl Default for EditorContext {
|
||||
// fn default() -> Self {
|
||||
// EditorContext {
|
||||
// footprint: None,
|
||||
// downstream_transform: None,
|
||||
// real_time: None,
|
||||
// animation_time: None,
|
||||
// index: None,
|
||||
// // editor_var_args: None,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl EditorContext {
|
||||
pub fn to_context(&self) -> Context {
|
||||
let mut context = OwnedContextImpl::default();
|
||||
if let Some(footprint) = self.footprint {
|
||||
context.set_footprint(footprint);
|
||||
}
|
||||
if let Some(footprint) = self.footprint {
|
||||
context.set_footprint(footprint);
|
||||
}
|
||||
// if let Some(downstream_transform) = self.downstream_transform {
|
||||
// context.set_downstream_transform(downstream_transform);
|
||||
// }
|
||||
if let Some(real_time) = self.real_time {
|
||||
context.set_real_time(real_time);
|
||||
}
|
||||
if let Some(animation_time) = self.animation_time {
|
||||
context.set_animation_time(animation_time);
|
||||
}
|
||||
if let Some(index) = self.index {
|
||||
context.set_index(index);
|
||||
}
|
||||
// if let Some(editor_var_args) = self.editor_var_args {
|
||||
// let (variable_names, values)
|
||||
// context.set_varargs((variable_names, values))
|
||||
// }
|
||||
context.into_context()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NullificationNode {
|
||||
first: SharedNodeContainer,
|
||||
nullify: Vec<ContextDependency>,
|
||||
}
|
||||
impl<'i> Node<'i, Any<'i>> for NullificationNode {
|
||||
type Output = DynFuture<'i, Any<'i>>;
|
||||
|
||||
fn eval(&'i self, input: Any<'i>) -> Self::Output {
|
||||
let new_input = match dyn_any::try_downcast::<Context>(input) {
|
||||
Ok(context) => match *context {
|
||||
Some(context) => {
|
||||
log::debug!("Nullifying inputs: {:?}", self.nullify);
|
||||
let mut new_context = OwnedContextImpl::from(context);
|
||||
new_context.nullify(&self.nullify);
|
||||
Box::new(new_context.into_context()) as Any<'i>
|
||||
}
|
||||
None => {
|
||||
let none: Context = None;
|
||||
Box::new(none) as Any<'i>
|
||||
}
|
||||
},
|
||||
Err(other_input) => other_input,
|
||||
};
|
||||
|
||||
Box::pin(async move { self.first.eval(new_input).await })
|
||||
}
|
||||
}
|
||||
|
||||
impl NullificationNode {
|
||||
pub fn new(first: SharedNodeContainer, nullify: Vec<ContextDependency>) -> Self {
|
||||
Self { first, nullify }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::vector::VectorDataTable;
|
||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use graphene_core::Ctx;
|
||||
pub use graphene_core::text::*;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn text<'i: 'n>(
|
||||
_: impl Ctx,
|
||||
editor: &'i WasmEditorApi,
|
||||
font_cache: std::sync::Arc<FontCache>,
|
||||
text: String,
|
||||
font_name: Font,
|
||||
#[unit(" px")]
|
||||
@@ -41,7 +40,7 @@ fn text<'i: 'n>(
|
||||
tilt,
|
||||
};
|
||||
|
||||
let font_data = editor.font_cache.get(&font_name).map(|f| load_font(f));
|
||||
let font_data = font_cache.get(&font_name).map(|f| load_font(f));
|
||||
|
||||
to_path(&text, font_data, typesetting, per_glyph_instances)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use graph_craft::document::value::RenderOutput;
|
||||
pub use graph_craft::document::value::RenderOutputType;
|
||||
use graph_craft::document::value::{EditorMetadata, RenderOutput};
|
||||
pub use graph_craft::wasm_application_io::*;
|
||||
use graphene_application_io::{ApplicationIo, ExportFormat, RenderConfig};
|
||||
use graphene_application_io::ApplicationIo;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use graphene_core::instances::Instances;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use graphene_core::math::bbox::Bbox;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::raster_types::{CPU, GPU, Raster, RasterDataTable};
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend};
|
||||
use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, WasmNotSend};
|
||||
use graphene_svg_renderer::RenderMetadata;
|
||||
use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
|
||||
|
||||
@@ -26,8 +26,8 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
#[node_macro::node(category("Debug: GPU"))]
|
||||
async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<WasmSurfaceHandle> {
|
||||
Arc::new(editor.application_io.as_ref().unwrap().create_window())
|
||||
async fn create_surface<'a: 'n>(_: impl Ctx, application_io: WasmApplicationIoValue) -> Arc<WasmSurfaceHandle> {
|
||||
Arc::new(application_io.0.as_ref().unwrap().create_window())
|
||||
}
|
||||
|
||||
// TODO: Fix and reenable in order to get the 'Draw Canvas' node working again.
|
||||
@@ -59,20 +59,20 @@ async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc<W
|
||||
// }
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Web Request"))]
|
||||
async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
||||
let Some(api) = editor.application_io.as_ref() else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
let Ok(data) = api.load_resource(url) else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
let Ok(data) = data.await else {
|
||||
return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
};
|
||||
// #[node_macro::node(category("Web Request"))]
|
||||
// async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> {
|
||||
// let Some(api) = editor.application_io.as_ref() else {
|
||||
// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
// };
|
||||
// let Ok(data) = api.load_resource(url) else {
|
||||
// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
// };
|
||||
// let Ok(data) = data.await else {
|
||||
// return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec());
|
||||
// };
|
||||
|
||||
data
|
||||
}
|
||||
// data
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Web Request"))]
|
||||
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> RasterDataTable<CPU> {
|
||||
@@ -118,16 +118,16 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
|
||||
#[cfg(feature = "vello")]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
|
||||
async fn render_canvas(
|
||||
render_config: RenderConfig,
|
||||
footprint: Footprint,
|
||||
hide_artboards: bool,
|
||||
data: impl GraphicElementRendered,
|
||||
editor: &WasmEditorApi,
|
||||
application_io: Arc<WasmApplicationIoValue>,
|
||||
surface_handle: wgpu_executor::WgpuSurface,
|
||||
render_params: RenderParams,
|
||||
) -> RenderOutputType {
|
||||
use graphene_application_io::SurfaceFrame;
|
||||
|
||||
let footprint = render_config.viewport;
|
||||
let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else {
|
||||
let Some(exec) = application_io.0.as_ref().unwrap().gpu_executor() else {
|
||||
unreachable!("Attempted to render with Vello when no GPU executor is available");
|
||||
};
|
||||
use vello::*;
|
||||
@@ -142,7 +142,7 @@ async fn render_canvas(
|
||||
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
|
||||
|
||||
let mut background = Color::from_rgb8_srgb(0x22, 0x22, 0x22);
|
||||
if !data.contains_artboard() && !render_config.hide_artboards {
|
||||
if !data.contains_artboard() && !hide_artboards {
|
||||
background = Color::WHITE;
|
||||
}
|
||||
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context, background)
|
||||
@@ -151,7 +151,7 @@ async fn render_canvas(
|
||||
|
||||
let frame = SurfaceFrame {
|
||||
surface_id: surface_handle.window_id,
|
||||
resolution: render_config.viewport.resolution,
|
||||
resolution: footprint.resolution,
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
};
|
||||
|
||||
@@ -230,73 +230,61 @@ where
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
||||
render_config: RenderConfig,
|
||||
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
|
||||
context: impl Ctx + ExtractFootprint,
|
||||
editor_metadata: EditorMetadata,
|
||||
application_io: Arc<WasmApplicationIoValue>,
|
||||
#[implementations(
|
||||
Context -> VectorDataTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> graphene_core::Artboard,
|
||||
Context -> graphene_core::ArtboardGroupTable,
|
||||
Context -> Option<Color>,
|
||||
Context -> Vec<Color>,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> String,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
RasterDataTable<GPU>,
|
||||
GraphicGroupTable,
|
||||
graphene_core::Artboard,
|
||||
graphene_core::ArtboardGroupTable,
|
||||
Option<Color>,
|
||||
Vec<Color>,
|
||||
bool,
|
||||
f32,
|
||||
f64,
|
||||
String,
|
||||
)]
|
||||
data: impl Node<Context<'static>, Output = T>,
|
||||
data: T,
|
||||
_surface_handle: impl Node<Context<'static>, Output = Option<wgpu_executor::WgpuSurface>>,
|
||||
) -> RenderOutput {
|
||||
let footprint = render_config.viewport;
|
||||
let ctx = OwnedContextImpl::default()
|
||||
.with_footprint(footprint)
|
||||
.with_real_time(render_config.time.time)
|
||||
.with_animation_time(render_config.time.animation_time.as_secs_f64())
|
||||
.into_context();
|
||||
ctx.footprint();
|
||||
let Some(footprint) = context.try_footprint().copied() else {
|
||||
log::error!("Footprint must be Some when rendering");
|
||||
return RenderOutput::default();
|
||||
};
|
||||
|
||||
let RenderConfig { hide_artboards, for_export, .. } = render_config;
|
||||
let render_params = RenderParams {
|
||||
view_mode: render_config.view_mode,
|
||||
view_mode: editor_metadata.view_mode,
|
||||
culling_bounds: None,
|
||||
thumbnail: false,
|
||||
hide_artboards,
|
||||
for_export,
|
||||
hide_artboards: editor_metadata.hide_artboards,
|
||||
for_export: editor_metadata.for_export,
|
||||
for_mask: false,
|
||||
alignment_parent_transform: None,
|
||||
};
|
||||
|
||||
let data = data.eval(ctx.clone()).await;
|
||||
let editor_api = editor_api.eval(None).await;
|
||||
|
||||
#[cfg(all(feature = "vello", not(test)))]
|
||||
let surface_handle = _surface_handle.eval(None).await;
|
||||
|
||||
let use_vello = editor_api.editor_preferences.use_vello();
|
||||
let use_vello = editor_metadata.use_vello;
|
||||
#[cfg(all(feature = "vello", not(test)))]
|
||||
let use_vello = use_vello && surface_handle.is_some();
|
||||
|
||||
let mut metadata = RenderMetadata::default();
|
||||
data.collect_metadata(&mut metadata, footprint, None);
|
||||
|
||||
let output_format = render_config.export_format;
|
||||
let data = match output_format {
|
||||
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
|
||||
ExportFormat::Canvas => {
|
||||
if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() {
|
||||
#[cfg(all(feature = "vello", not(test)))]
|
||||
return RenderOutput {
|
||||
data: render_canvas(render_config, data, editor_api, surface_handle.unwrap(), render_params).await,
|
||||
metadata,
|
||||
};
|
||||
#[cfg(any(not(feature = "vello"), test))]
|
||||
render_svg(data, SvgRender::new(), render_params, footprint)
|
||||
} else {
|
||||
render_svg(data, SvgRender::new(), render_params, footprint)
|
||||
}
|
||||
}
|
||||
_ => todo!("Non-SVG render output for {output_format:?}"),
|
||||
let data = if use_vello {
|
||||
#[cfg(all(feature = "vello", not(test)))]
|
||||
return RenderOutput {
|
||||
data: render_canvas(footprint, editor_metadata.hide_artboards, data, application_io, surface_handle.unwrap(), render_params).await,
|
||||
metadata,
|
||||
};
|
||||
#[cfg(any(not(feature = "vello"), test))]
|
||||
render_svg(data, SvgRender::new(), render_params, footprint)
|
||||
} else {
|
||||
render_svg(data, SvgRender::new(), render_params, footprint)
|
||||
};
|
||||
RenderOutput { data, metadata }
|
||||
}
|
||||
|
||||
@@ -211,6 +211,30 @@ pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams);
|
||||
|
||||
fn render_thumbnail(&self) -> String {
|
||||
let bounds = self.bounding_box(DAffine2::IDENTITY, true);
|
||||
|
||||
let render_params = RenderParams {
|
||||
view_mode: ViewMode::Normal,
|
||||
culling_bounds: bounds,
|
||||
thumbnail: true,
|
||||
hide_artboards: false,
|
||||
for_export: false,
|
||||
for_mask: false,
|
||||
alignment_parent_transform: None,
|
||||
};
|
||||
|
||||
// Render the thumbnail data into an SVG string
|
||||
let mut render = SvgRender::new();
|
||||
self.render_svg(&mut render, &render_params);
|
||||
|
||||
// Give the SVG a viewbox and outer <svg>...</svg> wrapper tag
|
||||
// let [min, max] = bounds.unwrap_or_default();
|
||||
// render.format_svg(min, max);
|
||||
|
||||
render.svg.to_svg_string()
|
||||
}
|
||||
|
||||
/// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection.
|
||||
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
|
||||
pub fn setup_network(name: &str) -> (DynamicExecutor, ProtoNetwork) {
|
||||
let mut network = load_from_name(name);
|
||||
let proto_network = network.flatten().unwrap();
|
||||
let proto_network = network.flatten().unwrap().0;
|
||||
let executor = block_on(DynamicExecutor::new(proto_network.0)).unwrap();
|
||||
(executor, proto_network)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use crate::node_registry::{MONITOR_NODES, NODE_REGISTRY};
|
||||
use crate::node_registry::{CACHE_NODES, NODE_REGISTRY};
|
||||
use dyn_any::StaticType;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode};
|
||||
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, downcast_node};
|
||||
use graph_craft::document::ProtonodeEntry;
|
||||
use graph_craft::document::value::{TaggedValue, UpcastNode};
|
||||
use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext, UpstreamInputMetadata};
|
||||
use graph_craft::proto::{GraphErrorType, GraphErrors};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation};
|
||||
use graphene_std::memo::{IntrospectMode, MonitorNode};
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::any::{EditorContext, EditorContextToContext, NullificationNode};
|
||||
use graphene_std::memo::IntrospectMode;
|
||||
use graphene_std::uuid::{CompiledProtonodeInput, NodeId, SNI};
|
||||
use graphene_std::{NodeIOTypes, OwnedContextImpl};
|
||||
use graphene_std::{Context, MemoHash};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::error::Error;
|
||||
use std::ptr::null;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// An executor of a node graph that does not require an online compilation server, and instead uses `Box<dyn ...>`.
|
||||
@@ -32,25 +32,24 @@ impl Default for DynamicExecutor {
|
||||
Self {
|
||||
output: None,
|
||||
tree: Default::default(),
|
||||
typing_context: TypingContext::new(&NODE_REGISTRY, &MONITOR_NODES),
|
||||
typing_context: TypingContext::new(&NODE_REGISTRY, &CACHE_NODES),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicExecutor {
|
||||
pub async fn new(proto_network: Vec<ProtoNode>) -> Result<Self, GraphErrors> {
|
||||
pub async fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {
|
||||
let mut typing_context = TypingContext::default();
|
||||
typing_context.update(&proto_network)?;
|
||||
let output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
|
||||
let output = Some(proto_network.output);
|
||||
let tree = BorrowTree::new(proto_network, &typing_context).await?;
|
||||
|
||||
Ok(Self { tree, output, typing_context })
|
||||
}
|
||||
|
||||
/// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible.
|
||||
#[cfg_attr(debug_assertions, inline(never))]
|
||||
pub async fn update(mut self, proto_network: Vec<ProtoNode>) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
|
||||
self.output = proto_network.get(0).map(|protonode| protonode.stable_node_id);
|
||||
pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result<(Vec<(SNI, Vec<Type>)>, Vec<(SNI, usize)>), GraphErrors> {
|
||||
self.output = Some(proto_network.output);
|
||||
self.typing_context.update(&proto_network)?;
|
||||
// A protonode id can change while having the same document path, and the path can change while having the same stable node id.
|
||||
// Either way, the mapping of paths to ids and ids to paths has to be kept in sync.
|
||||
@@ -58,12 +57,9 @@ impl DynamicExecutor {
|
||||
let (add, orphaned_proto_nodes) = self.tree.update(proto_network, &self.typing_context).await?;
|
||||
let mut remove = Vec::new();
|
||||
for sni in orphaned_proto_nodes {
|
||||
let Some(types) = self.typing_context.type_of(sni) else {
|
||||
log::error!("Could not get type for protonode {sni} when removing");
|
||||
continue;
|
||||
};
|
||||
remove.push((sni, types.inputs.len()));
|
||||
self.tree.free_node(&sni, types.inputs.len());
|
||||
if let Some(number_of_inputs) = self.tree.free_node(&sni) {
|
||||
remove.push((sni, number_of_inputs));
|
||||
}
|
||||
self.typing_context.remove_inference(&sni);
|
||||
}
|
||||
|
||||
@@ -71,7 +67,6 @@ impl DynamicExecutor {
|
||||
.into_iter()
|
||||
.filter_map(|sni| {
|
||||
let Some(types) = self.typing_context.type_of(sni) else {
|
||||
log::debug!("Could not get type for added node: {sni}");
|
||||
return None;
|
||||
};
|
||||
Some((sni, types.inputs.clone()))
|
||||
@@ -82,24 +77,27 @@ impl DynamicExecutor {
|
||||
}
|
||||
|
||||
/// Intospect the value for that specific protonode input, returning for example the cached value for a monitor node.
|
||||
pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Box<dyn std::any::Any + Send + Sync>, IntrospectError> {
|
||||
let node = self.get_monitor_node_container(protonode_input)?;
|
||||
node.introspect(introspect_mode).ok_or(IntrospectError::IntrospectNotImplemented)
|
||||
pub fn introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) -> Result<Option<Arc<dyn std::any::Any + Send + Sync>>, IntrospectError> {
|
||||
let node = self.get_introspect_node_container(protonode_input)?;
|
||||
Ok(node.introspect(introspect_mode))
|
||||
}
|
||||
|
||||
pub fn set_introspect(&self, protonode_input: CompiledProtonodeInput, introspect_mode: IntrospectMode) {
|
||||
let Ok(node) = self.get_monitor_node_container(protonode_input) else {
|
||||
let Ok(node) = self.get_introspect_node_container(protonode_input) else {
|
||||
log::error!("Could not get monitor node for input: {:?}", protonode_input);
|
||||
return;
|
||||
};
|
||||
node.set_introspect(introspect_mode);
|
||||
}
|
||||
|
||||
pub fn get_monitor_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result<SharedNodeContainer, IntrospectError> {
|
||||
pub fn get_introspect_node_container(&self, protonode_input: CompiledProtonodeInput) -> Result<SharedNodeContainer, IntrospectError> {
|
||||
// The SNI of the monitor nodes are the ids of the protonode + input index
|
||||
let monitor_node_id = NodeId(protonode_input.0.0 + protonode_input.1 as u64 + 1);
|
||||
let inserted_node = self.tree.nodes.get(&monitor_node_id).ok_or(IntrospectError::ProtoNodeNotFound(monitor_node_id))?;
|
||||
Ok(inserted_node.clone())
|
||||
let inserted_node = self.tree.nodes.get(&protonode_input.0).ok_or(IntrospectError::ProtoNodeNotFound(protonode_input))?;
|
||||
let node = inserted_node
|
||||
.input_introspection_entrypoints
|
||||
.get(protonode_input.1)
|
||||
.ok_or(IntrospectError::InputIndexOutOfBounds(protonode_input))?;
|
||||
Ok(node.clone())
|
||||
}
|
||||
|
||||
pub fn input_type(&self) -> Option<Type> {
|
||||
@@ -118,15 +116,38 @@ impl DynamicExecutor {
|
||||
self.output.and_then(|output| self.typing_context.type_of(output).map(|node_io| node_io.return_value.clone()))
|
||||
}
|
||||
|
||||
pub fn execute<I>(&self, input: I) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
|
||||
// If node to evaluate is None then the most downstream node is used
|
||||
pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option<SNI>) -> Result<TaggedValue, String> {
|
||||
let node_to_evaluate: NodeId = node_to_evaluate
|
||||
.or_else(|| self.output)
|
||||
.ok_or("Could not find output node when evaluating network. Has the network been compiled?")?;
|
||||
let input_type = self
|
||||
.typing_context
|
||||
.type_of(node_to_evaluate)
|
||||
.map(|node_io| node_io.call_argument.clone())
|
||||
.ok_or("Could not get input type of network to execute".to_string())?;
|
||||
// A node to convert the EditorContext to the Context is automatically inserted for each node at id-1
|
||||
let result = match input_type {
|
||||
t if t == concrete!(Context) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()),
|
||||
t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()),
|
||||
t => Err(format!("Invalid input type {t:?}")),
|
||||
};
|
||||
let result = match result {
|
||||
Ok(value) => value,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn execute<I>(&self, input: I, protonode_id: SNI) -> LocalFuture<'_, Result<TaggedValue, Box<dyn Error>>>
|
||||
where
|
||||
I: dyn_any::StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
|
||||
{
|
||||
Box::pin(async move {
|
||||
use futures::FutureExt;
|
||||
let output_node = self.output.ok_or("Could not execute network before compilation")?;
|
||||
|
||||
let result = self.tree.eval_tagged_value(output_node, input);
|
||||
let result = self.tree.eval_tagged_value(protonode_id, input);
|
||||
let wrapped_result = std::panic::AssertUnwindSafe(result).catch_unwind().await;
|
||||
|
||||
match wrapped_result {
|
||||
@@ -138,95 +159,14 @@ impl DynamicExecutor {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// If node to evaluate is None then the most downstream node is used
|
||||
// pub async fn evaluate_from_node(&self, editor_context: EditorContext, node_to_evaluate: Option<SNI>) -> Result<TaggedValue, String> {
|
||||
// let node_to_evaluate: NodeId = node_to_evaluate
|
||||
// .or_else(|| self.output)
|
||||
// .ok_or("Could not find output node when evaluating network. Has the network been compiled?")?;
|
||||
// let input_type = self
|
||||
// .typing_context
|
||||
// .type_of(node_to_evaluate)
|
||||
// .map(|node_io| node_io.call_argument.clone())
|
||||
// .ok_or("Could not get input type of network to execute".to_string())?;
|
||||
// let result = match input_type {
|
||||
// t if t == concrete!(EditorContext) => self.execute(editor_context, node_to_evaluate).await.map_err(|e| e.to_string()),
|
||||
// t if t == concrete!(()) => (&self).execute((), node_to_evaluate).await.map_err(|e| e.to_string()),
|
||||
// t => Err(format!("Invalid input type {t:?}")),
|
||||
// };
|
||||
// let result = match result {
|
||||
// Ok(value) => value,
|
||||
// Err(e) => return Err(e),
|
||||
// };
|
||||
|
||||
// Ok(result)
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EditorContext {
|
||||
// pub footprint: Option<Footprint>,
|
||||
// pub downstream_transform: Option<DAffine2>,
|
||||
// pub real_time: Option<f64>,
|
||||
// pub animation_time: Option<f64>,
|
||||
// pub index: Option<usize>,
|
||||
// pub editor_var_args: Option<(Vec<String>, Vec<Arc<Box<[dyn std::any::Any + 'static + std::panic::UnwindSafe]>>>)>,
|
||||
|
||||
// TODO: Temporarily used to execute with RenderConfig as call argument, will be removed once these fields can be passed
|
||||
// As a scope input to the reworked render node. This will allow the Editor Context to be used to evaluate any node
|
||||
pub render_config: RenderConfig,
|
||||
}
|
||||
|
||||
unsafe impl StaticType for EditorContext {
|
||||
type Static = EditorContext;
|
||||
}
|
||||
|
||||
// impl Default for EditorContext {
|
||||
// fn default() -> Self {
|
||||
// EditorContext {
|
||||
// footprint: None,
|
||||
// downstream_transform: None,
|
||||
// real_time: None,
|
||||
// animation_time: None,
|
||||
// index: None,
|
||||
// // editor_var_args: None,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl EditorContext {
|
||||
// pub fn to_context(&self) -> graphene_std::Context {
|
||||
// let mut context = OwnedContextImpl::default();
|
||||
// if let Some(footprint) = self.footprint {
|
||||
// context.set_footprint(footprint);
|
||||
// }
|
||||
// if let Some(footprint) = self.footprint {
|
||||
// context.set_footprint(footprint);
|
||||
// }
|
||||
// if let Some(downstream_transform) = self.downstream_transform {
|
||||
// context.set_downstream_transform(downstream_transform);
|
||||
// }
|
||||
// if let Some(real_time) = self.real_time {
|
||||
// context.set_real_time(real_time);
|
||||
// }
|
||||
// if let Some(animation_time) = self.animation_time {
|
||||
// context.set_animation_time(animation_time);
|
||||
// }
|
||||
// if let Some(index) = self.index {
|
||||
// context.set_index(index);
|
||||
// }
|
||||
// // if let Some(editor_var_args) = self.editor_var_args {
|
||||
// // let (variable_names, values)
|
||||
// // context.set_varargs((variable_names, values))
|
||||
// // }
|
||||
// context.into_context()
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum IntrospectError {
|
||||
PathNotFound(Vec<NodeId>),
|
||||
ProtoNodeNotFound(SNI),
|
||||
ProtoNodeNotFound(CompiledProtonodeInput),
|
||||
InputIndexOutOfBounds(CompiledProtonodeInput),
|
||||
InvalidInputType(CompiledProtonodeInput),
|
||||
NoData,
|
||||
RuntimeNotReady,
|
||||
IntrospectNotImplemented,
|
||||
@@ -236,14 +176,33 @@ impl std::fmt::Display for IntrospectError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
IntrospectError::PathNotFound(path) => write!(f, "Path not found: {:?}", path),
|
||||
IntrospectError::ProtoNodeNotFound(id) => write!(f, "ProtoNode not found: {:?}", id),
|
||||
IntrospectError::ProtoNodeNotFound(input) => write!(f, "ProtoNode not found: {:?}", input),
|
||||
IntrospectError::NoData => write!(f, "No data found for this node"),
|
||||
IntrospectError::RuntimeNotReady => write!(f, "Node runtime is not ready"),
|
||||
IntrospectError::IntrospectNotImplemented => write!(f, "Intospect not implemented"),
|
||||
IntrospectError::InputIndexOutOfBounds(input) => write!(f, "Invalid input index: {:?}", input),
|
||||
IntrospectError::InvalidInputType(input) => write!(f, "Invalid input type: {:?}", input),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InsertedProtonode {
|
||||
// If the inserted protonode is a value node, then do not clear types when removing
|
||||
is_value: bool,
|
||||
// Either the value node, cache node, or protonode if output is not clone
|
||||
cached_protonode: SharedNodeContainer,
|
||||
// Value nodes are the entry points, since they can be directly evaluated
|
||||
// Nodes with cloneable outputs have a cache, then editor entry point
|
||||
// Nodes without cloneable outputs just have an editor entry point connected to their output
|
||||
output_editor_entrypoint: SharedNodeContainer,
|
||||
// Nodes with inputs store references to the entry points of the upstream node
|
||||
// This is used to generate thumbnails
|
||||
input_thumbnail_entrypoints: Vec<SharedNodeContainer>,
|
||||
// They also store references to the upstream cache/value node, used for introspection
|
||||
input_introspection_entrypoints: Vec<SharedNodeContainer>,
|
||||
}
|
||||
|
||||
/// A store of dynamically typed nodes and their associated source map.
|
||||
///
|
||||
/// [`BorrowTree`] maintains two main data structures:
|
||||
@@ -264,51 +223,54 @@ impl std::fmt::Display for IntrospectError {
|
||||
/// A store of the dynamically typed nodes and also the source map.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct BorrowTree {
|
||||
// A hashmap of node IDs and dynamically typed nodes, as well as the number of inserted monitor nodes
|
||||
nodes: HashMap<SNI, SharedNodeContainer>,
|
||||
// A hashmap of node IDs to dynamically typed proto nodes, as well as the auto inserted MonitorCache nodes, and editor entry point
|
||||
nodes: HashMap<SNI, InsertedProtonode>,
|
||||
}
|
||||
|
||||
impl BorrowTree {
|
||||
pub async fn new(proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
|
||||
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
|
||||
let mut nodes = BorrowTree::default();
|
||||
for node in proto_network {
|
||||
for node in proto_network.into_nodes() {
|
||||
nodes.push_node(node, typing_context).await?
|
||||
}
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
/// Pushes new nodes into the tree and returns a vec of document nodes that had their types changed, and a vec of all nodes that were removed (including auto inserted value nodes)
|
||||
pub async fn update(&mut self, proto_network: Vec<ProtoNode>, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
|
||||
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<SNI>, HashSet<SNI>), GraphErrors> {
|
||||
let mut old_nodes = self.nodes.keys().copied().into_iter().collect::<HashSet<_>>();
|
||||
// List of all document node paths that need to be updated, which occurs if their path changes or type changes
|
||||
let mut nodes_with_new_type = Vec::new();
|
||||
for node in proto_network {
|
||||
for node in proto_network.into_nodes() {
|
||||
let sni = node.stable_node_id;
|
||||
old_nodes.remove(&sni);
|
||||
let sni = node.stable_node_id;
|
||||
if !self.nodes.contains_key(&sni) {
|
||||
if node.original_location.send_types_to_editor {
|
||||
// Do not send types for auto inserted value nodes
|
||||
if matches!(node.construction_args, ConstructionArgs::Nodes(_)) {
|
||||
nodes_with_new_type.push(sni)
|
||||
}
|
||||
self.push_node(node, typing_context);
|
||||
self.push_node(node, typing_context).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((nodes_with_new_type, old_nodes))
|
||||
}
|
||||
|
||||
fn node_deps(&self, nodes: &[SNI]) -> Vec<SharedNodeContainer> {
|
||||
nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect()
|
||||
fn node_deps(&self, input_metadata: &Vec<Option<UpstreamInputMetadata>>) -> Vec<&InsertedProtonode> {
|
||||
input_metadata
|
||||
.iter()
|
||||
.map(|input_metadata| self.nodes.get(&input_metadata.as_ref().expect("input should be mapped during SNI generation").input_sni).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Evaluate the output node of the [`BorrowTree`].
|
||||
/// Evaluate any node in the borrow tree
|
||||
pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>
|
||||
where
|
||||
I: StaticType + 'i + Send + Sync,
|
||||
O: StaticType + 'i,
|
||||
{
|
||||
let node = self.nodes.get(&id).cloned()?;
|
||||
let output = node.eval(Box::new(input));
|
||||
let node = self.nodes.get(&id)?;
|
||||
let output = node.output_editor_entrypoint.eval(Box::new(input));
|
||||
dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
|
||||
}
|
||||
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.
|
||||
@@ -317,8 +279,8 @@ impl BorrowTree {
|
||||
where
|
||||
I: StaticType + 'static + Send + Sync,
|
||||
{
|
||||
let inserted_node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
|
||||
let output = inserted_node.eval(Box::new(input));
|
||||
let inserted_node = self.nodes.get(&id).ok_or("Output node not found in executor")?;
|
||||
let output = inserted_node.output_editor_entrypoint.eval(Box::new(input));
|
||||
TaggedValue::try_from_any(output.await)
|
||||
}
|
||||
|
||||
@@ -377,12 +339,9 @@ impl BorrowTree {
|
||||
/// - Removes the node from `nodes` HashMap.
|
||||
/// - If the node is the primary node for its path in the `source_map`, it's also removed from there.
|
||||
/// - Returns `None` if the node is not found in the `nodes` HashMap.
|
||||
pub fn free_node(&mut self, id: &SNI, inputs: usize) {
|
||||
self.nodes.remove(&id);
|
||||
// Also remove all corresponding monitor nodes
|
||||
for monitor_index in 1..=inputs {
|
||||
self.nodes.remove(&NodeId(id.0 + monitor_index as u64));
|
||||
}
|
||||
pub fn free_node(&mut self, id: &SNI) -> Option<usize> {
|
||||
let removed_node = self.nodes.remove(&id).expect(&format!("Could not remove node: {:?}", id));
|
||||
removed_node.is_value.then_some(removed_node.input_thumbnail_entrypoints.len())
|
||||
}
|
||||
|
||||
/// Inserts a new node into the [`BorrowTree`], calling the constructor function from `node_registry.rs`.
|
||||
@@ -400,40 +359,115 @@ impl BorrowTree {
|
||||
/// - `Nodes`: Constructs a node using other nodes as dependencies.
|
||||
/// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments.
|
||||
/// - Returns an error if no constructor is found for the given node ID.
|
||||
/// Thumbnails is a mapping of the protonode input to the rendered thumbnail through the monitor cache node
|
||||
async fn push_node(&mut self, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> {
|
||||
let sni = proto_node.stable_node_id;
|
||||
// Move the value into the upcast node instead of cloning it
|
||||
match proto_node.construction_args {
|
||||
ConstructionArgs::Value(value) => {
|
||||
ConstructionArgs::Value(value_args) => {
|
||||
// The constructor for nodes with value construction args (value nodes) is not called.
|
||||
// It is not necessary to clone the Arc for the wasm editor api, since the value node is deduplicated and only called once.
|
||||
// It is cloned whenever it is evaluated
|
||||
let upcasted = UpcastNode::new(value);
|
||||
// let node = if let TaggedValue::ApplicationIo(api) = &*value {
|
||||
// let editor_api = UpcastAsRefNode::new(api.clone());
|
||||
// let node = Box::new(editor_api) as TypeErasedBox<'_>;
|
||||
// NodeContainer::new(node)
|
||||
// } else {
|
||||
|
||||
let upcasted = UpcastNode::new(value_args.value);
|
||||
let node = Box::new(upcasted) as TypeErasedBox<'_>;
|
||||
self.nodes.insert(sni, NodeContainer::new(node));
|
||||
let value_node = NodeContainer::new(node);
|
||||
|
||||
let inserted_protonode = InsertedProtonode {
|
||||
is_value: true,
|
||||
cached_protonode: value_node.clone(),
|
||||
output_editor_entrypoint: value_node,
|
||||
input_thumbnail_entrypoints: Vec::new(),
|
||||
input_introspection_entrypoints: Vec::new(),
|
||||
};
|
||||
self.nodes.insert(sni, inserted_protonode);
|
||||
}
|
||||
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
|
||||
ConstructionArgs::Nodes(ref node_construction_args) => {
|
||||
ConstructionArgs::Nodes(node_construction_args) => {
|
||||
let construction_nodes = self.node_deps(&node_construction_args.inputs);
|
||||
|
||||
let types = typing_context.type_of(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let monitor_nodes = construction_nodes
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(input_index, construction_node)| {
|
||||
let input_type = types.inputs.get(input_index).unwrap(); //.ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let monitor_constructor = typing_context.monitor_constructor(input_type).unwrap(); // .ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let monitor = monitor_constructor(construction_node);
|
||||
let monitor_node_container = NodeContainer::new(monitor);
|
||||
self.nodes.insert(NodeId(sni.0 + input_index as u64 + 1), monitor_node_container.clone());
|
||||
monitor_node_container
|
||||
})
|
||||
.collect();
|
||||
let input_thumbnail_entrypoints = construction_nodes
|
||||
.iter()
|
||||
.map(|inserted_protonode| inserted_protonode.output_editor_entrypoint.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let input_introspection_entrypoints = construction_nodes.iter().map(|inserted_protonode| inserted_protonode.cached_protonode.clone()).collect::<Vec<_>>();
|
||||
|
||||
let constructor = typing_context.constructor(sni).ok_or_else(|| vec![GraphError::new(&proto_node, GraphErrorType::NoConstructor)])?;
|
||||
let node = constructor(monitor_nodes).await;
|
||||
let node = NodeContainer::new(node);
|
||||
self.nodes.insert(sni, node);
|
||||
// Insert nullification if necessary
|
||||
let protonode_inputs = construction_nodes
|
||||
.iter()
|
||||
.zip(node_construction_args.inputs.into_iter())
|
||||
.map(|(inserted_protonode, input_metadata)| {
|
||||
let previous_input = inserted_protonode.cached_protonode.clone();
|
||||
let input_context_dependencies = input_metadata.unwrap().context_dependencies.unwrap();
|
||||
let protonode_input = if !input_context_dependencies.is_empty() {
|
||||
let nullification_node = NullificationNode::new(previous_input, input_context_dependencies);
|
||||
let node = Box::new(nullification_node) as TypeErasedBox<'_>;
|
||||
NodeContainer::new(node)
|
||||
} else {
|
||||
previous_input
|
||||
};
|
||||
protonode_input
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let constructor = typing_context.constructor(sni).ok_or_else(|| {
|
||||
vec![GraphError {
|
||||
stable_node_id: sni,
|
||||
identifier: node_construction_args.identifier.name.clone(),
|
||||
error: GraphErrorType::NoConstructor,
|
||||
}]
|
||||
})?;
|
||||
let node = constructor(protonode_inputs).await;
|
||||
let protonode = NodeContainer::new(node);
|
||||
|
||||
let types = typing_context.type_of(sni).ok_or_else(|| {
|
||||
vec![GraphError {
|
||||
stable_node_id: sni,
|
||||
identifier: node_construction_args.identifier.name,
|
||||
error: GraphErrorType::NoConstructor,
|
||||
}]
|
||||
})?;
|
||||
|
||||
// Insert cache nodes on the output if possible
|
||||
let cached_protonode = if let Some(cache_constructor) = typing_context.cache_constructor(&types.return_value.nested_type()) {
|
||||
let cache = cache_constructor(protonode);
|
||||
let cache_node_container = NodeContainer::new(cache);
|
||||
cache_node_container
|
||||
} else {
|
||||
protonode
|
||||
};
|
||||
|
||||
// If the call argument is Context, insert a conversion node between EditorContext to Context so that it can be evaluated
|
||||
// Also insert the nullification node to whatever the protonode is not dependent on
|
||||
let mut editor_entrypoint_input = cached_protonode.clone();
|
||||
if types.call_argument == concrete!(Context) {
|
||||
let nullify = graphene_std::all_context_dependencies()
|
||||
.into_iter()
|
||||
.filter(|dependency| !node_construction_args.context_dependencies.contains(dependency))
|
||||
.collect::<Vec<_>>();
|
||||
if !nullify.is_empty() {
|
||||
let nullification_node = NullificationNode::new(cached_protonode.clone(), nullify);
|
||||
let node = Box::new(nullification_node) as TypeErasedBox<'_>;
|
||||
editor_entrypoint_input = NodeContainer::new(node)
|
||||
}
|
||||
}
|
||||
|
||||
let editor_entry_point = EditorContextToContext::new(editor_entrypoint_input);
|
||||
let node = Box::new(editor_entry_point) as TypeErasedBox;
|
||||
let output_editor_entrypoint = NodeContainer::new(node);
|
||||
|
||||
let inserted_protonode = InsertedProtonode {
|
||||
is_value: false,
|
||||
cached_protonode,
|
||||
output_editor_entrypoint,
|
||||
input_thumbnail_entrypoints,
|
||||
input_introspection_entrypoints,
|
||||
};
|
||||
|
||||
self.nodes.insert(sni, inserted_protonode);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
@@ -449,7 +483,7 @@ mod test {
|
||||
#[test]
|
||||
fn push_node_sync() {
|
||||
let mut tree = BorrowTree::default();
|
||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![], NodeId(0));
|
||||
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), NodeId(0));
|
||||
let context = TypingContext::default();
|
||||
let future = tree.push_node(val_1_protonode, &context);
|
||||
futures::executor::block_on(future).unwrap();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use dyn_any::StaticType;
|
||||
use glam::{DVec2, IVec2, UVec2};
|
||||
use graph_craft::document::value::RenderOutput;
|
||||
use graph_craft::proto::{MonitorConstructor, NodeConstructor, TypeErasedBox};
|
||||
use graph_craft::proto::{CacheConstructor, NodeConstructor, TypeErasedBox};
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::*;
|
||||
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
|
||||
@@ -17,8 +17,8 @@ use graphene_std::any::DowncastBothNode;
|
||||
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
|
||||
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
|
||||
#[cfg(feature = "gpu")]
|
||||
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
|
||||
use node_registry_macros::{async_node, convert_node, into_node, monitor_node};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIoValue, WasmSurfaceHandle};
|
||||
use node_registry_macros::{async_node, cache_node, convert_node, into_node};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -119,22 +119,22 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
|
||||
#[cfg(feature = "gpu")]
|
||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RasterDataTable<GPU>]),
|
||||
#[cfg(feature = "gpu")]
|
||||
into_node!(from: &WasmEditorApi, to: &WgpuExecutor),
|
||||
// #[cfg(feature = "gpu")]
|
||||
// into_node!(from: &WasmApplicationIoValue, to: &WgpuExecutor),
|
||||
#[cfg(feature = "gpu")]
|
||||
(
|
||||
ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)),
|
||||
|args| {
|
||||
Box::pin(async move {
|
||||
let editor_api: DowncastBothNode<Context, &WasmEditorApi> = DowncastBothNode::new(args[0].clone());
|
||||
let editor_api: DowncastBothNode<Context, Arc<WasmApplicationIoValue>> = DowncastBothNode::new(args[0].clone());
|
||||
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(editor_api);
|
||||
let any: DynAnyNode<Context, _, _> = DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
},
|
||||
{
|
||||
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, &WasmEditorApi>>::new());
|
||||
let params = vec![fn_type_fut!(Context, &WasmEditorApi)];
|
||||
let node = <wgpu_executor::CreateGpuSurfaceNode<_>>::new(graphene_std::any::PanicNode::<Context, dyn_any::DynFuture<'static, Arc<WasmApplicationIoValue>>>::new());
|
||||
let params = vec![fn_type_fut!(Context, Arc<WasmApplicationIoValue>)];
|
||||
let mut node_io = <wgpu_executor::CreateGpuSurfaceNode<_> as NodeIO<'_, Context>>::to_async_node_io(&node, params);
|
||||
node_io.call_argument = concrete!(<Context as StaticType>::Static);
|
||||
node_io
|
||||
@@ -192,51 +192,51 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||
|
||||
pub static NODE_REGISTRY: Lazy<HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>> = Lazy::new(|| node_registry());
|
||||
|
||||
fn monitor_nodes() -> HashMap<Type, MonitorConstructor> {
|
||||
let nodes: Vec<(Type, MonitorConstructor)> = vec![
|
||||
monitor_node!(ImageTexture),
|
||||
monitor_node!(VectorDataTable),
|
||||
monitor_node!(GraphicGroupTable),
|
||||
monitor_node!(GraphicElement),
|
||||
monitor_node!(Artboard),
|
||||
monitor_node!(RasterDataTable<CPU>),
|
||||
monitor_node!(RasterDataTable<GPU>),
|
||||
monitor_node!(graphene_core::instances::Instances<Artboard>),
|
||||
monitor_node!(String),
|
||||
monitor_node!(IVec2),
|
||||
monitor_node!(DVec2),
|
||||
monitor_node!(bool),
|
||||
monitor_node!(f64),
|
||||
monitor_node!(u32),
|
||||
monitor_node!(u64),
|
||||
monitor_node!(()),
|
||||
monitor_node!(Vec<f64>),
|
||||
monitor_node!(BlendMode),
|
||||
monitor_node!(graphene_std::transform::ReferencePoint),
|
||||
monitor_node!(graphene_path_bool::BooleanOperation),
|
||||
monitor_node!(Option<Color>),
|
||||
monitor_node!(graphene_core::vector::style::Fill),
|
||||
monitor_node!(graphene_core::vector::style::StrokeCap),
|
||||
monitor_node!(graphene_core::vector::style::StrokeJoin),
|
||||
monitor_node!(graphene_core::vector::style::PaintOrder),
|
||||
monitor_node!(graphene_core::vector::style::StrokeAlign),
|
||||
monitor_node!(graphene_core::vector::style::Stroke),
|
||||
monitor_node!(graphene_core::vector::style::Gradient),
|
||||
monitor_node!(graphene_core::vector::style::GradientStops),
|
||||
monitor_node!(Vec<graphene_core::uuid::NodeId>),
|
||||
monitor_node!(Color),
|
||||
monitor_node!(Box<graphene_core::vector::VectorModification>),
|
||||
monitor_node!(graphene_std::vector::misc::CentroidType),
|
||||
monitor_node!(graphene_std::vector::misc::PointSpacingType),
|
||||
fn cache_nodes() -> HashMap<Type, CacheConstructor> {
|
||||
let nodes: Vec<(Type, CacheConstructor)> = vec![
|
||||
cache_node!(ImageTexture),
|
||||
cache_node!(VectorDataTable),
|
||||
cache_node!(GraphicGroupTable),
|
||||
cache_node!(GraphicElement),
|
||||
cache_node!(Artboard),
|
||||
cache_node!(RasterDataTable<CPU>),
|
||||
cache_node!(RasterDataTable<GPU>),
|
||||
cache_node!(graphene_core::instances::Instances<Artboard>),
|
||||
cache_node!(String),
|
||||
cache_node!(IVec2),
|
||||
cache_node!(DVec2),
|
||||
cache_node!(bool),
|
||||
cache_node!(f64),
|
||||
cache_node!(u32),
|
||||
cache_node!(u64),
|
||||
cache_node!(()),
|
||||
cache_node!(Vec<f64>),
|
||||
cache_node!(BlendMode),
|
||||
cache_node!(graphene_std::transform::ReferencePoint),
|
||||
cache_node!(graphene_path_bool::BooleanOperation),
|
||||
cache_node!(Option<Color>),
|
||||
cache_node!(graphene_core::vector::style::Fill),
|
||||
cache_node!(graphene_core::vector::style::StrokeCap),
|
||||
cache_node!(graphene_core::vector::style::StrokeJoin),
|
||||
cache_node!(graphene_core::vector::style::PaintOrder),
|
||||
cache_node!(graphene_core::vector::style::StrokeAlign),
|
||||
cache_node!(graphene_core::vector::style::Stroke),
|
||||
cache_node!(graphene_core::vector::style::Gradient),
|
||||
cache_node!(graphene_core::vector::style::GradientStops),
|
||||
cache_node!(Vec<graphene_core::uuid::NodeId>),
|
||||
cache_node!(Color),
|
||||
cache_node!(Box<graphene_core::vector::VectorModification>),
|
||||
cache_node!(graphene_std::vector::misc::CentroidType),
|
||||
cache_node!(graphene_std::vector::misc::PointSpacingType),
|
||||
];
|
||||
let mut monitor_nodes = HashMap::new();
|
||||
for (monitor_type, constructor) in nodes {
|
||||
monitor_nodes.insert(monitor_type, constructor);
|
||||
let mut cache_nodes = HashMap::new();
|
||||
for (cache_type, constructor) in nodes {
|
||||
cache_nodes.insert(cache_type, constructor);
|
||||
}
|
||||
monitor_nodes
|
||||
cache_nodes
|
||||
}
|
||||
|
||||
pub static MONITOR_NODES: Lazy<HashMap<Type, MonitorConstructor>> = Lazy::new(|| monitor_nodes());
|
||||
pub static CACHE_NODES: Lazy<HashMap<Type, CacheConstructor>> = Lazy::new(|| cache_nodes());
|
||||
|
||||
mod node_registry_macros {
|
||||
macro_rules! async_node {
|
||||
@@ -331,10 +331,10 @@ mod node_registry_macros {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! monitor_node {
|
||||
macro_rules! cache_node {
|
||||
($type:ty) => {
|
||||
(concrete!($type), |arg| {
|
||||
let node = <graphene_core::memo::MonitorNode<graphene_std::Context, _, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg));
|
||||
let node = <graphene_core::memo::MonitorMemoNode<_, _>>::new(graphene_std::registry::downcast_node::<graphene_std::Context, $type>(arg));
|
||||
let any: DynAnyNode<_, _, _> = graphene_std::any::DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox
|
||||
})
|
||||
@@ -342,7 +342,7 @@ mod node_registry_macros {
|
||||
}
|
||||
|
||||
pub(crate) use async_node;
|
||||
pub(crate) use cache_node;
|
||||
pub(crate) use convert_node;
|
||||
pub(crate) use into_node;
|
||||
pub(crate) use monitor_node;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::EditorMetadata;
|
||||
use graph_craft::document::value::RenderOutput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||
use graph_craft::generic;
|
||||
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||
use graph_craft::wasm_application_io::WasmApplicationIo;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::application_io::ApplicationIoValue;
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEditorApi>) -> NodeNetwork {
|
||||
pub fn wrap_network_in_scope(network: NodeNetwork, font_cache: Arc<FontCache>, editor_metadata: EditorMetadata, application_io: Arc<WasmApplicationIo>) -> NodeNetwork {
|
||||
let inner_network = DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(network),
|
||||
inputs: vec![],
|
||||
@@ -16,12 +20,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||
};
|
||||
|
||||
let render_node = DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::scope("editor-api")],
|
||||
inputs: vec![NodeInput::scope("application-io")],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
|
||||
skip_deduplication: true,
|
||||
@@ -35,9 +39,10 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||
},
|
||||
// TODO: Add conversion step
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(graphene_std::application_io::RenderConfig)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
inputs: vec![
|
||||
NodeInput::scope("editor-api"),
|
||||
NodeInput::scope("editor-metadata"),
|
||||
NodeInput::scope("application-io"),
|
||||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(T))), 0),
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
],
|
||||
@@ -58,9 +63,16 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||
let nodes = vec![inner_network, render_node];
|
||||
|
||||
NodeNetwork {
|
||||
// exports: vec![NodeInput::value(TaggedValue::RenderOutput(RenderOutput::default()), true)],
|
||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),
|
||||
scope_injections: [("editor-api".to_string(), TaggedValue::EditorApi(editor_api))].into_iter().collect(),
|
||||
scope_injections: [
|
||||
("font-cache".to_string(), TaggedValue::FontCache(font_cache)),
|
||||
("editor-metadata".to_string(), TaggedValue::EditorMetadata(editor_metadata)),
|
||||
("application-io".to_string(), TaggedValue::ApplicationIo(Arc::new(ApplicationIoValue(Some(application_io))))),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
// TODO(TrueDoctor): check if it makes sense to set `generated` to `true`
|
||||
generated: false,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::sync::atomic::AtomicU64;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Comma;
|
||||
use syn::{Error, Ident, PatIdent, Token, WhereClause, WherePredicate, parse_quote};
|
||||
use syn::{Error, Ident, PatIdent, Token, TypeParamBound, WhereClause, WherePredicate, parse_quote};
|
||||
static NODE_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
|
||||
@@ -346,6 +346,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
|
||||
|
||||
let node_input_accessor = generate_node_input_references(parsed, fn_generics, &field_idents, &graphene_core, &identifier);
|
||||
|
||||
let context_dependencies = input.context_dependency.clone();
|
||||
Ok(quote! {
|
||||
/// Underlying implementation for [#struct_name]
|
||||
#[inline]
|
||||
@@ -373,10 +375,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
mod #mod_name {
|
||||
use super::*;
|
||||
use #graphene_core as gcore;
|
||||
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextDependency};
|
||||
use gcore::value::ClonedNode;
|
||||
use gcore::ops::TypeNode;
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, NODE_CONTEXT_DEPENDENCY, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
||||
use gcore::ctor::ctor;
|
||||
|
||||
// Use the types specified in the implementation
|
||||
@@ -429,6 +431,17 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
||||
};
|
||||
NODE_METADATA.lock().unwrap().insert(#identifier(), metadata);
|
||||
}
|
||||
|
||||
#[cfg_attr(not(target_arch = "wasm32"), ctor)]
|
||||
fn register_context_dependency() {
|
||||
let mut context_dependency = NODE_CONTEXT_DEPENDENCY.lock().unwrap();
|
||||
context_dependency.insert(
|
||||
#identifier,
|
||||
vec![
|
||||
#(ContextDependency::#context_dependencies,)*
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -602,7 +615,6 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||
));
|
||||
}
|
||||
let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);
|
||||
|
||||
Ok(quote! {
|
||||
|
||||
#[cfg_attr(not(target_arch = "wasm32"), ctor)]
|
||||
@@ -615,11 +627,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[unsafe(no_mangle)]
|
||||
extern "C" fn #registry_name() {
|
||||
register_node();
|
||||
register_metadata();
|
||||
register_context_dependency();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ pub(crate) struct Input {
|
||||
pub(crate) pat_ident: PatIdent,
|
||||
pub(crate) ty: Type,
|
||||
pub(crate) implementations: Punctuated<Type, Comma>,
|
||||
pub(crate) context_dependency: Vec<proc_macro2::TokenStream>,
|
||||
}
|
||||
|
||||
impl Parse for Implementation {
|
||||
@@ -350,6 +351,7 @@ fn parse_inputs(inputs: &Punctuated<FnArg, Comma>) -> syn::Result<(Input, Vec<Pa
|
||||
pat_ident,
|
||||
ty: (**ty).clone(),
|
||||
implementations,
|
||||
context_dependency: Vec::new(),
|
||||
});
|
||||
} 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)))?;
|
||||
@@ -630,6 +632,24 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||
impl ParsedNodeFn {
|
||||
fn replace_impl_trait_in_input(&mut self) {
|
||||
if let Type::ImplTrait(impl_trait) = self.input.ty.clone() {
|
||||
let mut dependency_tokens = Vec::new();
|
||||
for bound in &impl_trait.bounds {
|
||||
if let syn::TypeParamBound::Trait(trait_bound) = bound {
|
||||
if let Some(ident) = trait_bound.path.get_ident() {
|
||||
match ident.to_string().as_str() {
|
||||
"ExtractFootprint" => dependency_tokens.push(quote::quote! {ExtractFootprint}),
|
||||
"ExtractDownstreamTransform" => dependency_tokens.push(quote::quote! {ExtractDownstreamTransform}),
|
||||
"ExtractRealTime" => dependency_tokens.push(quote::quote! {ExtractRealTime}),
|
||||
"ExtractAnimationTime" => dependency_tokens.push(quote::quote! {ExtractAnimationTime}),
|
||||
"ExtractIndex" => dependency_tokens.push(quote::quote! {ExtractIndex}),
|
||||
"ExtractVarArgs" => dependency_tokens.push(quote::quote! {ExtractVarArgs}),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.input.context_dependency = dependency_tokens;
|
||||
|
||||
let ident = Ident::new("_Input", impl_trait.span());
|
||||
let mut bounds = impl_trait.bounds;
|
||||
bounds.push(parse_quote!('n));
|
||||
@@ -768,6 +788,7 @@ mod tests {
|
||||
pat_ident: pat_ident("a"),
|
||||
ty: parse_quote!(f64),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(f64),
|
||||
is_async: false,
|
||||
@@ -829,6 +850,7 @@ mod tests {
|
||||
pat_ident: pat_ident("footprint"),
|
||||
ty: parse_quote!(Footprint),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(T),
|
||||
is_async: false,
|
||||
@@ -901,6 +923,7 @@ mod tests {
|
||||
pat_ident: pat_ident("_"),
|
||||
ty: parse_quote!(impl Ctx),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(VectorData),
|
||||
is_async: false,
|
||||
@@ -958,6 +981,7 @@ mod tests {
|
||||
pat_ident: pat_ident("image"),
|
||||
ty: parse_quote!(RasterDataTable<P>),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(RasterDataTable<P>),
|
||||
is_async: false,
|
||||
@@ -1027,6 +1051,7 @@ mod tests {
|
||||
pat_ident: pat_ident("a"),
|
||||
ty: parse_quote!(f64),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(f64),
|
||||
is_async: false,
|
||||
@@ -1084,6 +1109,7 @@ mod tests {
|
||||
pat_ident: pat_ident("api"),
|
||||
ty: parse_quote!(&WasmEditorApi),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(RasterDataTable<CPU>),
|
||||
is_async: true,
|
||||
@@ -1141,6 +1167,7 @@ mod tests {
|
||||
pat_ident: pat_ident("input"),
|
||||
ty: parse_quote!(i32),
|
||||
implementations: Punctuated::new(),
|
||||
context_dependency: Vec::new(),
|
||||
},
|
||||
output_type: parse_quote!(i32),
|
||||
is_async: false,
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
pub use context::Context;
|
||||
use dyn_any::StaticType;
|
||||
use glam::UVec2;
|
||||
use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle};
|
||||
use graphene_application_io::{ApplicationIo, ApplicationIoValue, SurfaceHandle};
|
||||
use graphene_core::{Color, Ctx};
|
||||
pub use graphene_svg_renderer::RenderContext;
|
||||
use std::sync::Arc;
|
||||
@@ -23,9 +23,9 @@ impl std::fmt::Debug for WgpuExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &'a WgpuExecutor {
|
||||
fn from(editor_api: &'a EditorApi<T>) -> Self {
|
||||
editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap()
|
||||
impl<'a, Io: ApplicationIo<Executor = WgpuExecutor>> From<&'a Arc<ApplicationIoValue<Io>>> for &'a WgpuExecutor {
|
||||
fn from(application_io: &'a Arc<ApplicationIoValue<Io>>) -> Self {
|
||||
application_io.0.as_ref().unwrap().gpu_executor().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,8 @@ impl WgpuExecutor {
|
||||
pub type WindowHandle = Arc<SurfaceHandle<Window>>;
|
||||
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn create_gpu_surface<'a: 'n, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi<Io>) -> Option<WgpuSurface> {
|
||||
let canvas = editor_api.application_io.as_ref()?.window()?;
|
||||
let executor = editor_api.application_io.as_ref()?.gpu_executor()?;
|
||||
fn create_gpu_surface<'a: 'n, Io: ApplicationIo<Executor = WgpuExecutor, Surface = Window> + 'a + Send + Sync>(_: impl Ctx + 'a, application_io: Arc<ApplicationIoValue<Io>>) -> Option<WgpuSurface> {
|
||||
let canvas = application_io.0.as_ref()?.window()?;
|
||||
let executor = application_io.0.as_ref()?.gpu_executor()?;
|
||||
Some(Arc::new(executor.create_surface(canvas).ok()?))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user