use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use crate::models::domain::{BuiltinVariant, Constructor, ExtensionApi, Operator};
pub fn make_variant_fns(
api: &ExtensionApi,
builtin: &BuiltinVariant,
) -> (TokenStream, TokenStream) {
let (special_decls, special_inits);
if let Some(builtin_class) = builtin.associated_builtin_class() {
let (construct_decls, construct_inits) =
make_construct_fns(api, builtin, &builtin_class.constructors);
let (destroy_decls, destroy_inits) =
make_destroy_fns(builtin, builtin_class.has_destructor);
let (op_eq_decls, op_eq_inits) =
make_operator_fns(builtin, &builtin_class.operators, "==", "EQUAL");
let (op_lt_decls, op_lt_inits) =
make_operator_fns(builtin, &builtin_class.operators, "<", "LESS");
special_decls = quote! {
#op_eq_decls
#op_lt_decls
#construct_decls
#destroy_decls
};
special_inits = quote! {
#op_eq_inits
#op_lt_inits
#construct_inits
#destroy_inits
};
} else {
special_decls = TokenStream::new();
special_inits = TokenStream::new();
};
let snake_case = builtin.snake_name();
let to_variant = format_ident!("{}_to_variant", snake_case);
let from_variant = format_ident!("{}_from_variant", snake_case);
let to_variant_str = to_variant.to_string();
let from_variant_str = from_variant.to_string();
let variant_type = builtin.sys_variant_type();
let variant_type = quote! { crate::#variant_type };
let decl = quote! {
pub #to_variant: unsafe extern "C" fn(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr),
pub #from_variant: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr),
#special_decls
};
let init = quote! {
#to_variant: {
let fptr = unsafe { get_to_variant_fn(#variant_type) };
crate::validate_builtin_lifecycle(fptr, #to_variant_str)
},
#from_variant: {
let fptr = unsafe { get_from_variant_fn(#variant_type) };
crate::validate_builtin_lifecycle(fptr, #from_variant_str)
},
#special_inits
};
(decl, init)
}
fn make_construct_fns(
api: &ExtensionApi,
builtin: &BuiltinVariant,
constructors: &[Constructor],
) -> (TokenStream, TokenStream) {
if constructors.is_empty() {
return (TokenStream::new(), TokenStream::new());
};
for (i, c) in constructors.iter().enumerate() {
assert_eq!(i, c.index);
}
assert!(
constructors[0].raw_parameters.is_empty(),
"default constructor at index 0 must have no parameters"
);
let args = &constructors[1].raw_parameters;
assert_eq!(args.len(), 1);
assert_eq!(args[0].name, "from");
assert_eq!(args[0].type_, builtin.godot_original_name());
let builtin_snake_name = builtin.snake_name();
let variant_type = builtin.sys_variant_type();
let construct_default = format_ident!("{builtin_snake_name}_construct_default");
let construct_copy = format_ident!("{builtin_snake_name}_construct_copy");
let construct_default_str = construct_default.to_string();
let construct_copy_str = construct_copy.to_string();
let (construct_extra_decls, construct_extra_inits) =
make_extra_constructors(api, builtin, constructors);
let decls = quote! {
pub #construct_default: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
pub #construct_copy: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
#(
#construct_extra_decls
)*
};
let inits = quote! {
#construct_default: {
let fptr = unsafe { get_construct_fn(crate::#variant_type, 0i32) };
crate::validate_builtin_lifecycle(fptr, #construct_default_str)
},
#construct_copy: {
let fptr = unsafe { get_construct_fn(crate::#variant_type, 1i32) };
crate::validate_builtin_lifecycle(fptr, #construct_copy_str)
},
#(
#construct_extra_inits
)*
};
(decls, inits)
}
fn make_extra_constructors(
api: &ExtensionApi,
builtin: &BuiltinVariant,
constructors: &[Constructor],
) -> (Vec<TokenStream>, Vec<TokenStream>) {
let mut extra_decls = Vec::with_capacity(constructors.len() - 2);
let mut extra_inits = Vec::with_capacity(constructors.len() - 2);
let variant_type = builtin.sys_variant_type();
for (i, ctor) in constructors.iter().enumerate().skip(2) {
let args = &ctor.raw_parameters;
assert!(
!args.is_empty(),
"custom constructors must have at least 1 parameter"
);
let type_name = builtin.snake_name();
let construct_custom = if args.len() == 1 && args[0].name == "from" {
let arg_type = api.builtin_by_original_name(&args[0].type_).snake_name();
format_ident!("{type_name}_from_{arg_type}")
} else {
let mut arg_names = args
.iter()
.fold(String::new(), |acc, arg| acc + &arg.name + "_");
arg_names.pop(); format_ident!("{type_name}_from_{arg_names}")
};
let construct_custom_str = construct_custom.to_string();
extra_decls.push(quote! {
pub #construct_custom: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr),
});
let i = i as i32;
extra_inits.push(quote! {
#construct_custom: {
let fptr = unsafe { get_construct_fn(crate::#variant_type, #i) };
crate::validate_builtin_lifecycle(fptr, #construct_custom_str)
},
});
}
(extra_decls, extra_inits)
}
fn make_destroy_fns(builtin: &BuiltinVariant, has_destructor: bool) -> (TokenStream, TokenStream) {
if !has_destructor {
return (TokenStream::new(), TokenStream::new());
}
let destroy = format_ident!("{}_destroy", builtin.snake_name());
let destroy_str = destroy.to_string();
let variant_type = builtin.sys_variant_type();
let decls = quote! {
pub #destroy: unsafe extern "C" fn(GDExtensionTypePtr),
};
let inits = quote! {
#destroy: {
let fptr = unsafe { get_destroy_fn(crate::#variant_type) };
crate::validate_builtin_lifecycle(fptr, #destroy_str)
},
};
(decls, inits)
}
fn make_operator_fns(
builtin: &BuiltinVariant,
operators: &[Operator],
json_symbol: &str,
sys_name: &str,
) -> (TokenStream, TokenStream) {
if operators.is_empty() || !operators.iter().any(|op| op.symbol == json_symbol) {
return (TokenStream::new(), TokenStream::new());
}
let operator = format_ident!(
"{}_operator_{}",
builtin.snake_name(),
sys_name.to_ascii_lowercase()
);
let operator_str = operator.to_string();
let variant_type = builtin.sys_variant_type();
let variant_type = quote! { crate::#variant_type };
let sys_ident = format_ident!("GDEXTENSION_VARIANT_OP_{}", sys_name);
let decl = quote! {
pub #operator: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr),
};
let init = quote! {
#operator: {
let fptr = unsafe { get_operator_fn(crate::#sys_ident, #variant_type, #variant_type) };
crate::validate_builtin_lifecycle(fptr, #operator_str)
},
};
(decl, init)
}