Add automatic type conversion and the node graph preprocessor (#2478)

* Prototype document network level into node insertion

* Implement Convert trait / node for places we can't use Into

* Add isize/usize and i128/u128 implementations for Convert trait

* Factor out substitutions into preprocessor crate

* Simplify layer node further

* Code review

* Mark preprocessed networks as generated

* Revert changes to layer node definition

* Skip generated flag for serialization

* Don't expand for tests

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2025-06-27 01:10:14 +02:00
committed by GitHub
parent 86da69e33f
commit a40a760f27
15 changed files with 484 additions and 128 deletions

View File

@@ -15,7 +15,7 @@ use graphene_std::GraphicElement;
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::wasm_application_io::*;
use node_registry_macros::{async_node, into_node};
use node_registry_macros::{async_node, convert_node, into_node};
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Arc;
@@ -23,10 +23,7 @@ use wgpu_executor::{WgpuExecutor, WgpuSurface, WindowHandle};
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: f64, to: f64),
into_node!(from: u32, to: f64),
into_node!(from: u8, to: u32),
let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: VectorDataTable, to: VectorDataTable),
into_node!(from: VectorDataTable, to: GraphicElement),
into_node!(from: VectorDataTable, to: GraphicGroupTable),
@@ -35,6 +32,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
into_node!(from: RasterDataTable<CPU>, to: RasterDataTable<CPU>),
// into_node!(from: RasterDataTable<CPU>, to: RasterDataTable<SRGBA8>),
into_node!(from: RasterDataTable<CPU>, to: GraphicElement),
into_node!(from: RasterDataTable<GPU>, to: GraphicElement),
into_node!(from: RasterDataTable<CPU>, to: GraphicGroupTable),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable<CPU>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
@@ -137,6 +135,26 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
},
),
];
node_types.extend(
[
convert_node!(from: f32, to: numbers),
convert_node!(from: f64, to: numbers),
convert_node!(from: i8, to: numbers),
convert_node!(from: u8, to: numbers),
convert_node!(from: u16, to: numbers),
convert_node!(from: i16, to: numbers),
convert_node!(from: i32, to: numbers),
convert_node!(from: u32, to: numbers),
convert_node!(from: i64, to: numbers),
convert_node!(from: u64, to: numbers),
convert_node!(from: i128, to: numbers),
convert_node!(from: u128, to: numbers),
convert_node!(from: isize, to: numbers),
convert_node!(from: usize, to: numbers),
]
.into_iter()
.flatten(),
);
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
@@ -151,12 +169,14 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
// This occurs for the ChannelMixerNode presumably because of the long name.
// This might be caused by the stringify! macro
let mut new_name = id.name.replace('\n', " ");
// Remove struct generics for all nodes except for the IntoNode
if !new_name.contains("IntoNode") {
// Remove struct generics for all nodes except for the IntoNode and ConvertNode
if !(new_name.contains("IntoNode") || new_name.contains("ConvertNode")) {
if let Some((path, _generics)) = new_name.split_once("<") {
new_name = path.to_string();
}
}
let nid = ProtoNodeIdentifier { name: Cow::Owned(new_name) };
map.entry(nid).or_default().insert(types.clone(), c);
}
@@ -203,9 +223,8 @@ mod node_registry_macros {
(from: $from:ty, to: $to:ty) => {
(
ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]),
|mut args| {
|_| {
Box::pin(async move {
args.reverse();
let node = graphene_core::ops::IntoNode::<$to>::new();
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
@@ -220,7 +239,47 @@ mod node_registry_macros {
)
};
}
macro_rules! convert_node {
(from: $from:ty, to: numbers) => {{
let x: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
convert_node!(from: $from, to: f32),
convert_node!(from: $from, to: f64),
convert_node!(from: $from, to: i8),
convert_node!(from: $from, to: u8),
convert_node!(from: $from, to: u16),
convert_node!(from: $from, to: i16),
convert_node!(from: $from, to: i32),
convert_node!(from: $from, to: u32),
convert_node!(from: $from, to: i64),
convert_node!(from: $from, to: u64),
convert_node!(from: $from, to: i128),
convert_node!(from: $from, to: u128),
convert_node!(from: $from, to: isize),
convert_node!(from: $from, to: usize),
];
x
}};
(from: $from:ty, to: $to:ty) => {
(
ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]),
|_| {
Box::pin(async move {
let node = graphene_core::ops::ConvertNode::<$to>::new();
let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node);
Box::new(any) as TypeErasedBox
})
},
{
let node = graphene_core::ops::ConvertNode::<$to>::new();
let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]);
node_io.call_argument = future!(<$from as StaticType>::Static);
node_io
},
)
};
}
pub(crate) use async_node;
pub(crate) use convert_node;
pub(crate) use into_node;
}

View File

@@ -78,5 +78,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
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(), (NodeId(2), concrete!(&WasmEditorApi)))].into_iter().collect(),
// TODO(TrueDoctor): check if it makes sense to set `generated` to `true`
generated: false,
}
}