mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)
* Implement experimental Context struct and traits * Add Ctx super trait * Checkpoint * Return Any instead of DynAny * Fix send implementation for inputs with lifetimes * Port more nodes * Uncomment nodes * Port more nodes * Port vector nodes * Partial progress (the stuff I'm more sure about) * Partial progress (the stuff that's not compiling and I'm not sure about) * Fix more errors * First pass of fixing errors introduced by rebase * Port wasm application io * Fix brush node types * Add type annotation * Fix warnings and wasm compilation * Change types for Document Node definitions * Improve debugging for footprint not found errors * Forward context in append artboard node * Fix thumbnails * Fix loading most demo artwork * Wrap output type of all nodes in future * Encode futures as part of the type * Fix document node definitions for future types * Remove Clippy warnings * Fix more things * Fix opening demo art with manual composition upgrading * Set correct type for manual composition * Fix brush * Fix tests * Update docs for deps * Fix up some node signature issues * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
parent
0c1e96b9c6
commit
4ff2bdb04f
43 changed files with 1338 additions and 1545 deletions
|
@ -37,7 +37,6 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
|
||||
let struct_generics: Vec<Ident> = fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect();
|
||||
let input_ident = &input.pat_ident;
|
||||
let input_type = &input.ty;
|
||||
|
||||
let field_idents: Vec<_> = fields
|
||||
.iter()
|
||||
|
@ -78,12 +77,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
}
|
||||
};
|
||||
|
||||
let mut future_idents = Vec::new();
|
||||
|
||||
let field_types: Vec<_> = fields
|
||||
.iter()
|
||||
.map(|field| match field {
|
||||
ParsedField::Regular { ty, .. } => ty.clone(),
|
||||
ParsedField::Node { output_type, input_type, .. } => match parsed.is_async {
|
||||
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future<Output=#output_type> + #graphene_core::WasmNotSend>),
|
||||
true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = impl core::future::Future<Output=#output_type>>),
|
||||
false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>),
|
||||
},
|
||||
})
|
||||
|
@ -164,7 +165,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
let eval_args = fields.iter().map(|field| match field {
|
||||
ParsedField::Regular { pat_ident, .. } => {
|
||||
let name = &pat_ident.ident;
|
||||
quote! { let #name = self.#name.eval(()); }
|
||||
quote! { let #name = self.#name.eval(__input.clone()).await; }
|
||||
}
|
||||
ParsedField::Node { pat_ident, .. } => {
|
||||
let name = &pat_ident.ident;
|
||||
|
@ -181,16 +182,32 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
});
|
||||
let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());
|
||||
|
||||
let input_type = &parsed.input.ty;
|
||||
let mut clauses = Vec::new();
|
||||
for (field, name) in fields.iter().zip(struct_generics.iter()) {
|
||||
clauses.push(match (field, *is_async) {
|
||||
(ParsedField::Regular { ty, .. }, _) => quote!(#name: #graphene_core::Node<'n, (), Output = #ty> ),
|
||||
(ParsedField::Node { input_type, output_type, .. }, false) => {
|
||||
quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync)
|
||||
(ParsedField::Regular { ty, .. }, _) => {
|
||||
let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all");
|
||||
let id = future_idents.len();
|
||||
let fut_ident = format_ident!("F{}", id);
|
||||
future_idents.push(fut_ident.clone());
|
||||
quote!(
|
||||
#fut_ident: core::future::Future<Output = #ty> + #graphene_core::WasmNotSend + 'n,
|
||||
for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend,
|
||||
#name: #graphene_core::Node<'n, #input_type, Output = #fut_ident> + #graphene_core::WasmNotSync
|
||||
)
|
||||
}
|
||||
(ParsedField::Node { input_type, output_type, .. }, true) => {
|
||||
quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output: core::future::Future<Output = #output_type> + #graphene_core::WasmNotSend> + #graphene_core::WasmNotSync)
|
||||
let id = future_idents.len();
|
||||
let fut_ident = format_ident!("F{}", id);
|
||||
future_idents.push(fut_ident.clone());
|
||||
|
||||
quote!(
|
||||
#fut_ident: core::future::Future<Output = #output_type> + #graphene_core::WasmNotSend + 'n,
|
||||
#name: #graphene_core::Node<'n, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync
|
||||
)
|
||||
}
|
||||
(ParsedField::Node { .. }, false) => unreachable!(),
|
||||
});
|
||||
}
|
||||
let where_clause = where_clause.clone().unwrap_or(WhereClause {
|
||||
|
@ -210,24 +227,16 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
});
|
||||
|
||||
let async_keyword = is_async.then(|| quote!(async));
|
||||
let await_keyword = is_async.then(|| quote!(.await));
|
||||
|
||||
let eval_impl = if *is_async {
|
||||
quote! {
|
||||
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
|
||||
#[inline]
|
||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||
let eval_impl = quote! {
|
||||
type Output = #graphene_core::registry::DynFuture<'n, #output_type>;
|
||||
#[inline]
|
||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||
Box::pin(async move {
|
||||
#(#eval_args)*
|
||||
Box::pin(self::#fn_name(__input #(, #field_names)*))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
type Output = #output_type;
|
||||
#[inline]
|
||||
fn eval(&'n self, __input: #input_type) -> Self::Output {
|
||||
#(#eval_args)*
|
||||
self::#fn_name(__input #(, #field_names)*)
|
||||
}
|
||||
self::#fn_name(__input #(, #field_names)*) #await_keyword
|
||||
})
|
||||
}
|
||||
};
|
||||
let path = match parsed.attributes.path {
|
||||
|
@ -245,10 +254,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
/// Underlying implementation for [#struct_name]
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
|
||||
#[automatically_derived]
|
||||
impl<'n, #(#fn_generics,)* #(#struct_generics,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
|
||||
impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
|
||||
#struct_where_clause
|
||||
{
|
||||
#eval_impl
|
||||
|
@ -260,7 +269,7 @@ 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, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||
use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO};
|
||||
use gcore::value::ClonedNode;
|
||||
use gcore::ops::TypeNode;
|
||||
use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
|
||||
|
@ -323,7 +332,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
}
|
||||
|
||||
let mut constructors = Vec::new();
|
||||
let unit = parse_quote!(());
|
||||
let unit = parse_quote!(gcore::Context);
|
||||
let parameter_types: Vec<_> = parsed
|
||||
.fields
|
||||
.iter()
|
||||
|
@ -331,9 +340,9 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
match field {
|
||||
ParsedField::Regular { implementations, ty, .. } => {
|
||||
if !implementations.is_empty() {
|
||||
implementations.iter().map(|ty| (&unit, ty, false)).collect()
|
||||
implementations.iter().map(|ty| (&unit, ty)).collect()
|
||||
} else {
|
||||
vec![(&unit, ty, false)]
|
||||
vec![(&unit, ty)]
|
||||
}
|
||||
}
|
||||
ParsedField::Node {
|
||||
|
@ -343,20 +352,19 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
..
|
||||
} => {
|
||||
if !implementations.is_empty() {
|
||||
implementations.iter().map(|impl_| (&impl_.input, &impl_.output, true)).collect()
|
||||
implementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect()
|
||||
} else {
|
||||
vec![(input_type, output_type, true)]
|
||||
vec![(input_type, output_type)]
|
||||
}
|
||||
}
|
||||
}
|
||||
.into_iter()
|
||||
.map(|(input, out, node)| (substitute_lifetimes(input.clone()), substitute_lifetimes(out.clone()), node))
|
||||
.map(|(input, out)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_")))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max();
|
||||
let future_node = (!parsed.is_async).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);));
|
||||
|
||||
for i in 0..max_implementations.unwrap_or(0) {
|
||||
let mut temp_constructors = Vec::new();
|
||||
|
@ -365,38 +373,24 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
|
||||
for (j, types) in parameter_types.iter().enumerate() {
|
||||
let field_name = field_names[j];
|
||||
let (input_type, output_type, impl_node) = &types[i.min(types.len() - 1)];
|
||||
let (input_type, output_type) = &types[i.min(types.len() - 1)];
|
||||
|
||||
let node = matches!(parsed.fields[j], ParsedField::Node { .. });
|
||||
|
||||
let downcast_node = quote!(
|
||||
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
|
||||
);
|
||||
temp_constructors.push(if node {
|
||||
if !parsed.is_async {
|
||||
return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
|
||||
}
|
||||
downcast_node
|
||||
} else {
|
||||
quote!(
|
||||
#downcast_node
|
||||
let #field_name = #field_name.eval(()).await;
|
||||
let #field_name = ClonedNode::new(#field_name);
|
||||
let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name);
|
||||
// try polling futures
|
||||
)
|
||||
});
|
||||
temp_node_io.push(quote!(fn_type!(#input_type, #output_type, alias: #output_type)));
|
||||
match parsed.is_async && *impl_node {
|
||||
true => panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)),
|
||||
false => panic_node_types.push(quote!(#input_type, #output_type)),
|
||||
};
|
||||
let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
|
||||
);
|
||||
if node && !parsed.is_async {
|
||||
return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
|
||||
}
|
||||
temp_constructors.push(downcast_node);
|
||||
temp_node_io.push(quote!(fn_type_fut!(#input_type, #output_type, alias: #output_type)));
|
||||
panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>));
|
||||
}
|
||||
let input_type = match parsed.input.implementations.is_empty() {
|
||||
true => parsed.input.ty.clone(),
|
||||
false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(),
|
||||
};
|
||||
let node_io = if parsed.is_async { quote!(to_async_node_io) } else { quote!(to_node_io) };
|
||||
constructors.push(quote!(
|
||||
(
|
||||
|args| {
|
||||
|
@ -404,14 +398,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
#(#temp_constructors;)*
|
||||
let node = #struct_name::new(#(#field_names,)*);
|
||||
// try polling futures
|
||||
#future_node
|
||||
let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);
|
||||
Box::new(any) as TypeErasedBox<'_>
|
||||
})
|
||||
}, {
|
||||
let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);
|
||||
let params = vec![#(#temp_node_io,)*];
|
||||
let mut node_io = NodeIO::<'_, #input_type>::#node_io(&node, params);
|
||||
let mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params);
|
||||
node_io
|
||||
|
||||
}
|
||||
|
@ -443,11 +436,11 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
|
|||
|
||||
use syn::{visit_mut::VisitMut, GenericArgument, Lifetime, Type};
|
||||
|
||||
struct LifetimeReplacer;
|
||||
struct LifetimeReplacer(&'static str);
|
||||
|
||||
impl VisitMut for LifetimeReplacer {
|
||||
fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
|
||||
lifetime.ident = syn::Ident::new("_", lifetime.ident.span());
|
||||
lifetime.ident = syn::Ident::new(self.0, lifetime.ident.span());
|
||||
}
|
||||
|
||||
fn visit_type_mut(&mut self, ty: &mut Type) {
|
||||
|
@ -472,7 +465,7 @@ impl VisitMut for LifetimeReplacer {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
fn substitute_lifetimes(mut ty: Type) -> Type {
|
||||
LifetimeReplacer.visit_type_mut(&mut ty);
|
||||
fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type {
|
||||
LifetimeReplacer(lifetime).visit_type_mut(&mut ty);
|
||||
ty
|
||||
}
|
||||
|
|
|
@ -4,8 +4,11 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||
use quote::{format_ident, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream, Parser};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::{Comma, RArrow};
|
||||
use syn::{AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, WhereClause};
|
||||
use syn::{
|
||||
parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause,
|
||||
};
|
||||
|
||||
use crate::codegen::generate_node_code;
|
||||
|
||||
|
@ -548,10 +551,12 @@ fn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attri
|
|||
// Modify the new_node_fn function to use the code generation
|
||||
pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
|
||||
let parse_result = parse_node_fn(attr, item.clone());
|
||||
let Ok(parsed_node) = parse_result else {
|
||||
let Ok(mut parsed_node) = parse_result else {
|
||||
let e = parse_result.unwrap_err();
|
||||
return Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error();
|
||||
};
|
||||
|
||||
parsed_node.replace_impl_trait_in_input();
|
||||
if let Err(e) = crate::validation::validate_node_fn(&parsed_node) {
|
||||
return Error::new(e.span(), format!("Validation Error:\n{e}")).to_compile_error();
|
||||
}
|
||||
|
@ -564,6 +569,31 @@ 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 ident = Ident::new("_Input", impl_trait.span());
|
||||
let mut bounds = impl_trait.bounds;
|
||||
bounds.push(parse_quote!('n));
|
||||
self.fn_generics.push(GenericParam::Type(TypeParam {
|
||||
attrs: Default::default(),
|
||||
ident: ident.clone(),
|
||||
colon_token: Some(Default::default()),
|
||||
bounds,
|
||||
eq_token: None,
|
||||
default: None,
|
||||
}));
|
||||
self.input.ty = parse_quote!(#ident);
|
||||
if self.input.implementations.is_empty() {
|
||||
self.input.implementations.push(parse_quote!(gcore::Context));
|
||||
}
|
||||
}
|
||||
if self.input.pat_ident.ident == "_" {
|
||||
self.input.pat_ident.ident = Ident::new("__ctx", self.input.pat_ident.ident.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -774,7 +804,7 @@ mod tests {
|
|||
let attr = quote!(category("Vector: Shape"));
|
||||
let input = quote!(
|
||||
/// Test
|
||||
fn circle(_: (), #[default(50.)] radius: f64) -> VectorData {
|
||||
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData {
|
||||
// Implementation details...
|
||||
}
|
||||
);
|
||||
|
@ -795,7 +825,7 @@ mod tests {
|
|||
where_clause: None,
|
||||
input: Input {
|
||||
pat_ident: pat_ident("_"),
|
||||
ty: parse_quote!(()),
|
||||
ty: parse_quote!(impl Ctx),
|
||||
implementations: Punctuated::new(),
|
||||
},
|
||||
output_type: parse_quote!(VectorData),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue