Restructure node graph execution to be safer (#1277)

* Reorganize file structure

* Remove all unsafe code

* Add testcase for debugging ub

* Convert into proper test with fail condition

* General cleanup

* Fix tests

* Add feature guard for deallocation

* Use raw pointer for storing values to avoid violating aliasing rules

* Add comment explaining the disabling of simd128

* Fix brush node

* Fix formatting
This commit is contained in:
Dennis Kobert
2023-06-03 01:18:44 +02:00
committed by GitHub
parent b7f5ac4af5
commit 8c20624dae
29 changed files with 363 additions and 299 deletions

View File

@@ -27,3 +27,4 @@ serde = { version = "1", features = ["derive"], optional = true }
glam = { version = "0.22" }
once_cell = "1.17.0"
futures = "0.3.28"
typed-arena = "2.0.2"

View File

@@ -1,18 +1,17 @@
use std::collections::{HashMap, HashSet};
use std::error::Error;
use std::sync::{Arc, RwLock};
use std::sync::Arc;
use dyn_any::StaticType;
use graph_craft::document::value::{TaggedValue, UpcastNode};
use graph_craft::document::NodeId;
use graph_craft::executor::Executor;
use graph_craft::proto::{ConstructionArgs, LocalFuture, ProtoNetwork, ProtoNode, TypingContext};
use graph_craft::graphene_compiler::Executor;
use graph_craft::proto::{ConstructionArgs, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, TypeErasedBox, TypingContext};
use graph_craft::Type;
use graphene_std::any::{TypeErasedPinned, TypeErasedPinnedRef};
use crate::node_registry;
#[derive(Debug, Clone)]
pub struct DynamicExecutor {
output: NodeId,
tree: BorrowTree,
@@ -79,45 +78,14 @@ impl<'a, I: StaticType + 'a> Executor<I, TaggedValue> for &'a DynamicExecutor {
}
}
pub struct NodeContainer<'n> {
pub node: TypeErasedPinned<'n>,
// the dependencies are only kept to ensure that the nodes are not dropped while still in use
_dependencies: Vec<Arc<RwLock<NodeContainer<'static>>>>,
}
impl<'a> core::fmt::Debug for NodeContainer<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeContainer").finish()
}
}
impl<'a> NodeContainer<'a> {
pub fn new(node: TypeErasedPinned<'a>, _dependencies: Vec<Arc<RwLock<NodeContainer<'static>>>>) -> Self {
Self { node, _dependencies }
}
/// Return a static reference to the TypeErasedNode
/// # Safety
/// This is unsafe because the returned reference is only valid as long as the NodeContainer is alive
pub unsafe fn erase_lifetime(self) -> NodeContainer<'static> {
std::mem::transmute(self)
}
}
impl NodeContainer<'static> {
pub unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> {
let s = &*(self as *const Self);
*(&s.node.as_ref() as *const TypeErasedPinnedRef<'static>)
}
}
#[derive(Default, Debug, Clone)]
#[derive(Default)]
pub struct BorrowTree {
nodes: HashMap<NodeId, Arc<RwLock<NodeContainer<'static>>>>,
nodes: HashMap<NodeId, Arc<NodeContainer>>,
source_map: HashMap<Vec<NodeId>, NodeId>,
}
impl BorrowTree {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<Self, String> {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, String> {
let mut nodes = BorrowTree::default();
for (id, node) in proto_network.nodes {
nodes.push_node(id, node, typing_context).await?
@@ -133,9 +101,7 @@ impl BorrowTree {
self.push_node(id, node, typing_context).await?;
} else {
let Some(node_container) = self.nodes.get_mut(&id) else { continue };
let mut node_container_writer = node_container.write().unwrap();
let node = node_container_writer.node.as_mut();
node.reset();
node_container.reset();
}
old_nodes.remove(&id);
}
@@ -143,40 +109,32 @@ impl BorrowTree {
Ok(old_nodes.into_iter().collect())
}
fn node_refs(&self, nodes: &[NodeId]) -> Vec<TypeErasedPinnedRef<'static>> {
self.node_deps(nodes).into_iter().map(|node| unsafe { node.read().unwrap().static_ref() }).collect()
}
fn node_deps(&self, nodes: &[NodeId]) -> Vec<Arc<RwLock<NodeContainer<'static>>>> {
fn node_deps(&self, nodes: &[NodeId]) -> Vec<Arc<NodeContainer>> {
nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect()
}
fn store_node(&mut self, node: Arc<RwLock<NodeContainer<'static>>>, id: NodeId) -> Arc<RwLock<NodeContainer<'static>>> {
self.nodes.insert(id, node.clone());
node
fn store_node(&mut self, node: Arc<NodeContainer>, id: NodeId) {
self.nodes.insert(id, node);
}
pub fn introspect(&self, node_path: &[NodeId]) -> Option<Option<Arc<dyn std::any::Any>>> {
let id = self.source_map.get(node_path)?;
let node = self.nodes.get(id)?;
let reader = node.read().unwrap();
let node = reader.node.as_ref();
Some(node.serialize())
}
pub fn get(&self, id: NodeId) -> Option<Arc<RwLock<NodeContainer<'static>>>> {
pub fn get(&self, id: NodeId) -> Option<Arc<NodeContainer>> {
self.nodes.get(&id).cloned()
}
pub async fn eval<'i, I: StaticType + 'i, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option<O> {
let node = self.nodes.get(&id).cloned()?;
let reader = node.read().unwrap();
let output = reader.node.eval(Box::new(input));
let output = node.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
}
pub async fn eval_tagged_value<'i, I: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Result<TaggedValue, String> {
let node = self.nodes.get(&id).cloned().ok_or_else(|| "Output node not found in executor")?;
let reader = node.read().unwrap();
let output = reader.node.eval(Box::new(input));
let node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = node.eval(Box::new(input));
TaggedValue::try_from_any(output.await)
}
@@ -196,23 +154,18 @@ impl BorrowTree {
match construction_args {
ConstructionArgs::Value(value) => {
let upcasted = UpcastNode::new(value);
let node = Box::pin(upcasted) as TypeErasedPinned<'_>;
let node = NodeContainer { node, _dependencies: vec![] };
let node = unsafe { node.erase_lifetime() };
self.store_node(Arc::new(node.into()), id);
let node = Box::new(upcasted) as TypeErasedBox<'_>;
let node = NodeContainer::new(node);
self.store_node(node.into(), id);
}
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
ConstructionArgs::Nodes(ids) => {
let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect();
let construction_nodes = self.node_refs(&ids);
let construction_nodes = self.node_deps(&ids);
let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?;
let node = constructor(construction_nodes).await;
let node = NodeContainer {
node,
_dependencies: self.node_deps(&ids),
};
let node = unsafe { node.erase_lifetime() };
self.store_node(Arc::new(node.into()), id);
let node = NodeContainer::new(node);
self.store_node(node.into(), id);
}
};
Ok(())

View File

@@ -1,7 +1,4 @@
#[macro_use]
extern crate log;
pub mod executor;
pub mod dynamic_executor;
pub mod node_registry;
#[cfg(test)]
@@ -72,8 +69,8 @@ mod tests {
..Default::default()
};
use crate::executor::DynamicExecutor;
use graph_craft::executor::{Compiler, Executor};
use crate::dynamic_executor::DynamicExecutor;
use graph_craft::graphene_compiler::{Compiler, Executor};
let compiler = Compiler {};
let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
@@ -120,12 +117,12 @@ mod tests {
..Default::default()
};
use crate::executor::DynamicExecutor;
use graph_craft::executor::Compiler;
use crate::dynamic_executor::DynamicExecutor;
use graph_craft::graphene_compiler::Compiler;
let compiler = Compiler {};
let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
let _exec = block_on(DynamicExecutor::new(protograph)).map(|e| panic!("The network should not type check: {:#?}", e)).unwrap_err();
let _exec = block_on(DynamicExecutor::new(protograph)).map(|e| panic!("The network should not type check ")).unwrap_err();
}
}

View File

@@ -1,7 +1,7 @@
use graph_craft::proto::{NodeConstructor, TypeErasedPinned};
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::ops::IdNode;
use graphene_core::quantization::QuantizationChannels;
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
use graphene_core::raster::color::Color;
use graphene_core::structural::Then;
use graphene_core::value::{ClonedNode, CopiedNode, ValueNode};
@@ -14,8 +14,7 @@ use graphene_core::{fn_type, raster::*};
use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor};
use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::brush;
use graphene_std::raster::BlendImageTupleNode;
use graphene_std::raster::*;
use dyn_any::StaticType;
@@ -51,7 +50,7 @@ macro_rules! register_node {
let node = construct_node!(args, $path, [$($type),*]).await;
let node = graphene_std::any::FutureWrapperNode::new(node);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
Box::new(any) as TypeErasedBox
})
},
{
@@ -79,7 +78,7 @@ macro_rules! async_node {
args.reverse();
let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*);
let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
{
@@ -118,7 +117,7 @@ macro_rules! raster_node {
let node = construct_node!(args, $path, [$($type),*]).await;
let node = graphene_std::any::FutureWrapperNode::new(node);
let any: DynAnyNode<Color, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
{
@@ -134,7 +133,7 @@ macro_rules! raster_node {
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<Image<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
{
@@ -150,7 +149,7 @@ macro_rules! raster_node {
let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node));
let map_node = graphene_std::any::FutureWrapperNode::new(map_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
{
@@ -170,7 +169,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
//register_node!(graphene_core::ops::IdNode, input: Any<'_>, params: []),
vec![(
NodeIdentifier::new("graphene_core::ops::IdNode"),
|_| Box::pin(async move { Box::pin(FutureWrapperNode::new(IdNode::new())) as TypeErasedPinned }),
|_| Box::pin(async move { FutureWrapperNode::new(IdNode::new()).into_type_erased() }),
NodeIOTypes::new(generic!(I), generic!(I), vec![]),
)],
// TODO: create macro to impl for all types
@@ -205,10 +204,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
use graphene_core::raster::*;
use graphene_core::value::*;
let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0]).eval(()).await;
let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1]).eval(()).await;
let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2]).eval(()).await;
let channel_a: ImageFrame<Color> = DowncastBothNode::new(args[3]).eval(()).await;
let channel_r: ImageFrame<Color> = DowncastBothNode::new(args[0].clone()).eval(()).await;
let channel_g: ImageFrame<Color> = DowncastBothNode::new(args[1].clone()).eval(()).await;
let channel_b: ImageFrame<Color> = DowncastBothNode::new(args[2].clone()).eval(()).await;
let channel_a: ImageFrame<Color> = DowncastBothNode::new(args[3].clone()).eval(()).await;
let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red));
let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green));
@@ -232,7 +231,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
let final_image = FutureWrapperNode::new(final_image);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
NodeIOTypes::new(
@@ -256,11 +255,11 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode<_>"),
|args| {
Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0]);
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
//let document_node = ClonedNode::new(document_node.eval(()));
let node = graphene_std::executor::MapGpuNode::new(document_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(graph_craft::document::DocumentNode)]),
@@ -270,13 +269,13 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_std::executor::BlendGpuImageNode<_, _, _>"),
|args| {
Box::pin(async move {
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]);
let background: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2].clone());
let node = graphene_std::executor::BlendGpuImageNode::new(background, blend_mode, opacity);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
NodeIOTypes::new(
@@ -289,7 +288,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
|args| {
Box::pin(async move {
let node = ComposeTypeErased::new(args[0], args[1]);
let node = ComposeTypeErased::new(args[0].clone(), args[1].clone());
node.into_type_erased()
})
},
@@ -300,99 +299,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
),
)],
register_node!(graphene_std::brush::IntoIterNode<_>, input: &Vec<BrushStroke>, params: []),
vec![(
NodeIdentifier::new("graphene_std::brush::BrushNode"),
|args| {
use graphene_core::structural::*;
use graphene_core::value::*;
use graphene_std::brush::*;
Box::pin(async move {
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let bounds: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[1]);
let strokes: DowncastBothNode<(), Vec<BrushStroke>> = DowncastBothNode::new(args[2]);
let image_val = image.eval(()).await;
let strokes_val = strokes.eval(()).await;
let stroke_bbox = strokes_val.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let image_bbox = Bbox::from_transform(image_val.transform).to_axis_aligned_bbox();
let bbox = stroke_bbox.union(&image_bbox);
let mut background_bounds = CopiedNode::new(bbox.to_transform());
let bounds_transform = bounds.eval(()).await.transform;
if bounds_transform != DAffine2::ZERO {
background_bounds = CopiedNode::new(bounds_transform);
}
let has_erase_strokes = strokes_val.iter().any(|s| s.style.blend_mode == BlendMode::Erase);
let blank_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)));
let opaque_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::WHITE)));
let mut erase_restore_mask = has_erase_strokes.then(|| opaque_image.eval(()));
let mut actual_image = ExtendImageNode::new(blank_image).eval(image_val);
for stroke in strokes_val {
let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.));
// Create brush texture.
// TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes.
let brush_texture = brush::create_brush_texture(stroke.style.clone());
// Compute transformation from stroke texture space into layer space, and create the stroke texture.
let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect();
let mut bbox = stroke.bounding_box();
bbox.start = bbox.start.floor();
bbox.end = bbox.end.floor();
let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter);
// For numerical stability we want to place the first blit point at a stable, integer offset
// in layer space.
let snap_offset = positions[0].floor() - positions[0];
let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.);
let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size);
match stroke.style.blend_mode {
BlendMode::Erase => {
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = Some(blit_node.eval(mask));
}
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.));
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params));
erase_restore_mask = Some(blit_node.eval(mask));
}
}
blend_mode => {
let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend));
let empty_stroke_texture = EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(stroke_to_layer);
let stroke_texture = blit_node.eval(empty_stroke_texture);
// TODO: Is this the correct way to do opacity in blending?
actual_image = brush::blend_with_mode(actual_image, stroke_texture, blend_mode, stroke.style.color.a() * 100.);
}
}
}
if let Some(mask) = erase_restore_mask {
let blend_params = BlendNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.));
let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params));
actual_image = blend_executor.eval((actual_image, mask));
}
// TODO: there *has* to be a better way to do this.
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(FutureWrapperNode::new(ClonedNode::new(actual_image))));
Box::pin(any) as TypeErasedPinned
})
},
NodeIOTypes::new(
concrete!(()),
concrete!(ImageFrame<Color>),
vec![fn_type!(ImageFrame<Color>), fn_type!(ImageFrame<Color>), fn_type!(Vec<BrushStroke>)],
),
)],
async_node!(graphene_std::brush::BrushNode<_, _>, input: ImageFrame<Color>, output: ImageFrame<Color>, params: [ImageFrame<Color>, Vec<BrushStroke>]),
// Filters
raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]),
raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlue]),
@@ -404,13 +311,13 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_core::raster::BlendNode<_, _, _, _>"),
|args| {
Box::pin(async move {
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0]);
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]);
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]);
let image: DowncastBothNode<(), ImageFrame<Color>> = DowncastBothNode::new(args[0].clone());
let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone());
let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone());
let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await));
let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node)));
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
NodeIOTypes::new(
@@ -438,24 +345,24 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
Box::pin(async move {
use graphene_core::raster::brightness_contrast::*;
let brightness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[0]);
let brightness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[0].clone());
let brightness = ClonedNode::new(brightness.eval(()).await as f32);
let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]);
let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1].clone());
let contrast = ClonedNode::new(contrast.eval(()).await as f32);
let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2]);
let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2].clone());
if use_legacy.eval(()).await {
let generate_brightness_contrast_legacy_mapper_node = GenerateBrightnessContrastLegacyMapperNode::new(brightness, contrast);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
} else {
let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast);
let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(())));
let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
}
})
},
@@ -495,10 +402,10 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
NodeIdentifier::new("graphene_core::memo::RefNode<_, _>"),
|args| {
Box::pin(async move {
let node: DowncastBothNode<Option<graphene_core::EditorApi>, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0]);
let node: DowncastBothNode<Option<graphene_core::EditorApi>, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone());
let node = <graphene_core::memo::RefNode<_, _>>::new(node);
let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node));
Box::pin(any) as TypeErasedPinned
any.into_type_erased()
})
},
NodeIOTypes::new(