diff --git a/godot-codegen/src/api_parser.rs b/godot-codegen/src/api_parser.rs index a22d8756c..e4852fde0 100644 --- a/godot-codegen/src/api_parser.rs +++ b/godot-codegen/src/api_parser.rs @@ -20,7 +20,7 @@ pub struct ExtensionApi { pub builtin_class_sizes: Vec, pub builtin_classes: Vec, pub classes: Vec, - pub global_enums: Vec, + pub global_enums: Vec, pub utility_functions: Vec, pub singletons: Vec, } @@ -40,16 +40,15 @@ pub struct ClassSize { #[derive(DeJson)] pub struct BuiltinClass { pub name: String, + pub indexing_return_type: Option, + pub is_keyed: bool, + pub members: Option>, + pub constants: Option>, + pub enums: Option>, // no bitfield + pub operators: Vec, + pub methods: Option>, pub constructors: Vec, pub has_destructor: bool, - pub operators: Vec, -} - -#[derive(DeJson)] -pub struct Operator { - pub name: String, - pub right_type: Option, // null if unary - pub return_type: String, } #[derive(DeJson)] @@ -60,8 +59,8 @@ pub struct Class { pub inherits: Option, // pub api_type: String, // pub constants: Option>, - pub enums: Option>, - pub methods: Option>, + pub enums: Option>, + pub methods: Option>, // pub properties: Option>, // pub signals: Option>, } @@ -75,23 +74,54 @@ pub struct Singleton { } #[derive(DeJson)] -pub struct ClassEnum { +pub struct Enum { pub name: String, pub is_bitfield: bool, - pub values: Vec, + pub values: Vec, } -// Same as above, but no bitfield #[derive(DeJson)] -pub struct GlobalEnum { +pub struct BuiltinClassEnum { + pub name: String, + pub values: Vec, +} + +impl BuiltinClassEnum { + pub(crate) fn to_enum(&self) -> Enum { + Enum { + name: self.name.clone(), + is_bitfield: false, + values: self.values.clone(), + } + } +} + +#[derive(DeJson, Clone)] +pub struct EnumConstant { pub name: String, - pub values: Vec, + pub value: i32, } #[derive(DeJson)] pub struct Constant { pub name: String, - pub value: i32, + #[nserde(rename = "type")] + pub type_: String, + pub value: String, +} + +#[derive(DeJson)] +pub struct Operator { + pub name: String, + pub right_type: Option, // null if unary + pub return_type: String, +} + +#[derive(DeJson)] +pub struct Member { + pub name: String, + #[nserde(rename = "type")] + pub type_: String, } #[derive(DeJson)] @@ -127,18 +157,29 @@ pub struct UtilityFunction { } #[derive(DeJson)] -pub struct Method { +pub struct BuiltinClassMethod { + pub name: String, + pub return_type: Option, + pub is_vararg: bool, + pub is_const: bool, + pub is_static: bool, + pub hash: Option, + pub arguments: Option>, +} + +#[derive(DeJson)] +pub struct ClassMethod { pub name: String, pub is_const: bool, pub is_vararg: bool, //pub is_static: bool, pub is_virtual: bool, pub hash: Option, - pub arguments: Option>, pub return_value: Option, + pub arguments: Option>, } -impl Method { +impl ClassMethod { pub fn map_args(&self, f: impl FnOnce(&Vec) -> R) -> R { match self.arguments.as_ref() { Some(args) => f(args), @@ -147,51 +188,30 @@ impl Method { } } +// Example: set_point_weight_scale -> +// [ {name: "id", type: "int", meta: "int64"}, +// {name: "weight_scale", type: "float", meta: "float"}, #[derive(DeJson, Clone)] pub struct MethodArg { pub name: String, #[nserde(rename = "type")] pub type_: String, + // pub meta: Option, } +// Example: get_available_point_id -> {type: "int", meta: "int64"} #[derive(DeJson)] pub struct MethodReturn { #[nserde(rename = "type")] pub type_: String, + // pub meta: Option, } -pub trait Enum { - fn name(&self) -> &str; - fn values(&self) -> &Vec; - fn is_bitfield(&self) -> bool; -} - -impl Enum for ClassEnum { - fn name(&self) -> &str { - &self.name - } - - fn values(&self) -> &Vec { - &self.values - } - - fn is_bitfield(&self) -> bool { - self.is_bitfield - } -} - -impl Enum for GlobalEnum { - fn name(&self) -> &str { - &self.name - } - - fn values(&self) -> &Vec { - &self.values - } - - fn is_bitfield(&self) -> bool { - // Hack until this is exported in the JSON - self.name.contains("Flag") +impl MethodReturn { + pub fn from_type(type_: &str) -> Self { + Self { + type_: type_.to_owned(), + } } } diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 63658516e..0fa08af5e 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -25,30 +25,30 @@ struct CentralItems { global_enum_defs: Vec, } -struct TypeNames { +pub(crate) struct TypeNames { /// "int" or "PackedVector2Array" - pascal_case: String, + pub pascal_case: String, /// "packed_vector2_array" - snake_case: String, + pub snake_case: String, /// "PACKED_VECTOR2_ARRAY" - //shout_case: String, + //pub shout_case: String, /// GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY - sys_variant_type: Ident, + pub sys_variant_type: Ident, } /// Allows collecting all builtin TypeNames before generating methods -struct BuiltinTypeInfo<'a> { - value: i32, - type_names: TypeNames, +pub(crate) struct BuiltinTypeInfo<'a> { + pub value: i32, + pub type_names: TypeNames, /// If `variant_get_ptr_destructor` returns a non-null function pointer for this type. /// List is directly sourced from extension_api.json (information would also be in variant_destruct.cpp). - has_destructor: bool, - constructors: Option<&'a Vec>, - operators: Option<&'a Vec>, + pub has_destructor: bool, + pub constructors: Option<&'a Vec>, + pub operators: Option<&'a Vec>, } pub(crate) fn generate_sys_central_file( @@ -103,12 +103,14 @@ pub(crate) fn generate_core_mod_file( pub mod class_macros {} } + pub mod builtin_classes {} pub mod utilities {} } } else { quote! { pub mod central; pub mod classes; + pub mod builtin_classes; pub mod utilities; } }; @@ -304,8 +306,7 @@ fn make_central_items(api: &ExtensionApi, build_config: &str, ctx: &mut Context) } } - let class_map = collect_builtin_classes(api); - let builtin_types_map = collect_builtin_types(api, &class_map); + let builtin_types_map = collect_builtin_types(api); let variant_operators = collect_variant_operators(api); // Generate builtin methods, now with info for all types available. @@ -389,10 +390,10 @@ fn collect_builtin_classes(api: &ExtensionApi) -> HashMap class_map } -fn collect_builtin_types<'a>( - api: &'a ExtensionApi, - class_map: &HashMap, -) -> HashMap> { +/// Returns map from "PackedStringArray" to all the info +pub(crate) fn collect_builtin_types(api: &ExtensionApi) -> HashMap> { + let class_map = collect_builtin_classes(api); + let variant_type_enum = api .global_enums .iter() @@ -456,7 +457,7 @@ fn collect_builtin_types<'a>( builtin_types_map } -fn collect_variant_operators(api: &ExtensionApi) -> Vec<&Constant> { +fn collect_variant_operators(api: &ExtensionApi) -> Vec<&EnumConstant> { let variant_operator_enum = api .global_enums .iter() diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 8e3f3fd97..990793678 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -4,15 +4,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -//! Generates a file for each Godot class +//! Generates a file for each Godot engine + builtin class -use proc_macro2::{Literal, TokenStream}; +use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; use std::path::{Path, PathBuf}; use crate::api_parser::*; +use crate::central_generator::{collect_builtin_types, BuiltinTypeInfo}; use crate::util::{ident, safe_ident, strlit, to_module_name, to_rust_type}; -use crate::{special_cases, util, Context, GeneratedClass, GeneratedModule, RustTy}; +use crate::{ + special_cases, util, Context, GeneratedBuiltin, GeneratedBuiltinModule, GeneratedClass, + GeneratedClassModule, RustTy, +}; pub(crate) fn generate_class_files( api: &ExtensionApi, @@ -45,7 +49,7 @@ pub(crate) fn generate_class_files( let class_ident = ident(&class.name); let module_ident = ident(&module_name); - modules.push(GeneratedModule { + modules.push(GeneratedClassModule { class_ident, module_ident, inherits_macro_ident: generated_class.inherits_macro_ident, @@ -53,8 +57,51 @@ pub(crate) fn generate_class_files( }); } + let out_path = gen_path.join("mod.rs"); let mod_contents = make_module_file(modules).to_string(); + std::fs::write(&out_path, mod_contents).expect("failed to write mod.rs file"); + out_files.push(out_path); +} + +pub(crate) fn generate_builtin_class_files( + api: &ExtensionApi, + ctx: &mut Context, + _build_config: &str, + gen_path: &Path, + out_files: &mut Vec, +) { + let _ = std::fs::remove_dir_all(gen_path); + std::fs::create_dir_all(gen_path).expect("create classes directory"); + + let builtin_types_map = collect_builtin_types(api); + + let mut modules = vec![]; + for class in api.builtin_classes.iter() { + if special_cases::is_builtin_type_deleted(&class.name) { + continue; + } + + let type_info = builtin_types_map + .get(&class.name) + .unwrap_or_else(|| panic!("builtin type not found: {}", class.name)); + let inner_class = format_ident!("Inner{}", class.name); + let generated_class = make_builtin_class(class, &inner_class, type_info, ctx); + let file_contents = generated_class.tokens.to_string(); + + let module_name = to_module_name(&class.name); + let out_path = gen_path.join(format!("{module_name}.rs")); + std::fs::write(&out_path, file_contents).expect("failed to write class file"); + out_files.push(out_path); + + let module_ident = ident(&module_name); + modules.push(GeneratedBuiltinModule { + class_ident: inner_class, + module_ident, + }); + } + let out_path = gen_path.join("mod.rs"); + let mod_contents = make_builtin_module_file(modules).to_string(); std::fs::write(&out_path, mod_contents).expect("failed to write mod.rs file"); out_files.push(out_path); } @@ -139,6 +186,7 @@ fn make_class(class: &Class, ctx: &mut Context) -> GeneratedClass { use crate::engine::*; use crate::builtin::*; use crate::obj::{AsArg, Gd}; + use sys::GodotFfi as _; pub(super) mod re_export { use super::*; @@ -208,9 +256,62 @@ fn make_class(class: &Class, ctx: &mut Context) -> GeneratedClass { } } -fn make_module_file(classes_and_modules: Vec) -> TokenStream { +fn make_builtin_class( + class: &BuiltinClass, + inner_class: &Ident, + type_info: &BuiltinTypeInfo, + ctx: &mut Context, +) -> GeneratedBuiltin { + let outer_class = if let RustTy::BuiltinIdent(ident) = to_rust_type(&class.name, ctx) { + ident + } else { + panic!("Rust type `{}` categorized wrong", class.name) + }; + + let class_enums = class.enums.as_ref().map(|class_enums| { + class_enums + .iter() + .map(BuiltinClassEnum::to_enum) + .collect::>() + }); + + let methods = make_builtin_methods(&class.methods, &class.name, type_info, ctx); + let enums = make_enums(&class_enums, &class.name, ctx); + + // mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub + let tokens = quote! { + use godot_ffi as sys; + use crate::builtin::*; + use crate::obj::{AsArg, Gd}; + use crate::sys::GodotFfi as _; + use crate::engine::Object; + + #[repr(transparent)] + pub struct #inner_class<'a> { + _outer_lifetime: std::marker::PhantomData<&'a ()>, + sys_ptr: sys::GDExtensionTypePtr, + } + impl<'a> #inner_class<'a> { + pub fn from_outer(outer: &#outer_class) -> Self { + Self { + _outer_lifetime: std::marker::PhantomData, + sys_ptr: outer.sys(), + } + } + + #methods + } + + #enums + }; + // note: TypePtr -> ObjectPtr conversion OK? + + GeneratedBuiltin { tokens } +} + +fn make_module_file(classes_and_modules: Vec) -> TokenStream { let decls = classes_and_modules.iter().map(|m| { - let GeneratedModule { + let GeneratedClassModule { module_ident, class_ident, is_pub, @@ -226,7 +327,7 @@ fn make_module_file(classes_and_modules: Vec) -> TokenStream { }); let macros = classes_and_modules.iter().map(|m| { - let GeneratedModule { + let GeneratedClassModule { inherits_macro_ident, .. } = m; @@ -249,7 +350,30 @@ fn make_module_file(classes_and_modules: Vec) -> TokenStream { } } -fn make_methods(methods: &Option>, class_name: &str, ctx: &mut Context) -> TokenStream { +fn make_builtin_module_file(classes_and_modules: Vec) -> TokenStream { + let decls = classes_and_modules.iter().map(|m| { + let GeneratedBuiltinModule { + module_ident, + class_ident, + .. + } = m; + + quote! { + mod #module_ident; + pub use #module_ident::#class_ident; + } + }); + + quote! { + #( #decls )* + } +} + +fn make_methods( + methods: &Option>, + class_name: &str, + ctx: &mut Context, +) -> TokenStream { let methods = match methods { Some(m) => m, None => return TokenStream::new(), @@ -264,13 +388,33 @@ fn make_methods(methods: &Option>, class_name: &str, ctx: &mut Conte } } -fn make_enums(enums: &Option>, _class_name: &str, _ctx: &Context) -> TokenStream { +fn make_builtin_methods( + methods: &Option>, + class_name: &str, + type_info: &BuiltinTypeInfo, + ctx: &mut Context, +) -> TokenStream { + let methods = match methods { + Some(m) => m, + None => return TokenStream::new(), + }; + + let definitions = methods + .iter() + .map(|method| make_builtin_method_definition(method, class_name, type_info, ctx)); + + quote! { + #( #definitions )* + } +} + +fn make_enums(enums: &Option>, _class_name: &str, _ctx: &Context) -> TokenStream { let enums = match enums { Some(e) => e, None => return TokenStream::new(), }; - let definitions = enums.iter().map(|e| util::make_enum_definition(e)); + let definitions = enums.iter().map(util::make_enum_definition); quote! { #( #definitions )* @@ -295,7 +439,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { } } -fn is_method_excluded(method: &Method, #[allow(unused_variables)] ctx: &mut Context) -> bool { +fn is_method_excluded(method: &ClassMethod, #[allow(unused_variables)] ctx: &mut Context) -> bool { // Currently excluded: // // * Private virtual methods designed for override; skip for now @@ -350,15 +494,14 @@ fn is_function_excluded(function: &UtilityFunction, ctx: &mut Context) -> bool { }) } -fn make_method_definition(method: &Method, class_name: &str, ctx: &mut Context) -> TokenStream { +fn make_method_definition( + method: &ClassMethod, + class_name: &str, + ctx: &mut Context, +) -> TokenStream { if is_method_excluded(method, ctx) || special_cases::is_deleted(class_name, &method.name) { return TokenStream::new(); } - - let is_varcall = method.is_vararg; - let (params, arg_exprs) = make_params(&method.arguments, is_varcall, ctx); - - let method_name_str = special_cases::maybe_renamed(class_name, &method.name); /*if method.map_args(|args| args.is_empty()) { // Getters (i.e. 0 arguments) will be stripped of their `get_` prefix, to conform to Rust convention if let Some(remainder) = method_name.strip_prefix("get_") { @@ -369,78 +512,108 @@ fn make_method_definition(method: &Method, class_name: &str, ctx: &mut Context) } } }*/ - let method_name = safe_ident(method_name_str); - let hash = method.hash; - // TODO &mut safety + let method_name_str = special_cases::maybe_renamed(class_name, &method.name); let receiver = if method.is_const { - quote!(&self) + quote! { &self, } } else { - quote!(&mut self) + quote! { &mut self, } }; + let hash = method.hash; + let is_varcall = method.is_vararg; - let (return_decl, call) = make_method_return(&method.return_value, is_varcall, ctx); - - let vis = if special_cases::is_private(class_name, &method.name) { - quote! { pub(crate) } + let variant_ffi = is_varcall.then(VariantFfi::variant_ptr); + let function_provider = if is_varcall { + ident("object_method_bind_call") } else { - quote! { pub } + ident("object_method_bind_ptrcall") }; - if is_varcall { - // varcall (using varargs) - quote! { - #vis fn #method_name( #receiver #(, #params )*, varargs: &[Variant]) #return_decl { - unsafe { - let __class_name = StringName::from(#class_name); - let __method_name = StringName::from(#method_name_str); - let __method_bind = sys::interface_fn!(classdb_get_method_bind)( - __class_name.string_sys(), - __method_name.string_sys(), - #hash - ); - let __call_fn = sys::interface_fn!(object_method_bind_call); + let init_code = quote! { + let __class_name = StringName::from(#class_name); + let __method_name = StringName::from(#method_name_str); + let __method_bind = sys::interface_fn!(classdb_get_method_bind)( + __class_name.string_sys(), + __method_name.string_sys(), + #hash + ); + let __call_fn = sys::interface_fn!(#function_provider); + }; + let varcall_invocation = quote! { + __call_fn(__method_bind, self.object_ptr, __args_ptr, __args.len() as i64, return_ptr, std::ptr::addr_of_mut!(__err)); + }; + let ptrcall_invocation = quote! { + __call_fn(__method_bind, self.object_ptr, __args_ptr, return_ptr); + }; - let __explicit_args = [ - #( #arg_exprs ),* - ]; - let mut __args = Vec::new(); - __args.extend(__explicit_args.iter().map(Variant::var_sys_const)); - __args.extend(varargs.iter().map(Variant::var_sys_const)); + make_function_definition( + method_name_str, + special_cases::is_private(class_name, &method.name), + receiver, + &method.arguments, + method.return_value.as_ref(), + variant_ffi, + init_code, + &varcall_invocation, + &ptrcall_invocation, + ctx, + ) +} - let __args_ptr = __args.as_ptr(); +fn make_builtin_method_definition( + method: &BuiltinClassMethod, + class_name_str: &str, + type_info: &BuiltinTypeInfo, + ctx: &mut Context, +) -> TokenStream { + // TODO implement varcalls + if method.is_vararg { + return TokenStream::new(); + } - #call - } - } - } + let method_name_str = &method.name; + + let receiver = if method.is_const { + quote! { &self, } } else { - // ptrcall - quote! { - #vis fn #method_name( #receiver, #( #params ),* ) #return_decl { - unsafe { - let __class_name = StringName::from(#class_name); - let __method_name = StringName::from(#method_name_str); - let __method_bind = sys::interface_fn!(classdb_get_method_bind)( - __class_name.string_sys(), - __method_name.string_sys(), - #hash - ); - let __call_fn = sys::interface_fn!(object_method_bind_ptrcall); + quote! { &mut self, } + }; - let __args = [ - #( #arg_exprs ),* - ]; - let __args_ptr = __args.as_ptr(); + let return_value = method.return_type.as_deref().map(MethodReturn::from_type); + let hash = method.hash; + let is_varcall = method.is_vararg; + let variant_ffi = is_varcall.then(VariantFfi::type_ptr); + + let variant_type = &type_info.type_names.sys_variant_type; + let init_code = quote! { + let __variant_type = sys::#variant_type; + let __method_name = StringName::from(#method_name_str); + let __call_fn = sys::interface_fn!(variant_get_ptr_builtin_method)( + __variant_type, + __method_name.string_sys(), + #hash + ); + let __call_fn = __call_fn.unwrap_unchecked(); + }; + let ptrcall_invocation = quote! { + __call_fn(self.sys_ptr, __args_ptr, return_ptr, __args.len() as i32); + }; - #call - } - } - } - } + make_function_definition( + method_name_str, + special_cases::is_private(class_name_str, &method.name), + receiver, + &method.arguments, + return_value.as_ref(), + variant_ffi, + init_code, + &ptrcall_invocation, + &ptrcall_invocation, + ctx, + ) } -pub(crate) fn make_function_definition( +pub(crate) fn make_utility_function_definition( function: &UtilityFunction, ctx: &mut Context, ) -> TokenStream { @@ -448,53 +621,119 @@ pub(crate) fn make_function_definition( return TokenStream::new(); } - let is_vararg = function.is_vararg; - let (params, arg_exprs) = make_params(&function.arguments, is_vararg, ctx); - let function_name_str = &function.name; - let function_name = safe_ident(function_name_str); + let return_value = function.return_type.as_deref().map(MethodReturn::from_type); let hash = function.hash; + let variant_ffi = function.is_vararg.then_some(VariantFfi::type_ptr()); + let init_code = quote! { + let __function_name = StringName::from(#function_name_str); + let __call_fn = sys::interface_fn!(variant_get_ptr_utility_function)(__function_name.string_sys(), #hash); + let __call_fn = __call_fn.unwrap_unchecked(); + }; + let invocation = quote! { + __call_fn(return_ptr, __args_ptr, __args.len() as i32); + }; + + make_function_definition( + &function.name, + false, + TokenStream::new(), + &function.arguments, + return_value.as_ref(), + variant_ffi, + init_code, + &invocation, + &invocation, + ctx, + ) +} + +/// Defines which methods to use to convert between `Variant` and FFI (either variant ptr or type ptr) +struct VariantFfi { + sys_method: Ident, + from_sys_init_method: Ident, +} +impl VariantFfi { + fn variant_ptr() -> Self { + Self { + sys_method: ident("var_sys_const"), + from_sys_init_method: ident("from_var_sys_init"), + } + } + fn type_ptr() -> Self { + Self { + sys_method: ident("sys_const"), + from_sys_init_method: ident("from_sys_init"), + } + } +} - let (return_decl, call) = make_utility_return(&function.return_type, is_vararg, ctx); +#[allow(clippy::too_many_arguments)] // adding a struct/trait that's used only here, one time, reduces complexity by precisely 0% +fn make_function_definition( + function_name: &str, + is_private: bool, + receiver: TokenStream, + method_args: &Option>, + return_value: Option<&MethodReturn>, + variant_ffi: Option, + init_code: TokenStream, + varcall_invocation: &TokenStream, + ptrcall_invocation: &TokenStream, + ctx: &mut Context, +) -> TokenStream { + let vis = if is_private { + quote! { pub(crate) } + } else { + quote! { pub } + }; - if is_vararg { + let is_varcall = variant_ffi.is_some(); + let fn_name = safe_ident(function_name); + let (params, arg_exprs) = make_params(method_args, is_varcall, ctx); + let (return_decl, call_code) = make_return( + return_value, + variant_ffi.as_ref(), + varcall_invocation, + ptrcall_invocation, + ctx, + ); + + if let Some(variant_ffi) = variant_ffi.as_ref() { + // varcall (using varargs) + let sys_method = &variant_ffi.sys_method; quote! { - pub fn #function_name( #( #params , )* varargs: &[Variant]) #return_decl { + #vis fn #fn_name( #receiver #( #params, )* varargs: &[Variant]) #return_decl { unsafe { - let __function_name = StringName::from(#function_name_str); - let __call_fn = sys::interface_fn!(variant_get_ptr_utility_function)(__function_name.string_sys(), #hash); - let __call_fn = __call_fn.unwrap_unchecked(); + #init_code let __explicit_args = [ #( #arg_exprs ),* ]; + let mut __args = Vec::new(); - { - use godot_ffi::GodotFfi; - __args.extend(__explicit_args.iter().map(Variant::sys_const)); - __args.extend(varargs.iter().map(Variant::sys_const)); - } + __args.extend(__explicit_args.iter().map(Variant::#sys_method)); + __args.extend(varargs.iter().map(Variant::#sys_method)); let __args_ptr = __args.as_ptr(); - #call + #call_code } } } } else { + // ptrcall quote! { - pub fn #function_name( #( #params ),* ) #return_decl { + #vis fn #fn_name( #receiver #( #params, )* ) #return_decl { unsafe { - let __function_name = StringName::from(#function_name_str); - let __call_fn = sys::interface_fn!(variant_get_ptr_utility_function)(__function_name.string_sys(), #hash); - let __call_fn = __call_fn.unwrap_unchecked(); + #init_code let __args = [ #( #arg_exprs ),* ]; + let __args_ptr = __args.as_ptr(); - #call + #call_code } } } @@ -533,85 +772,18 @@ fn make_params( (params, arg_exprs) } -fn make_method_return( - return_value: &Option, - is_varcall: bool, +fn make_return( + return_value: Option<&MethodReturn>, + variant_ffi: Option<&VariantFfi>, + varcall_invocation: &TokenStream, + ptrcall_invocation: &TokenStream, ctx: &mut Context, ) -> (TokenStream, TokenStream) { let return_decl: TokenStream; let return_ty: Option; - match return_value { - Some(ret) => { - let ty = to_rust_type(&ret.type_, ctx); - return_decl = ty.return_decl(); - return_ty = Some(ty); - } - None => { - return_decl = TokenStream::new(); - return_ty = None; - } - }; - - let call = match (is_varcall, return_ty) { - (true, Some(return_ty)) => { - // If the return type is not Variant, then convert to concrete target type - let return_expr = match return_ty { - RustTy::BuiltinIdent(ident) if ident == "Variant" => quote! { variant }, - _ => quote! { variant.to() }, - }; - - // TODO use Result instead of panic on error - quote! { - let variant = Variant::from_var_sys_init(|return_ptr| { - let mut __err = sys::default_call_error(); - __call_fn(__method_bind, self.object_ptr, __args_ptr, __args.len() as i64, return_ptr, std::ptr::addr_of_mut!(__err)); - assert_eq!(__err.error, sys::GDEXTENSION_CALL_OK); - }); - #return_expr - } - } - (true, None) => { - // TODO use Result instead of panic on error - quote! { - let mut __err = sys::default_call_error(); - __call_fn(__method_bind, self.object_ptr, __args_ptr, __args.len() as i64, std::ptr::null_mut(), std::ptr::addr_of_mut!(__err)); - assert_eq!(__err.error, sys::GDEXTENSION_CALL_OK); - } - } - (false, Some(RustTy::EngineClass(return_ty))) => { - quote! { - <#return_ty>::from_sys_init_opt(|return_ptr| { - __call_fn(__method_bind, self.object_ptr, __args_ptr, return_ptr); - }) - } - } - (false, Some(return_ty)) => { - quote! { - <#return_ty as sys::GodotFfi>::from_sys_init(|return_ptr| { - __call_fn(__method_bind, self.object_ptr, __args_ptr, return_ptr); - }) - } - } - (false, None) => { - quote! { - __call_fn(__method_bind, self.object_ptr, __args_ptr, std::ptr::null_mut()); - } - } - }; - - (return_decl, call) -} - -fn make_utility_return( - return_value: &Option, - is_vararg: bool, - ctx: &mut Context, -) -> (TokenStream, TokenStream) { - let return_decl; - let return_ty; if let Some(ret) = return_value { - let ty = to_rust_type(ret, ctx); + let ty = to_rust_type(&ret.type_, ctx); return_decl = ty.return_decl(); return_ty = Some(ty); } else { @@ -619,44 +791,54 @@ fn make_utility_return( return_ty = None; } - let call = match (is_vararg, return_ty) { - (true, Some(return_ty)) => { + let call = match (variant_ffi, return_ty) { + (Some(variant_ffi), Some(return_ty)) => { // If the return type is not Variant, then convert to concrete target type let return_expr = match return_ty { RustTy::BuiltinIdent(ident) if ident == "Variant" => quote! { variant }, _ => quote! { variant.to() }, }; + let from_sys_init_method = &variant_ffi.from_sys_init_method; + // Note: __err may remain unused if the #call does not handle errors (e.g. utility fn, ptrcall, ...) + // TODO use Result instead of panic on error quote! { - use godot_ffi::GodotFfi; - let variant = Variant::from_sys_init(|return_ptr| { - __call_fn(return_ptr, __args_ptr, __args.len() as i32); + let variant = Variant::#from_sys_init_method(|return_ptr| { + let mut __err = sys::default_call_error(); + #varcall_invocation + assert_eq!(__err.error, sys::GDEXTENSION_CALL_OK); }); #return_expr } } - (true, None) => { + (Some(_), None) => { + // Note: __err may remain unused if the #call does not handle errors (e.g. utility fn, ptrcall, ...) + // TODO use Result instead of panic on error quote! { - __call_fn(std::ptr::null_mut(), __args_ptr, __args.len() as i32); + let mut __err = sys::default_call_error(); + let return_ptr = std::ptr::null_mut(); + #varcall_invocation + assert_eq!(__err.error, sys::GDEXTENSION_CALL_OK); } } - (false, Some(RustTy::EngineClass(return_ty))) => { + (None, Some(RustTy::EngineClass(return_ty))) => { quote! { <#return_ty>::from_sys_init_opt(|return_ptr| { - __call_fn(return_ptr, __args_ptr, __args.len() as i32); + #ptrcall_invocation }) } } - (false, Some(return_ty)) => { + (None, Some(return_ty)) => { quote! { <#return_ty as sys::GodotFfi>::from_sys_init(|return_ptr| { - __call_fn(return_ptr, __args_ptr, __args.len() as i32); + #ptrcall_invocation }) } } - (false, None) => { + (None, None) => { quote! { - __call_fn(std::ptr::null_mut(), __args_ptr, __args.len() as i32); + let return_ptr = std::ptr::null_mut(); + #ptrcall_invocation } } }; diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index 02c78656c..61356ce2f 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -23,7 +23,7 @@ use central_generator::{ generate_core_central_file, generate_core_mod_file, generate_sys_central_file, generate_sys_mod_file, }; -use class_generator::generate_class_files; +use class_generator::{generate_builtin_class_files, generate_class_files}; use context::Context; use util::ident; use utilities_generator::generate_utilities_file; @@ -86,12 +86,21 @@ pub fn generate_core_files(core_gen_path: &Path, stubs_only: bool) { ); watch.record("generate_class_files"); + generate_builtin_class_files( + &api, + &mut ctx, + build_config, + &core_gen_path.join("builtin_classes"), + &mut out_files, + ); + watch.record("generate_builtin_class_files"); + rustfmt_if_needed(out_files); watch.record("rustfmt"); watch.write_stats_to(&core_gen_path.join("codegen-stats.txt")); } -#[cfg(feature = "codegen-fmt")] +// #[cfg(feature = "codegen-fmt")] fn rustfmt_if_needed(out_files: Vec) { println!("Format {} generated files...", out_files.len()); @@ -111,9 +120,9 @@ fn rustfmt_if_needed(out_files: Vec) { println!("Rustfmt completed."); } - -#[cfg(not(feature = "codegen-fmt"))] -fn rustfmt_if_needed(_out_files: Vec) {} +// +// #[cfg(not(feature = "codegen-fmt"))] +// fn rustfmt_if_needed(_out_files: Vec) {} // ---------------------------------------------------------------------------------------------------------------------------------------------- // Shared utility types @@ -173,13 +182,22 @@ struct GeneratedClass { has_pub_module: bool, } -struct GeneratedModule { +struct GeneratedBuiltin { + tokens: TokenStream, +} + +struct GeneratedClassModule { class_ident: Ident, module_ident: Ident, inherits_macro_ident: Ident, is_pub: bool, } +struct GeneratedBuiltinModule { + class_ident: Ident, + module_ident: Ident, +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Shared config diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index 686f2283f..4ed1a4262 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -55,9 +55,14 @@ pub fn is_private(class_name: &str, method_name: &str) -> bool { } } +pub fn is_builtin_type_deleted(class_name: &str) -> bool { + class_name == "Nil" || class_name.chars().next().unwrap().is_ascii_lowercase() +} + pub fn maybe_renamed<'m>(class_name: &str, method_name: &'m str) -> &'m str { match (class_name, method_name) { - ("GDScript", "new") => "instantiate", + // GDScript, GDScriptNativeClass, possibly more in the future + (_, "new") => "instantiate", _ => method_name, } } diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index 3639ce5b7..61c009ba0 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -9,20 +9,20 @@ use crate::{Context, RustTy}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; -pub fn make_enum_definition(enum_: &dyn Enum) -> TokenStream { +pub fn make_enum_definition(enum_: &Enum) -> TokenStream { // TODO enums which have unique ords could be represented as Rust enums // This would allow exhaustive matches (or at least auto-completed matches + #[non_exhaustive]). But even without #[non_exhaustive], // this might be a forward compatibility hazard, if Godot deprecates enumerators and adds new ones with existing ords. - let enum_name = ident(enum_.name()); + let enum_name = ident(&enum_.name); - let values = enum_.values(); + let values = &enum_.values; let mut enumerators = Vec::with_capacity(values.len()); // let mut matches = Vec::with_capacity(values.len()); let mut unique_ords = Vec::with_capacity(values.len()); for enumerator in values { - let name = make_enumerator_name(&enumerator.name, enum_.name()); + let name = make_enumerator_name(&enumerator.name, &enum_.name); let ordinal = Literal::i32_unsuffixed(enumerator.value); enumerators.push(quote! { @@ -38,7 +38,7 @@ pub fn make_enum_definition(enum_: &dyn Enum) -> TokenStream { unique_ords.sort(); unique_ords.dedup(); - let bitfield_ops = if enum_.is_bitfield() { + let bitfield_ops = if enum_.is_bitfield { let tokens = quote! { // impl #enum_name { // pub const UNSET: Self = Self { ord: 0 }; @@ -282,11 +282,12 @@ fn to_rust_type_uncached(ty: &str, ctx: &mut Context) -> RustTy { } else if let Some(packed_arr_ty) = ty.strip_prefix("Packed") { // Don't trigger on PackedScene ;P if packed_arr_ty.ends_with("Array") { - return RustTy::BuiltinIdent(ident(packed_arr_ty)); + return RustTy::BuiltinIdent(ident(ty)); + //return RustTy::BuiltinIdent(ident(packed_arr_ty)); } } else if let Some(elem_ty) = ty.strip_prefix("typedarray::") { - if let Some(packed_arr_ty) = elem_ty.strip_prefix("Packed") { - return RustTy::BuiltinIdent(ident(packed_arr_ty)); + if let Some(_packed_arr_ty) = elem_ty.strip_prefix("Packed") { + return RustTy::BuiltinIdent(ident(elem_ty)); } let rust_elem_ty = to_rust_type(elem_ty, ctx); diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index aef935817..0c6fa13d5 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -8,7 +8,7 @@ use quote::quote; use std::path::{Path, PathBuf}; use crate::api_parser::*; -use crate::class_generator::make_function_definition; +use crate::class_generator::make_utility_function_definition; use crate::Context; pub(crate) fn generate_utilities_file( @@ -21,7 +21,7 @@ pub(crate) fn generate_utilities_file( for utility_fn in &api.utility_functions { // note: category unused -> could be their own mod - let def = make_function_definition(utility_fn, ctx); + let def = make_utility_function_definition(utility_fn, ctx); utility_fn_defs.push(def); } @@ -30,6 +30,7 @@ pub(crate) fn generate_utilities_file( use crate::builtin::*; use crate::obj::Gd; use crate::engine::Object; + use sys::GodotFfi as _; #(#utility_fn_defs)* }; diff --git a/godot-core/src/builtin/arrays.rs b/godot-core/src/builtin/arrays.rs index 8e3763845..0fdbfba7e 100644 --- a/godot-core/src/builtin/arrays.rs +++ b/godot-core/src/builtin/arrays.rs @@ -6,42 +6,43 @@ use godot_ffi as sys; -use crate::builtin::{FromVariant, Variant}; +use crate::builtin::{inner, FromVariant, Variant}; use std::marker::PhantomData; -use sys::{ffi_methods, interface_fn, types::*, GodotFfi}; +use sys::types::*; +use sys::{ffi_methods, interface_fn, GodotFfi}; impl_builtin_stub!(Array, OpaqueArray); -impl_builtin_stub!(ByteArray, OpaquePackedByteArray); -impl_builtin_stub!(ColorArray, OpaquePackedColorArray); -impl_builtin_stub!(Float32Array, OpaquePackedFloat32Array); -impl_builtin_stub!(Float64Array, OpaquePackedFloat64Array); -impl_builtin_stub!(Int32Array, OpaquePackedInt32Array); -impl_builtin_stub!(Int64Array, OpaquePackedInt64Array); -impl_builtin_stub!(StringArray, OpaquePackedStringArray); -impl_builtin_stub!(Vector2Array, OpaquePackedVector2Array); -impl_builtin_stub!(Vector3Array, OpaquePackedVector3Array); +impl_builtin_stub!(PackedByteArray, OpaquePackedByteArray); +impl_builtin_stub!(PackedColorArray, OpaquePackedColorArray); +impl_builtin_stub!(PackedFloat32Array, OpaquePackedFloat32Array); +impl_builtin_stub!(PackedFloat64Array, OpaquePackedFloat64Array); +impl_builtin_stub!(PackedInt32Array, OpaquePackedInt32Array); +impl_builtin_stub!(PackedInt64Array, OpaquePackedInt64Array); +impl_builtin_stub!(PackedStringArray, OpaquePackedStringArray); +impl_builtin_stub!(PackedVector2Array, OpaquePackedVector2Array); +impl_builtin_stub!(PackedVector3Array, OpaquePackedVector3Array); impl_builtin_froms!(Array; - ByteArray => array_from_packed_byte_array, - ColorArray => array_from_packed_color_array, - Float32Array => array_from_packed_float32_array, - Float64Array => array_from_packed_float64_array, - Int32Array => array_from_packed_int32_array, - Int64Array => array_from_packed_int64_array, - StringArray => array_from_packed_string_array, - Vector2Array => array_from_packed_vector2_array, - Vector3Array => array_from_packed_vector3_array, + PackedByteArray => array_from_packed_byte_array, + PackedColorArray => array_from_packed_color_array, + PackedFloat32Array => array_from_packed_float32_array, + PackedFloat64Array => array_from_packed_float64_array, + PackedInt32Array => array_from_packed_int32_array, + PackedInt64Array => array_from_packed_int64_array, + PackedStringArray => array_from_packed_string_array, + PackedVector2Array => array_from_packed_vector2_array, + PackedVector3Array => array_from_packed_vector3_array, ); -impl_builtin_froms!(ByteArray; Array => packed_byte_array_from_array); -impl_builtin_froms!(ColorArray; Array => packed_color_array_from_array); -impl_builtin_froms!(Float32Array; Array => packed_float32_array_from_array); -impl_builtin_froms!(Float64Array; Array => packed_float64_array_from_array); -impl_builtin_froms!(Int32Array; Array => packed_int32_array_from_array); -impl_builtin_froms!(Int64Array; Array => packed_int64_array_from_array); -impl_builtin_froms!(StringArray; Array => packed_string_array_from_array); -impl_builtin_froms!(Vector2Array; Array => packed_vector2_array_from_array); -impl_builtin_froms!(Vector3Array; Array => packed_vector3_array_from_array); +impl_builtin_froms!(PackedByteArray; Array => packed_byte_array_from_array); +impl_builtin_froms!(PackedColorArray; Array => packed_color_array_from_array); +impl_builtin_froms!(PackedFloat32Array; Array => packed_float32_array_from_array); +impl_builtin_froms!(PackedFloat64Array; Array => packed_float64_array_from_array); +impl_builtin_froms!(PackedInt32Array; Array => packed_int32_array_from_array); +impl_builtin_froms!(PackedInt64Array; Array => packed_int64_array_from_array); +impl_builtin_froms!(PackedStringArray; Array => packed_string_array_from_array); +impl_builtin_froms!(PackedVector2Array; Array => packed_vector2_array_from_array); +impl_builtin_froms!(PackedVector3Array; Array => packed_vector3_array_from_array); impl Array { pub fn get(&self, index: i64) -> Option { @@ -53,6 +54,20 @@ impl Array { Some((*ptr).clone()) } } + + #[cfg(not(any(gdext_test, doctest)))] + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerArray { + inner::InnerArray::from_outer(self) + } +} + +impl_builtin_traits! { + for Array { + Default => array_construct_default; + Clone => array_construct_copy; + Drop => array_destroy; + } } #[repr(C)] diff --git a/godot-core/src/builtin/macros.rs b/godot-core/src/builtin/macros.rs index 24d5d6699..7bda1c20c 100644 --- a/godot-core/src/builtin/macros.rs +++ b/godot-core/src/builtin/macros.rs @@ -11,10 +11,18 @@ macro_rules! impl_builtin_traits_inner { impl Default for $Type { #[inline] fn default() -> Self { + // Note: can't use from_sys_init(), as that calls the default constructor + // (because most assignments expect initialized target type) + + let mut uninit = std::mem::MaybeUninit::<$Type>::uninit(); + unsafe { - let mut gd_val = sys::$GdType::default(); - ::godot_ffi::builtin_fn!($gd_method)(&mut gd_val); - <$Type>::from_sys(gd_val) + let self_ptr = (*uninit.as_mut_ptr()).sys_mut(); + sys::builtin_call! { + $gd_method(self_ptr, std::ptr::null_mut()) + }; + + uninit.assume_init() } } } @@ -99,6 +107,21 @@ macro_rules! impl_builtin_traits_inner { } } }; + + ( FromVariant for $Type:ty => $gd_method:ident ) => { + impl $crate::builtin::variant::FromVariant for $Type { + fn try_from_variant(variant: &$crate::builtin::Variant) -> Result { + let result = unsafe { + Self::from_sys_init(|self_ptr| { + let converter = sys::builtin_fn!($gd_method); + converter(self_ptr, variant.var_sys()); + }) + }; + + Ok(result) + } + } + }; } macro_rules! impl_builtin_traits { @@ -116,9 +139,10 @@ macro_rules! impl_builtin_traits { } macro_rules! impl_builtin_stub { + // ($Class:ident, $OpaqueTy:ident $( ; )? $( $Traits:ident ),* ) => { ($Class:ident, $OpaqueTy:ident) => { #[repr(C)] - #[derive(Copy, Clone)] + // #[derive(Copy, Clone)] pub struct $Class { opaque: sys::types::$OpaqueTy, } diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index 56e3b8855..40f326bff 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -64,3 +64,23 @@ pub use vector3::*; pub use vector3i::*; pub use vector4::*; pub use vector4i::*; + +#[doc(hidden)] +pub mod inner { + #[cfg(not(gdext_test))] + pub use crate::gen::builtin_classes::*; +} + +// pub struct PackedArray { +// _phantom: std::marker::PhantomData +// } +// +// pub type PackedByteArray = PackedArray; +// pub type PackedInt32Array = PackedArray; +// pub type PackedInt64Array = PackedArray; +// pub type PackedFloat32Array = PackedArray; +// pub type PackedFloat64Array = PackedArray; +// pub type PackedStringArray = PackedArray; +// pub type PackedVector2Array = PackedArray; +// pub type PackedVector3Array = PackedArray; +// pub type PackedColorArray = PackedArray; diff --git a/godot-core/src/builtin/others.rs b/godot-core/src/builtin/others.rs index 2aff9fa1c..d5ef33a8a 100644 --- a/godot-core/src/builtin/others.rs +++ b/godot-core/src/builtin/others.rs @@ -6,7 +6,7 @@ // Stub for various other built-in classes, which are currently incomplete, but whose types // are required for codegen -use crate::builtin::{StringName, Vector2}; +use crate::builtin::{inner, StringName, Vector2}; use crate::obj::{Gd, GodotClass}; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; @@ -60,4 +60,16 @@ impl Callable { }) } } + + #[cfg(not(any(gdext_test, doctest)))] + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerCallable { + inner::InnerCallable::from_outer(self) + } +} + +impl_builtin_traits! { + for Callable { + FromVariant => callable_from_variant; + } } diff --git a/godot-core/src/builtin/vector2.rs b/godot-core/src/builtin/vector2.rs index 9506086be..334ff2853 100644 --- a/godot-core/src/builtin/vector2.rs +++ b/godot-core/src/builtin/vector2.rs @@ -9,7 +9,7 @@ use std::fmt; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; -use crate::builtin::Vector2i; +use crate::builtin::{inner, Vector2i}; /// Vector used for 2D math using floating point coordinates. /// @@ -84,6 +84,12 @@ impl Vector2 { fn to_glam(self) -> glam::Vec2 { glam::Vec2::new(self.x, self.y) } + + #[cfg(not(any(gdext_test, doctest)))] + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerVector2 { + inner::InnerVector2::from_outer(self) + } } /// Formats the vector like Godot: `(x, y)`. diff --git a/godot-ffi/src/lib.rs b/godot-ffi/src/lib.rs index a182b5696..bf0e3b4ff 100644 --- a/godot-ffi/src/lib.rs +++ b/godot-ffi/src/lib.rs @@ -79,7 +79,6 @@ mod real_impl { "Initialize GDExtension interface: {}", ver.to_str().unwrap() ); - //dbg!(*interface); BINDING = Some(GodotBinding { interface: *interface, diff --git a/itest/rust/src/builtin_test.rs b/itest/rust/src/builtin_test.rs new file mode 100644 index 000000000..1b9943f5b --- /dev/null +++ b/itest/rust/src/builtin_test.rs @@ -0,0 +1,69 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use crate::itest; +use godot::builtin::inner::*; +use godot::prelude::*; + +pub(crate) fn run() -> bool { + let mut ok = true; + ok &= test_builtins_vector2(); + ok &= test_builtins_array(); + ok &= test_builtins_callable(); + ok +} + +#[itest] +fn test_builtins_vector2() { + let vec = Vector2::new(3.0, -4.0); + let inner: InnerVector2 = vec.as_inner(); + + let len_sq = inner.length_squared(); + assert_eq!(len_sq, 25.0); + + let abs = inner.abs(); + assert_eq!(abs, Vector2::new(3.0, 4.0)); + + let normalized = inner.is_normalized(); + assert_eq!(normalized, false); +} + +#[itest] +fn test_builtins_array() { + let array = Array::default(); + let mut inner: InnerArray = array.as_inner(); + + let a = 7.to_variant(); + let b = GodotString::from("Seven").to_variant(); + + inner.append(a.clone()); + inner.append(b.clone()); + + assert_eq!(inner.size(), 2); + assert_eq!(inner.pop_front(), a); + assert_eq!(inner.pop_front(), b); + assert_eq!(inner.pop_front(), Variant::nil()); +} + +#[itest] +fn test_builtins_callable() { + let obj = Node2D::new_alloc(); + let cb = Callable::from_object_method(obj.share(), "set_position"); + let inner: InnerCallable = cb.as_inner(); + + assert_eq!(inner.is_null(), false); + assert_eq!(inner.get_object_id(), obj.instance_id().to_i64()); + assert_eq!(inner.get_method(), StringName::from("set_position")); + + // TODO once varargs is available + // let pos = Vector2::new(5.0, 7.0); + // inner.call(&[pos.to_variant()]); + // assert_eq!(obj.get_position(), pos); + // + // inner.bindv(array); + + obj.free(); +} diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index 00b62d55c..4983b8863 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -13,6 +13,7 @@ use godot::test::itest; use std::panic::UnwindSafe; mod base_test; +mod builtin_test; mod enum_test; mod export_test; mod gdscript_ffi_test; @@ -27,11 +28,12 @@ mod virtual_methods_test; fn run_tests() -> bool { let mut ok = true; ok &= base_test::run(); + ok &= builtin_test::run(); + ok &= enum_test::run(); + ok &= export_test::run(); ok &= gdscript_ffi_test::run(); ok &= node_test::run(); - ok &= enum_test::run(); ok &= object_test::run(); - ok &= export_test::run(); ok &= singleton_test::run(); ok &= string_test::run(); ok &= utilities_test::run();