Make the dynamic node graph execution asynchronous (#1218)

* Make node graph execution async

Make node macro generate async node implementations

Start propagating async through the node system

Async checkpoint

Make Any<'i> Send + Sync

Determine node io type using panic node

Fix types for raster_node macro

Finish porting node registry?

Fix lifetime errors

Remove Send + Sync requirements and start making node construction async

Async MVP

Fix tests

Clippy fix

* Fix nodes

* Simplify lifetims for node macro + make node macro more modular

* Reenable more nodes

* Fix pasting images

* Remove http test from brush node

* Fix output type for cache node

* Fix types for let scope

* Fix formatting
This commit is contained in:
Dennis Kobert 2023-05-27 11:48:57 +02:00 committed by Keavon Chambers
parent 5c7211cb30
commit 4bd9fbd073
40 changed files with 834 additions and 471 deletions

View file

@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, ToTokens};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse_macro_input, punctuated::Punctuated, token::Comma, FnArg, GenericParam, Ident, ItemFn, Lifetime, Pat, PatIdent, PathArguments, PredicateType, ReturnType, Token, TraitBound, Type, TypeParam,
TypeParamBound, WhereClause, WherePredicate,
@ -8,14 +8,69 @@ use syn::{
#[proc_macro_attribute]
pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
//let node_name = parse_macro_input!(attr as Ident);
let mut imp = node_impl_proxy(attr.clone(), item.clone());
let new = node_new_impl(attr, item);
imp.extend(new);
imp
}
#[proc_macro_attribute]
pub fn node_new(attr: TokenStream, item: TokenStream) -> TokenStream {
node_new_impl(attr, item)
}
#[proc_macro_attribute]
pub fn node_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
node_impl_proxy(attr, item)
}
fn node_new_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
let node = parse_macro_input!(attr as syn::PathSegment);
let function = parse_macro_input!(item as ItemFn);
let node = &node;
let node_name = &node.ident;
let mut args = match node.arguments.clone() {
let mut args = args(node);
let arg_idents = args
.iter()
.filter(|x| x.to_token_stream().to_string().starts_with('_'))
.map(|arg| Ident::new(arg.to_token_stream().to_string().to_lowercase().as_str(), Span::call_site()))
.collect::<Vec<_>>();
let (_, _, parameter_pat_ident_patterns) = parse_inputs(&function);
let parameter_idents = parameter_pat_ident_patterns.iter().map(|pat_ident| &pat_ident.ident).collect::<Vec<_>>();
// Extract the output type of the entire node - `()` by default
let struct_generics = (0..parameter_pat_ident_patterns.len())
.map(|x| {
let ident = format_ident!("S{x}");
ident
})
.collect::<Punctuated<_, Comma>>();
for ident in struct_generics.iter() {
args.push(Type::Verbatim(quote::quote!(#ident)));
}
let struct_generics_iter = struct_generics.iter();
quote::quote! {
#[automatically_derived]
impl <#(#args),*> #node_name<#(#args),*>
{
pub const fn new(#(#parameter_idents: #struct_generics_iter),*) -> Self{
Self{
#(#parameter_idents,)*
#(#arg_idents: core::marker::PhantomData,)*
}
}
}
}
.into()
}
fn args(node: &syn::PathSegment) -> Vec<Type> {
match node.arguments.clone() {
PathArguments::AngleBracketed(args) => args
.args
.into_iter()
@ -25,53 +80,59 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
})
.collect::<Vec<_>>(),
_ => Default::default(),
}
}
fn node_impl_proxy(attr: TokenStream, item: TokenStream) -> TokenStream {
let fn_item = item.clone();
let function = parse_macro_input!(fn_item as ItemFn);
let sync_input = if function.sig.asyncness.is_some() {
node_impl_impl(attr, item, Asyncness::AllAsync)
} else {
node_impl_impl(attr, item, Asyncness::Sync)
};
let arg_idents = args
.iter()
.filter(|x| x.to_token_stream().to_string().starts_with('_'))
.map(|arg| Ident::new(arg.to_token_stream().to_string().to_lowercase().as_str(), Span::call_site()))
.collect::<Vec<_>>();
sync_input
}
enum Asyncness {
Sync,
AsyncOut,
AllAsync,
}
let mut function_inputs = function.sig.inputs.iter().filter_map(|arg| if let FnArg::Typed(typed_arg) = arg { Some(typed_arg) } else { None });
fn node_impl_impl(attr: TokenStream, item: TokenStream, asyncness: Asyncness) -> TokenStream {
//let node_name = parse_macro_input!(attr as Ident);
let node = parse_macro_input!(attr as syn::PathSegment);
let function = parse_macro_input!(item as ItemFn);
let node = &node;
let node_name = &node.ident;
let mut args = args(node);
let async_out = match asyncness {
Asyncness::Sync => false,
Asyncness::AsyncOut | Asyncness::AllAsync => true,
};
let async_in = matches!(asyncness, Asyncness::AllAsync);
let body = &function.block;
let mut type_generics = function.sig.generics.params.clone();
let mut where_clause = function.sig.generics.where_clause.clone().unwrap_or(WhereClause {
where_token: Token![where](Span::call_site()),
predicates: Default::default(),
});
// Extract primary input as first argument
let primary_input = function_inputs.next().expect("Primary input required - set to `()` if not needed.");
type_generics.iter_mut().for_each(|x| {
if let GenericParam::Type(t) = x {
t.bounds.insert(0, TypeParamBound::Lifetime(Lifetime::new("'input", Span::call_site())));
}
});
let (primary_input, parameter_inputs, parameter_pat_ident_patterns) = parse_inputs(&function);
let primary_input_ty = &primary_input.ty;
let Pat::Ident(PatIdent{ident: primary_input_ident, mutability: primary_input_mutability,..} ) =&*primary_input.pat else {
panic!("Expected ident as primary input.");
};
let primary_input_ty = &primary_input.ty;
let aux_type_generics = type_generics
.iter()
.filter(|gen| {
if let GenericParam::Type(ty) = gen {
!function.sig.inputs.iter().take(1).any(|param_ty| match param_ty {
FnArg::Typed(pat_ty) => ty.ident == pat_ty.ty.to_token_stream().to_string(),
_ => false,
})
} else {
false
}
})
.cloned()
.collect::<Vec<_>>();
let body = function.block;
// Extract secondary inputs as all other arguments
let parameter_inputs = function_inputs.collect::<Vec<_>>();
let parameter_pat_ident_patterns = parameter_inputs
.iter()
.map(|input| {
let Pat::Ident(pat_ident) = &*input.pat else { panic!("Expected ident for secondary input."); };
pat_ident
})
.collect::<Vec<_>>();
let parameter_idents = parameter_pat_ident_patterns.iter().map(|pat_ident| &pat_ident.ident).collect::<Vec<_>>();
let parameter_mutability = parameter_pat_ident_patterns.iter().map(|pat_ident| &pat_ident.mutability);
@ -82,20 +143,94 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
quote::quote!(())
};
let struct_generics = (0..parameter_inputs.len())
.map(|x| {
let ident = format_ident!("S{x}");
ident
})
.collect::<Punctuated<_, Comma>>();
let struct_generics_iter = struct_generics.iter();
let struct_generics = (0..parameter_pat_ident_patterns.len()).map(|x| format_ident!("S{x}")).collect::<Vec<_>>();
let future_generics = (0..parameter_pat_ident_patterns.len()).map(|x| format_ident!("F{x}")).collect::<Vec<_>>();
let future_types = future_generics.iter().map(|x| Type::Verbatim(x.to_token_stream())).collect::<Vec<_>>();
let parameter_types = parameter_inputs.iter().map(|x| *x.ty.clone()).collect::<Vec<Type>>();
for ident in struct_generics.iter() {
args.push(Type::Verbatim(quote::quote!(#ident)));
}
// Generics are simply `S0` through to `Sn-1` where n is the number of secondary inputs
let node_generics = struct_generics
let node_generics = construct_node_generics(&struct_generics);
let future_generic_params = construct_node_generics(&future_generics);
let generics = if async_in {
type_generics
.into_iter()
.chain(node_generics.iter().cloned())
.chain(future_generic_params.iter().cloned())
.collect::<Punctuated<_, Comma>>()
} else {
type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>()
};
// Bindings for all of the above generics to a node with an input of `()` and an output of the type in the function
let node_bounds = if async_in {
let mut node_bounds = input_node_bounds(future_types, node_generics, |ty| quote! {Node<'input, (), Output = #ty>});
let future_bounds = input_node_bounds(parameter_types, future_generic_params, |ty| quote! { core::future::Future<Output = #ty>});
node_bounds.extend(future_bounds);
node_bounds
} else {
input_node_bounds(parameter_types, node_generics, |ty| quote! {Node<'input, (), Output = #ty>})
};
where_clause.predicates.extend(node_bounds);
let output = if async_out {
quote::quote!(core::pin::Pin<Box<dyn core::future::Future< Output = #output> + 'input>>)
} else {
quote::quote!(#output)
};
let parameters = if matches!(asyncness, Asyncness::AllAsync) {
quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(()).await;)*)
} else {
quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(());)*)
};
let mut body_with_inputs = quote::quote!(
#parameters
{#body}
);
if async_out {
body_with_inputs = quote::quote!(Box::pin(async move { #body_with_inputs }));
}
quote::quote! {
#[automatically_derived]
impl <'input, #generics> Node<'input, #primary_input_ty> for #node_name<#(#args),*>
#where_clause
{
type Output = #output;
#[inline]
fn eval(&'input self, #primary_input_mutability #primary_input_ident: #primary_input_ty) -> Self::Output {
#body_with_inputs
}
}
}
.into()
}
fn parse_inputs(function: &ItemFn) -> (&syn::PatType, Vec<&syn::PatType>, Vec<&PatIdent>) {
let mut function_inputs = function.sig.inputs.iter().filter_map(|arg| if let FnArg::Typed(typed_arg) = arg { Some(typed_arg) } else { None });
// Extract primary input as first argument
let primary_input = function_inputs.next().expect("Primary input required - set to `()` if not needed.");
// Extract secondary inputs as all other arguments
let parameter_inputs = function_inputs.collect::<Vec<_>>();
let parameter_pat_ident_patterns = parameter_inputs
.iter()
.map(|input| {
let Pat::Ident(pat_ident) = &*input.pat else { panic!("Expected ident for secondary input."); };
pat_ident
})
.collect::<Vec<_>>();
(primary_input, parameter_inputs, parameter_pat_ident_patterns)
}
fn construct_node_generics(struct_generics: &[Ident]) -> Vec<GenericParam> {
struct_generics
.iter()
.cloned()
.map(|ident| {
@ -108,22 +243,17 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
default: None,
})
})
.collect::<Punctuated<_, Comma>>();
type_generics.iter_mut().for_each(|x| {
if let GenericParam::Type(t) = x {
t.bounds.insert(0, TypeParamBound::Lifetime(Lifetime::new("'input", Span::call_site())));
}
});
let generics = type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>();
let new_fn_generics = aux_type_generics.into_iter().chain(node_generics.iter().cloned()).collect::<Punctuated<_, Comma>>();
// Bindings for all of the above generics to a node with an input of `()` and an output of the type in the function
let extra_where_clause = parameter_inputs
.collect()
}
fn input_node_bounds(parameter_inputs: Vec<Type>, node_generics: Vec<GenericParam>, trait_bound: impl Fn(Type) -> proc_macro2::TokenStream) -> Vec<WherePredicate> {
parameter_inputs
.iter()
.zip(&node_generics)
.map(|(ty, name)| {
let ty = &ty.ty;
let GenericParam::Type(generic_ty) = name else { panic!("Expected type generic."); };
let ident = &generic_ty.ident;
let bound = trait_bound(ty.clone());
WherePredicate::Type(PredicateType {
lifetimes: None,
bounded_ty: Type::Verbatim(ident.to_token_stream()),
@ -131,43 +261,10 @@ pub fn node_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
bounds: Punctuated::from_iter([TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: syn::TraitBoundModifier::None,
lifetimes: syn::parse_quote!(for<'any_input>),
path: syn::parse_quote!(Node<'any_input, (), Output = #ty>),
lifetimes: None, //syn::parse_quote!(for<'any_input>),
path: syn::parse_quote!(#bound),
})]),
})
})
.collect::<Vec<_>>();
where_clause.predicates.extend(extra_where_clause.clone());
let input_lifetime = if generics.is_empty() { quote::quote!() } else { quote::quote!('input,) };
quote::quote! {
impl <'input, #generics> Node<'input, #primary_input_ty> for #node_name<#(#args),*>
#where_clause
{
type Output = #output;
#[inline]
fn eval(&'input self, #primary_input_mutability #primary_input_ident: #primary_input_ty) -> Self::Output {
#(
let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(());
)*
#body
}
}
impl <#input_lifetime #new_fn_generics> #node_name<#(#args),*>
where #(#extra_where_clause),*
{
pub const fn new(#(#parameter_idents: #struct_generics_iter),*) -> Self{
Self{
#(#parameter_idents,)*
#(#arg_idents: core::marker::PhantomData,)*
}
}
}
}
.into()
.collect()
}