diff --git a/examples/dodge-the-creeps/rust/src/hud.rs b/examples/dodge-the-creeps/rust/src/hud.rs index cd474d185..83ae9a6e7 100644 --- a/examples/dodge-the-creeps/rust/src/hud.rs +++ b/examples/dodge-the-creeps/rust/src/hud.rs @@ -20,7 +20,7 @@ impl Hud { message_label.show(); let mut timer = self.base.get_node_as::("MessageTimer"); - timer.start(0.0); + timer.start(); } pub fn show_game_over(&self) { diff --git a/examples/dodge-the-creeps/rust/src/main_scene.rs b/examples/dodge-the-creeps/rust/src/main_scene.rs index 8eaf1a4a0..7abbfed65 100644 --- a/examples/dodge-the-creeps/rust/src/main_scene.rs +++ b/examples/dodge-the-creeps/rust/src/main_scene.rs @@ -1,11 +1,12 @@ use crate::hud::Hud; use crate::mob; use crate::player; -use godot::engine::node::InternalMode; + use godot::engine::{Marker2D, PathFollow2D, RigidBody2D, Timer}; use godot::prelude::*; + use rand::Rng as _; -use std::f64::consts::PI; +use std::f32::consts::PI; // Deriving GodotClass makes the class available to Godot #[derive(GodotClass)] @@ -33,7 +34,7 @@ impl Main { hud.bind_mut().show_game_over(); self.music().stop(); - self.death_sound().play(0.0); + self.death_sound().play(); } #[func] @@ -45,22 +46,22 @@ impl Main { self.score = 0; player.bind_mut().start(start_position.get_position()); - start_timer.start(0.0); + start_timer.start(); let mut hud = self.base.get_node_as::("Hud"); let hud = hud.bind_mut(); hud.update_score(self.score); hud.show_message("Get Ready".into()); - self.music().play(0.0); + self.music().play(); } #[func] fn on_start_timer_timeout(&self) { let mut mob_timer = self.base.get_node_as::("MobTimer"); let mut score_timer = self.base.get_node_as::("ScoreTimer"); - mob_timer.start(0.0); - score_timer.start(0.0); + mob_timer.start(); + score_timer.start(); } #[func] @@ -82,7 +83,7 @@ impl Main { let mut rng = rand::thread_rng(); let progress = rng.gen_range(u32::MIN..u32::MAX); - mob_spawn_location.set_progress(progress.into()); + mob_spawn_location.set_progress(progress as f32); mob_scene.set_position(mob_spawn_location.get_position()); let mut direction = mob_spawn_location.get_rotation() + PI / 2.0; @@ -90,11 +91,7 @@ impl Main { mob_scene.set_rotation(direction); - self.base.add_child( - mob_scene.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + self.base.add_child(mob_scene.share().upcast()); let mut mob = mob_scene.cast::(); { @@ -103,7 +100,7 @@ impl Main { let range = rng.gen_range(mob.min_speed..mob.max_speed); mob.set_linear_velocity(Vector2::new(range, 0.0)); - let lin_vel = mob.get_linear_velocity().rotated(real::from_f64(direction)); + let lin_vel = mob.get_linear_velocity().rotated(real::from_f32(direction)); mob.set_linear_velocity(lin_vel); } @@ -111,7 +108,6 @@ impl Main { hud.bind_mut().connect( "start_game".into(), Callable::from_object_method(mob, "on_start_game"), - 0, ); } diff --git a/examples/dodge-the-creeps/rust/src/mob.rs b/examples/dodge-the-creeps/rust/src/mob.rs index d57502721..aa201ce0d 100644 --- a/examples/dodge-the-creeps/rust/src/mob.rs +++ b/examples/dodge-the-creeps/rust/src/mob.rs @@ -40,7 +40,7 @@ impl RigidBody2DVirtual for Mob { .base .get_node_as::("AnimatedSprite2D"); - sprite.play("".into(), 1.0, false); + sprite.play(); let anim_names = sprite.get_sprite_frames().unwrap().get_animation_names(); // TODO use pick_random() once implemented diff --git a/examples/dodge-the-creeps/rust/src/player.rs b/examples/dodge-the-creeps/rust/src/player.rs index 15bb7b786..ca9b5a116 100644 --- a/examples/dodge-the-creeps/rust/src/player.rs +++ b/examples/dodge-the-creeps/rust/src/player.rs @@ -74,16 +74,16 @@ impl Area2DVirtual for Player { // Note: exact=false by default, in Rust we have to provide it explicitly let input = Input::singleton(); - if input.is_action_pressed("move_right".into(), false) { + if input.is_action_pressed("move_right".into()) { velocity += Vector2::RIGHT; } - if input.is_action_pressed("move_left".into(), false) { + if input.is_action_pressed("move_left".into()) { velocity += Vector2::LEFT; } - if input.is_action_pressed("move_down".into(), false) { + if input.is_action_pressed("move_down".into()) { velocity += Vector2::DOWN; } - if input.is_action_pressed("move_up".into(), false) { + if input.is_action_pressed("move_up".into()) { velocity += Vector2::UP; } @@ -103,7 +103,7 @@ impl Area2DVirtual for Player { animated_sprite.set_flip_v(velocity.y > 0.0) } - animated_sprite.play(animation.into(), 1.0, false); + animated_sprite.play_ex().name(animation.into()).done(); } else { animated_sprite.stop(); } diff --git a/godot-codegen/Cargo.toml b/godot-codegen/Cargo.toml index d126a003d..07dabe335 100644 --- a/godot-codegen/Cargo.toml +++ b/godot-codegen/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["gamedev", "godot", "engine", "codegen"] categories = ["game-engines", "graphics"] [features] -default = [] +default = ["codegen-fmt"] codegen-fmt = [] codegen-full = [] double-precision = [] diff --git a/godot-codegen/src/api_parser.rs b/godot-codegen/src/api_parser.rs index d8a3b8f50..ac5f2ff51 100644 --- a/godot-codegen/src/api_parser.rs +++ b/godot-codegen/src/api_parser.rs @@ -217,7 +217,8 @@ pub struct MethodArg { pub name: String, #[nserde(rename = "type")] pub type_: String, - // pub meta: Option, + pub meta: Option, + pub default_value: Option, } // Example: get_available_point_id -> {type: "int", meta: "int64"} @@ -225,13 +226,14 @@ pub struct MethodArg { pub struct MethodReturn { #[nserde(rename = "type")] pub type_: String, - // pub meta: Option, + pub meta: Option, } impl MethodReturn { - pub fn from_type(type_: &str) -> Self { + pub fn from_type_no_meta(type_: &str) -> Self { Self { type_: type_.to_owned(), + meta: None, } } } diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 11054ab06..bc3aa0e93 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -522,7 +522,7 @@ fn make_enumerator( ) -> (Ident, TokenStream, Literal) { let enumerator_name = &type_names.json_builtin_name; let pascal_name = to_pascal_case(enumerator_name); - let rust_ty = to_rust_type(enumerator_name, ctx); + let rust_ty = to_rust_type(enumerator_name, None, ctx); let ord = Literal::i32_unsuffixed(value); (ident(&pascal_name), rust_ty.to_token_stream(), ord) diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 902ed33cb..ca1c94210 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -14,13 +14,15 @@ use crate::api_parser::*; use crate::central_generator::{collect_builtin_types, BuiltinTypeInfo}; use crate::util::{ ident, option_as_slice, parse_native_structures_format, safe_ident, to_pascal_case, - to_rust_type, to_rust_type_abi, to_snake_case, NativeStructuresField, + to_rust_expr, to_rust_type, to_rust_type_abi, to_snake_case, unmap_meta, NativeStructuresField, }; use crate::{ special_cases, util, Context, GeneratedBuiltin, GeneratedBuiltinModule, GeneratedClass, GeneratedClassModule, ModName, RustTy, TyName, }; +// ---------------------------------------------------------------------------------------------------------------------------------------------- + struct FnReceiver { /// `&self`, `&mut self`, (none) param: TokenStream, @@ -44,14 +46,123 @@ impl FnReceiver { } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +struct FnParam { + name: Ident, + type_: RustTy, + default_value: Option, +} + +impl FnParam { + fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { + option_as_slice(method_args) + .iter() + .map(|arg| Self::new(arg, ctx)) + .collect() + } + + fn new_range_no_defaults( + method_args: &Option>, + ctx: &mut Context, + ) -> Vec { + option_as_slice(method_args) + .iter() + .map(|arg| Self::new_no_defaults(arg, ctx)) + .collect() + } + + fn new(method_arg: &MethodArg, ctx: &mut Context) -> FnParam { + let name = safe_ident(&method_arg.name); + let type_ = to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx); + let default_value = method_arg + .default_value + .as_ref() + .map(|v| to_rust_expr(v, &type_)); + + FnParam { + name, + type_, + default_value, + } + } + + fn new_no_defaults(method_arg: &MethodArg, ctx: &mut Context) -> FnParam { + FnParam { + name: safe_ident(&method_arg.name), + type_: to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx), + //type_: to_rust_type(&method_arg.type_, &method_arg.meta, ctx), + default_value: None, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +struct FnReturn { + decl: TokenStream, + type_: Option, +} + +impl FnReturn { + fn new(return_value: &Option, ctx: &mut Context) -> Self { + if let Some(ret) = return_value { + let ty = to_rust_type(&ret.type_, ret.meta.as_ref(), ctx); + + Self { + decl: ty.return_decl(), + type_: Some(ty), + } + } else { + Self { + decl: TokenStream::new(), + type_: None, + } + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +enum FnQualifier { + Mut, // &mut self + Const, // &self + Static, // Self + Global, // (nothing) +} + +impl FnQualifier { + fn is_static_or_global(&self) -> bool { + matches!(self, Self::Static | Self::Global) + } +} + +impl FnQualifier { + fn for_method(is_const: bool, is_static: bool) -> FnQualifier { + if is_static { + FnQualifier::Static + } else if is_const { + FnQualifier::Const + } else { + FnQualifier::Mut + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + struct FnSignature<'a> { function_name: &'a str, + surrounding_class: Option<&'a TyName>, // None if global function is_private: bool, is_virtual: bool, - method_args: &'a [MethodArg], - return_value: Option<&'a MethodReturn>, + qualifier: FnQualifier, + params: Vec, + return_value: FnReturn, } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + struct FnCode { receiver: FnReceiver, variant_ffi: Option, @@ -62,6 +173,43 @@ struct FnCode { // ---------------------------------------------------------------------------------------------------------------------------------------------- +struct FnDefinition { + functions: TokenStream, + builders: TokenStream, +} + +impl FnDefinition { + fn none() -> FnDefinition { + FnDefinition { + functions: TokenStream::new(), + builders: TokenStream::new(), + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +struct FnDefinitions { + functions: TokenStream, + builders: TokenStream, +} + +impl FnDefinitions { + fn expand(definitions: impl Iterator) -> FnDefinitions { + // Collect needed because borrowed by 2 closures + let definitions: Vec<_> = definitions.collect(); + let functions = definitions.iter().map(|def| &def.functions); + let structs = definitions.iter().map(|def| &def.builders); + + FnDefinitions { + functions: quote! { #( #functions )* }, + builders: quote! { #( #structs )* }, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + pub(crate) fn generate_class_files( api: &ExtensionApi, ctx: &mut Context, @@ -334,7 +482,11 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate }; let constructor = make_constructor(class, ctx); - let methods = make_methods(option_as_slice(&class.methods), class_name, ctx); + let FnDefinitions { + functions: methods, + builders, + } = make_methods(option_as_slice(&class.methods), class_name, ctx); + let enums = make_enums(option_as_slice(&class.enums), class_name, ctx); let constants = make_constants(option_as_slice(&class.constants), class_name, ctx); let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty); @@ -441,6 +593,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate } } + #builders #enums }; // note: TypePtr -> ObjectPtr conversion OK? @@ -471,14 +624,14 @@ fn make_notify_method(class_name: &TyName, ctx: &mut Context) -> TokenStream { /// a panic. The reason is that the receiving virtual method `on_notification()` acquires a `GdMut` lock dynamically, which must /// be exclusive. pub fn notify(&mut self, what: #enum_name) { - self.notification(i32::from(what) as i64, false); + self.notification(i32::from(what), false); } /// ⚠️ Like [`Self::notify()`], but starts at the most-derived class and goes up the hierarchy. /// /// See docs of that method, including the panics. pub fn notify_reversed(&mut self, what: #enum_name) { - self.notification(i32::from(what) as i64, true); + self.notification(i32::from(what), true); } } } @@ -583,7 +736,7 @@ fn make_builtin_class( type_info: &BuiltinTypeInfo, ctx: &mut Context, ) -> GeneratedBuiltin { - let outer_class = if let RustTy::BuiltinIdent(ident) = to_rust_type(&class.name, ctx) { + let outer_class = if let RustTy::BuiltinIdent(ident) = to_rust_type(&class.name, None, ctx) { ident } else { panic!("Rust type `{}` categorized wrong", class.name) @@ -597,12 +750,22 @@ fn make_builtin_class( .collect::>() }); - let methods = make_builtin_methods(option_as_slice(&class.methods), class_name, type_info, ctx); + let FnDefinitions { + functions: methods, + builders, + } = make_builtin_methods( + option_as_slice(&class.methods), + class_name, + inner_class_name, + type_info, + ctx, + ); + let enums = make_enums(&class_enums, class_name, ctx); let special_constructors = make_special_builtin_methods(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! { + let code = quote! { use godot_ffi as sys; use crate::builtin::*; use crate::engine::native::*; @@ -626,11 +789,12 @@ fn make_builtin_class( #methods } + #builders #enums }; // note: TypePtr -> ObjectPtr conversion OK? - GeneratedBuiltin { code: tokens } + GeneratedBuiltin { code } } fn make_native_structure( @@ -775,29 +939,26 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> } } -fn make_methods(methods: &[ClassMethod], class_name: &TyName, ctx: &mut Context) -> TokenStream { +fn make_methods(methods: &[ClassMethod], class_name: &TyName, ctx: &mut Context) -> FnDefinitions { let definitions = methods .iter() .map(|method| make_method_definition(method, class_name, ctx)); - quote! { - #( #definitions )* - } + FnDefinitions::expand(definitions) } fn make_builtin_methods( methods: &[BuiltinClassMethod], class_name: &TyName, + inner_class_name: &TyName, type_info: &BuiltinTypeInfo, ctx: &mut Context, -) -> TokenStream { - let definitions = methods - .iter() - .map(|method| make_builtin_method_definition(method, class_name, type_info, ctx)); +) -> FnDefinitions { + let definitions = methods.iter().map(|method| { + make_builtin_method_definition(method, class_name, inner_class_name, type_info, ctx) + }); - quote! { - #( #definitions )* - } + FnDefinitions::expand(definitions) } fn make_enums(enums: &[Enum], _class_name: &TyName, _ctx: &Context) -> TokenStream { @@ -859,7 +1020,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { RustTy::EngineClass { class, .. } => is_class_excluded(&class), } } - is_rust_type_excluded(&to_rust_type(ty, ctx)) + is_rust_type_excluded(&to_rust_type(ty, None, ctx)) } fn is_method_excluded( @@ -915,10 +1076,10 @@ fn make_method_definition( method: &ClassMethod, class_name: &TyName, ctx: &mut Context, -) -> TokenStream { +) -> FnDefinition { if is_method_excluded(method, false, ctx) || special_cases::is_deleted(class_name, &method.name) { - return TokenStream::new(); + return FnDefinition::none(); } /*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 @@ -978,10 +1139,12 @@ fn make_method_definition( make_function_definition( &FnSignature { function_name: method_name_str, + surrounding_class: Some(class_name), is_private: special_cases::is_private(class_name, &method.name), is_virtual: false, - method_args: option_as_slice(&method.arguments), - return_value: method.return_value.as_ref(), + qualifier: FnQualifier::for_method(method.is_const, method.is_static), + params: FnParam::new_range(&method.arguments, ctx), + return_value: FnReturn::new(&method.return_value, ctx), }, &FnCode { receiver, @@ -990,19 +1153,22 @@ fn make_method_definition( varcall_invocation, ptrcall_invocation, }, - ctx, ) } fn make_builtin_method_definition( method: &BuiltinClassMethod, class_name: &TyName, + inner_class_name: &TyName, type_info: &BuiltinTypeInfo, ctx: &mut Context, -) -> TokenStream { +) -> FnDefinition { let method_name_str = &method.name; - let return_value = method.return_type.as_deref().map(MethodReturn::from_type); + let return_value = method + .return_type + .as_deref() + .map(MethodReturn::from_type_no_meta); let hash = method.hash.expect("missing hash for builtin method"); let is_varcall = method.is_vararg; let variant_ffi = is_varcall.then(VariantFfi::type_ptr); @@ -1028,10 +1194,15 @@ fn make_builtin_method_definition( make_function_definition( &FnSignature { function_name: method_name_str, + surrounding_class: Some(inner_class_name), is_private: special_cases::is_private(class_name, &method.name), is_virtual: false, - method_args: option_as_slice(&method.arguments), - return_value: return_value.as_ref(), + qualifier: FnQualifier::for_method(method.is_const, method.is_static), + + // Disable default parameters for builtin classes. + // They are not public-facing and need more involved implementation (lifetimes etc). Also reduces number of symbols in API. + params: FnParam::new_range_no_defaults(&method.arguments, ctx), + return_value: FnReturn::new(&return_value, ctx), }, &FnCode { receiver, @@ -1040,7 +1211,6 @@ fn make_builtin_method_definition( varcall_invocation: ptrcall_invocation.clone(), ptrcall_invocation, }, - ctx, ) } @@ -1053,7 +1223,10 @@ pub(crate) fn make_utility_function_definition( } let function_name_str = &function.name; - let return_value = function.return_type.as_deref().map(MethodReturn::from_type); + let return_value = function + .return_type + .as_deref() + .map(MethodReturn::from_type_no_meta); let hash = function.hash; let variant_ffi = function.is_vararg.then_some(VariantFfi::type_ptr()); let init_code = quote! { @@ -1065,13 +1238,15 @@ pub(crate) fn make_utility_function_definition( __call_fn(return_ptr, __args_ptr, __args.len() as i32); }; - make_function_definition( + let definition = make_function_definition( &FnSignature { function_name: function_name_str, + surrounding_class: None, is_private: false, is_virtual: false, - method_args: option_as_slice(&function.arguments), - return_value: return_value.as_ref(), + qualifier: FnQualifier::Global, + params: FnParam::new_range(&function.arguments, ctx), + return_value: FnReturn::new(&return_value, ctx), }, &FnCode { receiver: FnReceiver::global_function(), @@ -1080,8 +1255,13 @@ pub(crate) fn make_utility_function_definition( varcall_invocation: invocation.clone(), ptrcall_invocation: invocation, }, - ctx, - ) + ); + + assert!( + definition.builders.is_empty(), + "utility functions should not have builders" + ); + definition.functions } /// Defines which methods to use to convert between `Variant` and FFI (either variant ptr or type ptr) @@ -1104,12 +1284,24 @@ impl VariantFfi { } } -fn make_function_definition(sig: &FnSignature, code: &FnCode, ctx: &mut Context) -> TokenStream { - let vis = if sig.is_private { +fn make_vis(is_private: bool) -> TokenStream { + if is_private { quote! { pub(crate) } } else { quote! { pub } + } +} + +fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { + let has_default_params = function_uses_default_params(sig); + let vis = if has_default_params { + // Public API mapped by separate function. + // Needs to be crate-public because default-arg builder lives outside of the module. + quote! { pub(crate) } + } else { + make_vis(sig.is_private) }; + let (maybe_unsafe, safety_doc) = if function_uses_pointers(sig) { ( quote! { unsafe }, @@ -1124,10 +1316,21 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode, ctx: &mut Context) }; let is_varcall = code.variant_ffi.is_some(); - let [params, variant_types, arg_exprs, arg_names] = - make_params(sig.method_args, is_varcall, ctx); + let [params, variant_types, arg_exprs, arg_names, meta_arg_decls] = + make_params_and_impl(&sig.params, is_varcall, false); + + let primary_fn_name = if has_default_params { + format_ident!("{}_full", safe_ident(sig.function_name)) + } else { + safe_ident(sig.function_name) + }; + + let (default_fn_code, default_structs_code) = if has_default_params { + make_function_definition_with_defaults(sig, code, &primary_fn_name) + } else { + (TokenStream::new(), TokenStream::new()) + }; - let fn_name = safe_ident(sig.function_name); let (prepare_arg_types, error_fn_context); if code.variant_ffi.is_some() { // varcall (using varargs) @@ -1156,20 +1359,20 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode, ctx: &mut Context) error_fn_context = sig.function_name.to_token_stream(); }; - let (return_decl, call_code) = make_return( - sig.return_value, + let return_decl = &sig.return_value.decl; + let call_code = make_return_and_impl( + &sig.return_value, code, prepare_arg_types, error_fn_context, sig.is_virtual, - ctx, ); let receiver_param = &code.receiver.param; - if sig.is_virtual { + let primary_function = if sig.is_virtual { quote! { #safety_doc - #maybe_unsafe fn #fn_name( + #maybe_unsafe fn #primary_fn_name( #receiver_param #( #params, )* ) #return_decl { @@ -1182,7 +1385,7 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode, ctx: &mut Context) let init_code = &code.init_code; quote! { #safety_doc - #vis #maybe_unsafe fn #fn_name( + #vis #maybe_unsafe fn #primary_fn_name( #receiver_param #( #params, )* varargs: &[Variant] @@ -1209,13 +1412,14 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode, ctx: &mut Context) let init_code = &code.init_code; quote! { #safety_doc - #vis #maybe_unsafe fn #fn_name( + #vis #maybe_unsafe fn #primary_fn_name( #receiver_param #( #params, )* ) #return_decl { unsafe { #init_code + #( #meta_arg_decls )* let __args = [ #( #arg_exprs ),* ]; @@ -1226,10 +1430,267 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode, ctx: &mut Context) } } } + }; + + FnDefinition { + functions: quote! { + #primary_function + #default_fn_code + }, + builders: default_structs_code, + } +} + +fn make_function_definition_with_defaults( + sig: &FnSignature, + code: &FnCode, + full_fn_name: &Ident, +) -> (TokenStream, TokenStream) { + let (default_fn_params, required_fn_params): (Vec<_>, Vec<_>) = sig + .params + .iter() + .partition(|arg| arg.default_value.is_some()); + + let simple_fn_name = safe_ident(sig.function_name); + let extended_fn_name = format_ident!("{}_ex", simple_fn_name); + let vis = make_vis(sig.is_private); + + let (builder_doc, surround_class_prefix) = make_extender_doc(sig, &extended_fn_name); + + let ExtenderReceiver { + object_fn_param, + object_param, + object_arg, + } = make_extender_receiver(sig); + + let Extender { + builder_ty, + builder_lifetime, + builder_anon_lifetime, + builder_methods, + builder_fields, + builder_args, + builder_inits, + } = make_extender(sig, object_fn_param, default_fn_params); + + let receiver_param = &code.receiver.param; + let receiver_self = &code.receiver.self_prefix; + let (required_params, required_args) = make_params_and_args(&required_fn_params); + let return_decl = &sig.return_value.decl; + + // Technically, the builder would not need a lifetime -- it could just maintain an `object_ptr` copy. + // However, this increases the risk that it is used out of place (not immediately for a default-param call). + // Ideally we would require &mut, but then we would need `mut Gd` objects everywhere. + + // #[allow] exceptions: + // - wrong_self_convention: to_*() and from_*() are taken from Godot + // - redundant_field_names: 'value: value' is a possible initialization pattern + // - needless-update: '..self' has nothing left to change + let builders = quote! { + #[doc = #builder_doc] + #[must_use] + pub struct #builder_ty #builder_lifetime { + // #builder_surround_ref + #( #builder_fields, )* + } + + #[allow(clippy::wrong_self_convention, clippy::redundant_field_names, clippy::needless_update)] + impl #builder_lifetime #builder_ty #builder_lifetime { + fn new( + #object_param + #( #required_params, )* + ) -> Self { + Self { + #( #builder_inits, )* + } + } + + #( #builder_methods )* + + #[inline] + pub fn done(self) #return_decl { + #surround_class_prefix #full_fn_name( + #( #builder_args, )* + ) + } + } + }; + + let functions = quote! { + #[inline] + #vis fn #simple_fn_name( + #receiver_param + #( #required_params, )* + ) #return_decl { + #receiver_self #extended_fn_name( + #( #required_args, )* + ).done() + } + + #[inline] + #vis fn #extended_fn_name( + #receiver_param + #( #required_params, )* + ) -> #builder_ty #builder_anon_lifetime { + #builder_ty::new( + #object_arg + #( #required_args, )* + ) + } + }; + + (functions, builders) +} + +fn make_extender_doc(sig: &FnSignature, extended_fn_name: &Ident) -> (String, TokenStream) { + // Not in the above match, because this is true for both static/instance methods. + // Static/instance is determined by first argument (always use fully qualified function call syntax). + let surround_class_prefix; + let builder_doc; + + match sig.surrounding_class { + Some(TyName { rust_ty, .. }) => { + surround_class_prefix = quote! { re_export::#rust_ty:: }; + builder_doc = format!( + "Default-param extender for [`{class}::{method}`][super::{class}::{method}].", + class = rust_ty, + method = extended_fn_name, + ); + } + None => { + // There are currently no default parameters for utility functions + // -> this is currently dead code, but _should_ work if Godot ever adds them. + surround_class_prefix = TokenStream::new(); + builder_doc = format!( + "Default-param extender for [`{function}`][super::{function}].", + function = extended_fn_name + ); + } + }; + + (builder_doc, surround_class_prefix) +} + +fn make_extender_receiver(sig: &FnSignature) -> ExtenderReceiver { + let builder_mut = if matches!(sig.qualifier, FnQualifier::Const) { + quote! {} + } else { + quote! { mut } + }; + + // Treat the object parameter like other parameters, as first in list. + // Only add it if the method is not global or static. + match &sig.surrounding_class { + Some(surrounding_class) if !sig.qualifier.is_static_or_global() => { + let class = &surrounding_class.rust_ty; + + ExtenderReceiver { + object_fn_param: Some(FnParam { + name: ident("surround_object"), + // Not exactly EngineClass, but close enough + type_: RustTy::EngineClass { + tokens: quote! { &'a #builder_mut re_export::#class }, + class: String::new(), + }, + default_value: None, + }), + object_param: quote! { surround_object: &'a #builder_mut re_export::#class, }, + object_arg: quote! { self, }, + } + } + _ => ExtenderReceiver { + object_fn_param: None, + object_param: TokenStream::new(), + object_arg: TokenStream::new(), + }, } } +struct ExtenderReceiver { + object_fn_param: Option, + object_param: TokenStream, + object_arg: TokenStream, +} + +struct Extender { + builder_ty: Ident, + builder_lifetime: TokenStream, + builder_anon_lifetime: TokenStream, + builder_methods: Vec, + builder_fields: Vec, + builder_args: Vec, + builder_inits: Vec, +} + +fn make_extender( + sig: &FnSignature, + object_fn_param: Option, + default_fn_params: Vec<&FnParam>, +) -> Extender { + // Note: could build a documentation string with default values here, but the Rust tokens are not very readable, + // and often not helpful, such as Enum::from_ord(13). Maybe one day those could be resolved and curated. + + let (lifetime, anon_lifetime) = if sig.qualifier.is_static_or_global() { + (TokenStream::new(), TokenStream::new()) + } else { + (quote! { <'a> }, quote! { <'_> }) + }; + + let all_fn_params = object_fn_param.iter().chain(&sig.params); + let len = all_fn_params.size_hint().0; + + let mut result = Extender { + builder_ty: format_ident!("Ex{}", to_pascal_case(sig.function_name)), + builder_lifetime: lifetime, + builder_anon_lifetime: anon_lifetime, + builder_methods: Vec::with_capacity(default_fn_params.len()), + builder_fields: Vec::with_capacity(len), + builder_args: Vec::with_capacity(len), + builder_inits: Vec::with_capacity(len), + }; + + for param in all_fn_params { + let FnParam { + name, + type_, + default_value, + } = param; + + // Initialize with default parameters where available, forward constructor args otherwise + let init = if let Some(value) = default_value { + quote! { #name: #value } + } else { + quote! { #name } + }; + + result.builder_fields.push(quote! { #name: #type_ }); + result.builder_args.push(quote! { self.#name }); + result.builder_inits.push(init); + } + + for param in default_fn_params { + let FnParam { name, type_, .. } = param; + + let method = quote! { + #[inline] + pub fn #name(self, value: #type_) -> Self { + // Currently not testing whether the parameter was already set + Self { + #name: value, + ..self + } + } + }; + + result.builder_methods.push(method); + } + + result +} + fn make_receiver(is_static: bool, is_const: bool, ffi_arg: TokenStream) -> FnReceiver { + // could reuse FnQualifier as parameter + let param = if is_static { quote! {} } else if is_const { @@ -1257,55 +1718,79 @@ fn make_receiver(is_static: bool, is_const: bool, ffi_arg: TokenStream) -> FnRec } } -fn make_params( - method_args: &[MethodArg], +fn make_params_and_impl( + method_args: &[FnParam], is_varcall: bool, - ctx: &mut Context, -) -> [Vec; 4] { + skip_defaults: bool, +) -> [Vec; 5] { let mut params = vec![]; let mut variant_types = vec![]; let mut arg_exprs = vec![]; let mut arg_names = vec![]; - for arg in method_args.iter() { - let param_name = safe_ident(&arg.name); - let param_ty = to_rust_type(&arg.type_, ctx); + let mut meta_arg_decls = vec![]; + + for param in method_args.iter() { + if skip_defaults && param.default_value.is_some() { + continue; + } + + let param_name = ¶m.name; + let param_ty = ¶m.type_; + let canonical_ty = unmap_meta(param_ty); let arg_expr = if is_varcall { quote! { <#param_ty as ToVariant>::to_variant(&#param_name) } } else if let RustTy::EngineClass { tokens: path, .. } = ¶m_ty { quote! { <#path as AsArg>::as_arg_ptr(&#param_name) } } else { + let param_ty = if let Some(canonical_ty) = canonical_ty.as_ref() { + canonical_ty.to_token_stream() + } else { + param_ty.to_token_stream() + }; quote! { <#param_ty as sys::GodotFfi>::sys_const(&#param_name) } }; + // Note: could maybe reuse GodotFuncMarshal in the future + let meta_arg_decl = if let Some(canonical) = canonical_ty { + quote! { let #param_name = #param_name as #canonical; } + } else { + quote! {} + }; + params.push(quote! { #param_name: #param_ty }); variant_types.push(quote! { <#param_ty as VariantMetadata>::variant_type() }); arg_exprs.push(arg_expr); arg_names.push(quote! { #param_name }); + meta_arg_decls.push(meta_arg_decl); } - [params, variant_types, arg_exprs, arg_names] + + [params, variant_types, arg_exprs, arg_names, meta_arg_decls] +} + +fn make_params_and_args(method_args: &[&FnParam]) -> (Vec, Vec) { + method_args + .iter() + .map(|param| { + let param_name = ¶m.name; + let param_ty = ¶m.type_; + + (quote! { #param_name: #param_ty }, quote! { #param_name }) + }) + .unzip() + // .unzip::<(Vec<_>, Vec<_>)>() } -#[allow(clippy::too_many_arguments)] -fn make_return( - return_value: Option<&MethodReturn>, +fn make_return_and_impl( + return_value: &FnReturn, code: &FnCode, prepare_arg_types: TokenStream, error_fn_context: TokenStream, // only for panic message is_virtual: bool, - ctx: &mut Context, -) -> (TokenStream, TokenStream) { - let return_decl: TokenStream; - let return_ty: Option; - - if let Some(ret) = return_value { - let ty = to_rust_type(&ret.type_, ctx); - return_decl = ty.return_decl(); - return_ty = Some(ty); - } else { - return_decl = TokenStream::new(); - return_ty = None; - } +) -> TokenStream { + let FnReturn { + type_: return_ty, .. + } = return_value; let FnCode { varcall_invocation, @@ -1314,7 +1799,7 @@ fn make_return( .. } = code; - let call = match (is_virtual, variant_ffi, return_ty) { + match (is_virtual, variant_ffi, return_ty) { (true, _, _) => { quote! { unimplemented!() @@ -1364,10 +1849,19 @@ fn make_return( } } (false, None, Some(return_ty)) => { + let (ffi_ty, conversion); + if let Some(canonical_ty) = unmap_meta(return_ty) { + ffi_ty = canonical_ty.to_token_stream(); + conversion = quote! { as #return_ty }; + } else { + ffi_ty = return_ty.to_token_stream(); + conversion = quote! {}; + }; + quote! { - <#return_ty as sys::GodotFfi>::from_sys_init_default(|return_ptr| { + <#ffi_ty as sys::GodotFfi>::from_sys_init_default(|return_ptr| { #ptrcall_invocation - }) + }) #conversion } } (false, None, None) => { @@ -1376,9 +1870,7 @@ fn make_return( #ptrcall_invocation } } - }; - - (return_decl, call) + } } fn make_virtual_methods_trait( @@ -1450,30 +1942,34 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream { } } -fn make_virtual_method(class_method: &ClassMethod, ctx: &mut Context) -> TokenStream { - let method_name = virtual_method_name(class_method); +fn make_virtual_method(method: &ClassMethod, ctx: &mut Context) -> TokenStream { + let method_name = virtual_method_name(method); // Virtual methods are never static. - assert!(!class_method.is_static); + assert!(!method.is_static); - make_function_definition( + let definition = make_function_definition( &FnSignature { function_name: method_name, + surrounding_class: None, // no default parameters needed for virtual methods is_private: false, is_virtual: true, - method_args: option_as_slice(&class_method.arguments), - return_value: class_method.return_value.as_ref(), + qualifier: FnQualifier::for_method(method.is_const, method.is_static), + params: FnParam::new_range(&method.arguments, ctx), + return_value: FnReturn::new(&method.return_value, ctx), }, &FnCode { - receiver: make_receiver(false, class_method.is_const, TokenStream::new()), + receiver: make_receiver(false, method.is_const, TokenStream::new()), // make_return() requests following args, but they are not used for virtual methods. We can provide empty streams. variant_ffi: None, init_code: TokenStream::new(), varcall_invocation: TokenStream::new(), ptrcall_invocation: TokenStream::new(), }, - ctx, - ) + ); + + // Virtual methods have no builders. + definition.functions } fn make_all_virtual_methods( @@ -1493,11 +1989,13 @@ fn make_all_virtual_methods( // Get virtuals defined on the current class. extend_virtuals(class); + // Add virtuals from superclasses. for base in all_base_names { let superclass = ctx.get_engine_class(base); extend_virtuals(superclass); } + all_virtuals .into_iter() .filter_map(|method| { @@ -1536,15 +2034,18 @@ fn virtual_method_name(class_method: &ClassMethod) -> &str { } fn function_uses_pointers(sig: &FnSignature) -> bool { - if sig.method_args.iter().any(|x| x.type_.contains('*')) { - return true; - } + let has_pointer_params = sig + .params + .iter() + .any(|param| matches!(param.type_, RustTy::RawPointer { .. })); - if let Some(return_value) = sig.return_value { - if return_value.type_.contains('*') { - return true; - } - } + let has_pointer_return = matches!(sig.return_value.type_, Some(RustTy::RawPointer { .. })); - false + // No short-circuiting due to variable decls, but that's fine. + has_pointer_params || has_pointer_return +} + +fn function_uses_default_params(sig: &FnSignature) -> bool { + sig.params.iter().any(|arg| arg.default_value.is_some()) + && !special_cases::is_excluded_from_default_params(sig.surrounding_class, sig.function_name) } diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index 53209d111..a069ed274 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -5,7 +5,7 @@ */ use crate::api_parser::Class; -use crate::{util, ExtensionApi, RustTy, TyName}; +use crate::{util, ExtensionApi, GodotTy, RustTy, TyName}; use proc_macro2::Ident; use quote::format_ident; use std::collections::{HashMap, HashSet}; @@ -17,7 +17,7 @@ pub(crate) struct Context<'a> { native_structures_types: HashSet<&'a str>, singletons: HashSet<&'a str>, inheritance_tree: InheritanceTree, - cached_rust_types: HashMap, + cached_rust_types: HashMap, notifications_by_class: HashMap>, notification_enum_names_by_class: HashMap, } @@ -151,7 +151,7 @@ impl<'a> Context<'a> { &self.inheritance_tree } - pub fn find_rust_type(&'a self, ty: &str) -> Option<&'a RustTy> { + pub fn find_rust_type(&'a self, ty: &GodotTy) -> Option<&'a RustTy> { self.cached_rust_types.get(ty) } @@ -166,8 +166,8 @@ impl<'a> Context<'a> { .clone() } - pub fn insert_rust_type(&mut self, ty: &str, resolved: RustTy) { - let prev = self.cached_rust_types.insert(ty.to_string(), resolved); + pub fn insert_rust_type(&mut self, godot_ty: GodotTy, resolved: RustTy) { + let prev = self.cached_rust_types.insert(godot_ty, resolved); assert!(prev.is_none(), "no overwrites of RustTy"); } } diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index e95159a2b..3f35b8f6a 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -134,7 +134,26 @@ fn rustfmt_if_needed(_out_files: Vec) {} // ---------------------------------------------------------------------------------------------------------------------------------------------- // Shared utility types -#[derive(Clone)] +// Same as above, without lifetimes + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +struct GodotTy { + ty: String, + meta: Option, +} + +// impl GodotTy { +// fn new<'a>(ty: &'a String, meta: &'a Option) -> Self { +// Self { +// ty: ty.clone(), +// meta: meta.clone(), +// } +// } +// } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[derive(Clone, Debug)] enum RustTy { /// `bool`, `Vector3i` BuiltinIdent(Ident), @@ -193,7 +212,6 @@ impl ToTokens for RustTy { RustTy::EngineArray { tokens: path, .. } => path.to_tokens(tokens), RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens), RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens), - //RustTy::Other(path) => path.to_tokens(tokens), } } } diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index ede8bd381..f2d286a9f 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -15,6 +15,8 @@ // NOTE: the methods are generally implemented on Godot types (e.g. AABB, not Aabb) +#![allow(clippy::match_like_matches_macro)] // if there is only one rule + use crate::TyName; #[rustfmt::skip] @@ -60,6 +62,18 @@ pub(crate) fn is_private(class_name: &TyName, godot_method_name: &str) -> bool { } } +#[rustfmt::skip] +pub(crate) fn is_excluded_from_default_params(class_name: Option<&TyName>, godot_method_name: &str) -> bool { + // None if global/utilities function + let class_name = class_name.map_or("", |ty| ty.godot_ty.as_str()); + + match (class_name, godot_method_name) { + | ("Object", "notification") + + => true, _ => false + } +} + /// True if builtin type is excluded (`NIL` or scalars) pub(crate) fn is_builtin_type_deleted(class_name: &TyName) -> bool { let name = class_name.godot_ty.as_str(); diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index b93d4541c..340c76f03 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -6,9 +6,9 @@ use crate::api_parser::{ClassConstant, Enum}; use crate::special_cases::is_builtin_scalar; -use crate::{Context, ModName, RustTy, TyName}; +use crate::{Context, GodotTy, ModName, RustTy, TyName}; use proc_macro2::{Ident, Literal, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct NativeStructuresField { @@ -238,27 +238,77 @@ pub fn safe_ident(s: &str) -> Ident { } } -fn to_hardcoded_rust_type(ty: &str) -> Option<&str> { +/// Converts a potential "meta" type (like u32) to its canonical type (like i64). +/// +/// Avoids dragging along the meta type through [`RustTy::BuiltinIdent`]. +pub(crate) fn unmap_meta(rust_ty: &RustTy) -> Option { + let RustTy::BuiltinIdent(rust_ty) = rust_ty else { + return None; + }; + + // Don't use match because it needs allocation (unless == is repeated) + // Even though i64 and f64 can have a meta of the same type, there's no need to return that here, as there won't be any conversion. + + for ty in ["u64", "u32", "u16", "u8", "i32", "i16", "i8"] { + if rust_ty == ty { + return Some(ident("i64")); + } + } + + if rust_ty == "f32" { + return Some(ident("f64")); + } + + None +} + +fn to_hardcoded_rust_ident(full_ty: &GodotTy) -> Option<&str> { + let ty = full_ty.ty.as_str(); + let meta = full_ty.meta.as_deref(); + + let result = match (ty, meta) { + // Integers + ("int", Some("int64") | None) => "i64", + ("int", Some("int32")) => "i32", + ("int", Some("int16")) => "i16", + ("int", Some("int8")) => "i8", + ("int", Some("uint64")) => "u64", + ("int", Some("uint32")) => "u32", + ("int", Some("uint16")) => "u16", + ("int", Some("uint8")) => "u8", + + // Floats + ("float", Some("double") | None) => "f64", + ("float", Some("float")) => "f32", + + // Others + ("bool", None) => "bool", + ("String", None) => "GodotString", + ("Array", None) => "VariantArray", + + // Types needed for native structures mapping + ("uint8_t", None) => "u8", + ("uint16_t", None) => "u16", + ("uint32_t", None) => "u32", + ("uint64_t", None) => "u64", + ("int8_t", None) => "i8", + ("int16_t", None) => "i16", + ("int32_t", None) => "i32", + ("int64_t", None) => "i64", + ("real_t", None) => "real", + ("void", None) => "c_void", + _ => return None, + }; + + Some(result) +} + +fn to_hardcoded_rust_enum(ty: &str) -> Option<&str> { let result = match ty { - "int" => "i64", - "float" => "f64", - "String" => "GodotString", - "Array" => "VariantArray", //"enum::Error" => "GodotError", "enum::Variant.Type" => "VariantType", "enum::Variant.Operator" => "VariantOperator", "enum::Vector3.Axis" => "Vector3Axis", - // Types needed for native structures mapping - "uint8_t" => "u8", - "uint16_t" => "u16", - "uint32_t" => "u32", - "uint64_t" => "u64", - "int8_t" => "i8", - "int16_t" => "i16", - "int32_t" => "i32", - "int64_t" => "i64", - "real_t" => "real", - "void" => "c_void", _ => return None, }; Some(result) @@ -267,12 +317,12 @@ fn to_hardcoded_rust_type(ty: &str) -> Option<&str> { /// Maps an input type to a Godot type with the same C representation. This is subtly different than [`to_rust_type`], /// which maps to an appropriate corresponding Rust type. This function should be used in situations where the C ABI for /// a type must match the Godot equivalent exactly, such as when dealing with pointers. -pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context<'_>) -> RustTy { +pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> RustTy { match ty { "int" => RustTy::BuiltinIdent(ident("i32")), "float" => RustTy::BuiltinIdent(ident("f32")), "double" => RustTy::BuiltinIdent(ident("f64")), - _ => to_rust_type(ty, ctx), + _ => to_rust_type(ty, None, ctx), } } @@ -280,19 +330,26 @@ pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context<'_>) -> RustTy { /// /// Uses an internal cache (via `ctx`), as several types are ubiquitous. // TODO take TyName as input -pub(crate) fn to_rust_type(ty: &str, ctx: &mut Context<'_>) -> RustTy { +pub(crate) fn to_rust_type<'a>(ty: &'a str, meta: Option<&'a String>, ctx: &mut Context) -> RustTy { + let full_ty = GodotTy { + ty: ty.to_string(), + meta: meta.cloned(), + }; + // Separate find + insert slightly slower, but much easier with lifetimes // The insert path will be hit less often and thus doesn't matter - if let Some(rust_ty) = ctx.find_rust_type(ty) { + if let Some(rust_ty) = ctx.find_rust_type(&full_ty) { rust_ty.clone() } else { - let rust_ty = to_rust_type_uncached(ty, ctx); - ctx.insert_rust_type(ty, rust_ty.clone()); + let rust_ty = to_rust_type_uncached(&full_ty, ctx); + ctx.insert_rust_type(full_ty, rust_ty.clone()); rust_ty } } -fn to_rust_type_uncached(ty: &str, ctx: &mut Context) -> RustTy { +fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy { + let ty = full_ty.ty.as_str(); + /// Transforms a Godot class/builtin/enum IDENT (without `::` or other syntax) to a Rust one fn rustify_ty(ty: &str) -> Ident { if is_builtin_scalar(ty) { @@ -303,30 +360,36 @@ fn to_rust_type_uncached(ty: &str, ctx: &mut Context) -> RustTy { } if ty.ends_with('*') { - // Pointer type; strip '*', see if const, and then resolve the - // inner type. + // Pointer type; strip '*', see if const, and then resolve the inner type. let mut ty = ty[0..ty.len() - 1].to_string(); + // 'const' should apply to the innermost pointer, if present. let is_const = ty.starts_with("const ") && !ty.ends_with('*'); if is_const { ty = ty.replace("const ", ""); } - // .trim() is necessary here, as the Godot extension API - // places a space between a type and its stars if it's a - // double pointer. That is, Godot writes "int*" but, if it's a - // double pointer, then it writes "int **" instead (with a - // space in the middle). - let inner_type = to_rust_type(ty.trim(), ctx); + + // .trim() is necessary here, as Godot places a space between a type and the stars when representing a double pointer. + // Example: "int*" but "int **". + let inner_type = to_rust_type(ty.trim(), None, ctx); return RustTy::RawPointer { inner: Box::new(inner_type), is_const, }; } - if let Some(hardcoded) = to_hardcoded_rust_type(ty) { + // Only place where meta is relevant is here. + if let Some(hardcoded) = to_hardcoded_rust_ident(full_ty) { return RustTy::BuiltinIdent(ident(hardcoded)); } + if let Some(hardcoded) = to_hardcoded_rust_enum(ty) { + return RustTy::EngineEnum { + tokens: ident(hardcoded).to_token_stream(), + surrounding_class: None, // would need class passed in + }; + } + let qualified_enum = ty .strip_prefix("enum::") .or_else(|| ty.strip_prefix("bitfield::")); @@ -356,11 +419,7 @@ fn to_rust_type_uncached(ty: &str, ctx: &mut Context) -> RustTy { return RustTy::BuiltinIdent(rustify_ty(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(rustify_ty(elem_ty)); - } - - let rust_elem_ty = to_rust_type(elem_ty, ctx); + let rust_elem_ty = to_rust_type(elem_ty, None, ctx); return if ctx.is_builtin(elem_ty) { RustTy::BuiltinArray(quote! { Array<#rust_elem_ty> }) } else { @@ -415,3 +474,311 @@ pub fn parse_native_structures_format(input: &str) -> Option TokenStream { + // println!("\n> to_rust_expr({expr}, {ty:?})"); + + to_rust_expr_inner(expr, ty, false) +} + +fn to_rust_expr_inner(expr: &str, ty: &RustTy, is_inner: bool) -> TokenStream { + // println!("> to_rust_expr_inner({expr}, {is_inner})"); + + // Simple literals + match expr { + "true" => return quote! { true }, + "false" => return quote! { false }, + "[]" | "{}" if is_inner => return quote! {}, + "[]" => return quote! { Array::new() }, // VariantArray or Array + "{}" => return quote! { Dictionary::new() }, + "null" => { + return match ty { + RustTy::BuiltinIdent(ident) if ident == "Variant" => quote! { Variant::nil() }, + RustTy::EngineClass { .. } => quote! { unimplemented!("see #156") }, + _ => panic!("null not representable in target type {ty:?}"), + } + } + // empty string appears only for Callable/Rid in 4.0; default ctor syntax in 4.1+ + "" | "RID()" | "Callable()" if !is_inner => { + return match ty { + RustTy::BuiltinIdent(ident) if ident == "Rid" => quote! { Rid::Invalid }, + RustTy::BuiltinIdent(ident) if ident == "Callable" => { + quote! { Callable::invalid() } + } + _ => panic!("empty string not representable in target type {ty:?}"), + } + } + _ => {} + } + + // Integer literals + if let Ok(num) = expr.parse::() { + let lit = Literal::i64_unsuffixed(num); + return match ty { + RustTy::EngineEnum { .. } => quote! { crate::obj::EngineEnum::from_ord(#lit) }, + RustTy::BuiltinIdent(ident) if ident == "Variant" => quote! { Variant::from(#lit) }, + RustTy::BuiltinIdent(ident) + if ident == "i64" || ident == "f64" || unmap_meta(ty).is_some() => + { + suffixed_lit(num, ident) + } + _ if is_inner => quote! { #lit as _ }, + // _ => quote! { #lit as #ty }, + _ => panic!("cannot map integer literal {expr} to type {ty:?}"), + }; + } + + // Float literals (some floats already handled by integer literals) + if let Ok(num) = expr.parse::() { + return match ty { + RustTy::BuiltinIdent(ident) if ident == "f64" || unmap_meta(ty).is_some() => { + suffixed_lit(num, ident) + } + _ if is_inner => { + let lit = Literal::f64_unsuffixed(num); + quote! { #lit as _ } + } + _ => panic!("cannot map float literal {expr} to type {ty:?}"), + }; + } + + // "..." -> String|StringName|NodePath + if let Some(expr) = expr.strip_prefix('"') { + let expr = expr.strip_suffix('"').expect("unmatched opening '\"'"); + return if is_inner { + quote! { #expr } + } else { + match ty { + RustTy::BuiltinIdent(ident) + if ident == "GodotString" || ident == "StringName" || ident == "NodePath" => + { + quote! { #ident::from(#expr) } + } + _ => quote! { GodotString::from(#expr) }, + //_ => panic!("cannot map string literal \"{expr}\" to type {ty:?}"), + } + }; + } + + // "&..." -> StringName + if let Some(expr) = expr.strip_prefix("&\"") { + let expr = expr.strip_suffix('"').expect("unmatched opening '&\"'"); + return quote! { StringName::from(#expr) }; + } + + // "^..." -> NodePath + if let Some(expr) = expr.strip_prefix("^\"") { + let expr = expr.strip_suffix('"').expect("unmatched opening '^\"'"); + return quote! { NodePath::from(#expr) }; + } + + // Constructor calls + if let Some(pos) = expr.find('(') { + let godot_ty = &expr[..pos]; + let wrapped = expr[pos + 1..].strip_suffix(')').expect("unmatched '('"); + + let (rust_ty, ctor) = match godot_ty { + "NodePath" => ("NodePath", "from"), + "String" => ("GodotString", "from"), + "StringName" => ("StringName", "from"), + "RID" => ("Rid", "default"), + "Rect2" => ("Rect2", "from_components"), + "Rect2i" => ("Rect2i", "from_components"), + "Vector2" | "Vector2i" | "Vector3" | "Vector3i" => (godot_ty, "new"), + "Transform2D" => ("Transform2D", "__internal_codegen"), + "Transform3D" => ("Transform3D", "__internal_codegen"), + "Color" => { + if wrapped.chars().filter(|&c| c == ',').count() == 2 { + ("Color", "from_rgb") + } else { + ("Color", "from_rgba") + } + } + array if array.starts_with("Packed") && array.ends_with("Array") => { + assert_eq!(wrapped, "", "only empty packed arrays supported for now"); + (array, "new") + } + array if array.starts_with("Array[") => { + assert_eq!(wrapped, "[]", "only empty typed arrays supported for now"); + ("Array", "new") + } + _ => panic!("unsupported type: {godot_ty}"), + }; + + // Split wrapped parts by comma + let subtokens = wrapped.split(',').map(|part| { + let part = part.trim(); // ignore whitespace around commas + + // If there is no comma, there will still be one part (the empty string) -- do not substitute + if part.is_empty() { + quote! {} + } else { + to_rust_expr_inner(part, ty, true) + } + }); + + let rust_ty = ident(rust_ty); + let ctor = ident(ctor); + return quote! { + #rust_ty::#ctor(#(#subtokens),*) + }; + } + + panic!( + "Not yet supported GDScript expression: '{expr}'\n\ + Please report this at https://github.com/godot-rust/gdext/issues/new." + ); +} + +fn suffixed_lit(num: impl std::fmt::Display, suffix: &Ident) -> TokenStream { + // i32, u16 etc happens to be also the literal suffix + let combined = format!("{num}{suffix}"); + combined + .parse::() + .unwrap_or_else(|_| panic!("invalid literal {combined}")) + .to_token_stream() +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[test] +fn gdscript_to_rust_expr() { + // The 'None' type is used to simulate absence of type information. Some tests are commented out, because this functionality is not + // yet needed. If we ever want to reuse to_rust_expr() in other contexts, we could re-enable them. + + let ty_int = RustTy::BuiltinIdent(ident("i64")); + let ty_int = Some(&ty_int); + + let ty_int_u16 = RustTy::BuiltinIdent(ident("u16")); + let ty_int_u16 = Some(&ty_int_u16); + + let ty_float = RustTy::BuiltinIdent(ident("f64")); + let ty_float = Some(&ty_float); + + let ty_float_f32 = RustTy::BuiltinIdent(ident("f32")); + let ty_float_f32 = Some(&ty_float_f32); + + let ty_enum = RustTy::EngineEnum { + tokens: quote! { SomeEnum }, + surrounding_class: None, + }; + let ty_enum = Some(&ty_enum); + + let ty_variant = RustTy::BuiltinIdent(ident("Variant")); + let ty_variant = Some(&ty_variant); + + // let ty_object = RustTy::EngineClass { + // tokens: quote! { Gd }, + // class: "MyClass".to_string(), + // }; + // let ty_object = Some(&ty_object); + + let ty_string = RustTy::BuiltinIdent(ident("GodotString")); + let ty_string = Some(&ty_string); + + let ty_stringname = RustTy::BuiltinIdent(ident("StringName")); + let ty_stringname = Some(&ty_stringname); + + let ty_nodepath = RustTy::BuiltinIdent(ident("NodePath")); + let ty_nodepath = Some(&ty_nodepath); + + #[rustfmt::skip] + let table = [ + // int + ("0", ty_int, quote! { 0i64 }), + ("-1", ty_int, quote! { -1i64 }), + ("2147483647", ty_int, quote! { 2147483647i64 }), + ("-2147483648", ty_int, quote! { -2147483648i64 }), + // ("2147483647", None, quote! { 2147483647 }), + // ("-2147483648", None, quote! { -2147483648 }), + + // int, meta=uint16 + ("0", ty_int_u16, quote! { 0u16 }), + ("65535", ty_int_u16, quote! { 65535u16 }), + + // float (from int/float) + ("0", ty_float, quote! { 0f64 }), + ("2147483647", ty_float, quote! { 2147483647f64 }), + ("-1.5", ty_float, quote! { -1.5f64 }), + ("2e3", ty_float, quote! { 2000f64 }), + // ("1.0", None, quote! { 1.0 }), + // ("1e-05", None, quote! { 0.00001 }), + + // float, meta=f32 (from int/float) + ("0", ty_float_f32, quote! { 0f32 }), + ("-2147483648", ty_float_f32, quote! { -2147483648f32 }), + ("-2.5", ty_float_f32, quote! { -2.5f32 }), + ("3e3", ty_float, quote! { 3000f64 }), + + // enum (from int) + ("7", ty_enum, quote! { crate::obj::EngineEnum::from_ord(7) }), + + // Variant (from int) + ("8", ty_variant, quote! { Variant::from(8) }), + + // Special literals + ("true", None, quote! { true }), + ("false", None, quote! { false }), + ("{}", None, quote! { Dictionary::new() }), + ("[]", None, quote! { Array::new() }), + + ("null", ty_variant, quote! { Variant::nil() }), + // TODO implement #156: + //("null", ty_object, quote! { None }), + + // String-likes + ("\" \"", None, quote! { GodotString::from(" ") }), + ("\"{_}\"", None, quote! { GodotString::from("{_}") }), + ("&\"text\"", None, quote! { StringName::from("text") }), + ("^\"text\"", None, quote! { NodePath::from("text") }), + + ("\"text\"", ty_string, quote! { GodotString::from("text") }), + ("\"text\"", ty_stringname, quote! { StringName::from("text") }), + ("\"text\"", ty_nodepath, quote! { NodePath::from("text") }), + + // Composites + ("NodePath(\"\")", None, quote! { NodePath::from("") }), + ("Color(1, 0, 0.5, 1)", None, quote! { Color::from_rgba(1 as _, 0 as _, 0.5 as _, 1 as _) }), + ("Vector3(0, 1, 2.5)", None, quote! { Vector3::new(0 as _, 1 as _, 2.5 as _) }), + ("Rect2(1, 2.2, -3.3, 0)", None, quote! { Rect2::from_components(1 as _, 2.2 as _, -3.3 as _, 0 as _) }), + ("Rect2i(1, 2.2, -3.3, 0)", None, quote! { Rect2i::from_components(1 as _, 2.2 as _, -3.3 as _, 0 as _) }), + ("PackedFloat32Array()", None, quote! { PackedFloat32Array::new() }), + // Due to type inference, it should be enough to just write `Array::new()` + ("Array[Plane]([])", None, quote! { Array::new() }), + ("Array[RDPipelineSpecializationConstant]([])", None, quote! { Array::new() }), + ("Array[RID]([])", None, quote! { Array::new() }), + + // Composites with destructuring + ("Transform3D(1, 2, 3, 4, -1.1, -1.2, -1.3, -1.4, 0, 0, 0, 0)", None, quote! { + Transform3D::__internal_codegen( + 1 as _, 2 as _, 3 as _, + 4 as _, -1.1 as _, -1.2 as _, + -1.3 as _, -1.4 as _, 0 as _, + 0 as _, 0 as _, 0 as _ + ) + }), + + ("Transform2D(1, 2, -1.1,1.2, 0, 0)", None, quote! { + Transform2D::__internal_codegen( + 1 as _, 2 as _, + -1.1 as _, 1.2 as _, + 0 as _, 0 as _ + ) + }), + ]; + + for (gdscript, ty, rust) in table { + // Use arbitrary type if not specified -> should not be read + let ty_dontcare = RustTy::EngineArray { + tokens: TokenStream::new(), + elem_class: String::new(), + }; + let ty = ty.unwrap_or(&ty_dontcare); + + let actual = to_rust_expr(gdscript, ty).to_string(); + let expected = rust.to_string(); + + // println!("{actual} -> {expected}"); + assert_eq!(actual, expected); + } +} diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index c50174d66..efec5fc1c 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -17,13 +17,11 @@ pub(crate) fn generate_utilities_file( gen_path: &Path, out_files: &mut Vec, ) { - let mut utility_fn_defs = vec![]; - for utility_fn in &api.utility_functions { - // note: category unused -> could be their own mod - - let def = make_utility_function_definition(utility_fn, ctx); - utility_fn_defs.push(def); - } + // note: category unused -> could be their own mod + let utility_fn_defs = api + .utility_functions + .iter() + .map(|utility_fn| make_utility_function_definition(utility_fn, ctx)); let tokens = quote! { //! Global utility functions. diff --git a/godot-core/src/builtin/callable.rs b/godot-core/src/builtin/callable.rs index 83fe981d0..9b0849a8c 100644 --- a/godot-core/src/builtin/callable.rs +++ b/godot-core/src/builtin/callable.rs @@ -53,6 +53,18 @@ impl Callable { } } + /// Creates an invalid/empty object that is not able to be called. + /// + /// _Godot equivalent: `Callable()`_ + pub fn invalid() -> Self { + unsafe { + Self::from_sys_init(|self_ptr| { + let ctor = ::godot_ffi::builtin_fn!(callable_construct_default); + ctor(self_ptr, std::ptr::null_mut()) + }) + } + } + /// Calls the method represented by this callable. /// /// Arguments passed should match the method's signature. @@ -162,7 +174,9 @@ impl Callable { impl_builtin_traits! { for Callable { - Default => callable_construct_default; + // Currently no Default::default() to encourage explicit valid initialization. + //Default => callable_construct_default; + // Equality for custom callables depend on the equality implementation of that custom callable. This // is from what i can tell currently implemented as total equality in all cases, but i dont believe // there are any guarantees that all implementations of equality for custom callables will be. @@ -182,7 +196,7 @@ unsafe impl GodotFfi for Callable { ffi_methods! { type sys::GDExtensionTypePtr = *mut Opaque; .. } unsafe fn from_sys_init_default(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Self { - let mut result = Self::default(); + let mut result = Self::invalid(); init_fn(result.sys_mut()); result } diff --git a/godot-core/src/builtin/transform2d.rs b/godot-core/src/builtin/transform2d.rs index 956a55c0d..9ba81c896 100644 --- a/godot-core/src/builtin/transform2d.rs +++ b/godot-core/src/builtin/transform2d.rs @@ -112,6 +112,21 @@ impl Transform2D { ) } + /// Unstable, used to simplify codegen. Too many parameters for public API and easy to have off-by-one, `from_cols()` is preferred. + #[doc(hidden)] + #[rustfmt::skip] + #[allow(clippy::too_many_arguments)] + pub const fn __internal_codegen( + ax: real, ay: real, + bx: real, by: real, + ox: real, oy: real, + ) -> Self { + Self::from_cols( + Vector2::new(ax, ay), + Vector2::new(bx, by), + Vector2::new(ox, oy), + ) + } /// Create a reference to the first two columns of the transform /// interpreted as a [`Basis2D`]. fn basis<'a>(&'a self) -> &'a Basis2D { diff --git a/godot-core/src/builtin/transform3d.rs b/godot-core/src/builtin/transform3d.rs index b14a4022e..33796ce5d 100644 --- a/godot-core/src/builtin/transform3d.rs +++ b/godot-core/src/builtin/transform3d.rs @@ -92,6 +92,24 @@ impl Transform3D { } } + /// Unstable, used to simplify codegen. Too many parameters for public API and easy to have off-by-one, `from_cols()` is preferred. + #[doc(hidden)] + #[rustfmt::skip] + #[allow(clippy::too_many_arguments)] + pub const fn __internal_codegen( + ax: real, ay: real, az: real, + bx: real, by: real, bz: real, + cx: real, cy: real, cz: real, + ox: real, oy: real, oz: real + ) -> Self { + Self::from_cols( + Vector3::new(ax, ay, az), + Vector3::new(bx, by, bz), + Vector3::new(cx, cy, cz), + Vector3::new(ox, oy, oz), + ) + } + /// Returns the inverse of the transform, under the assumption that the /// transformation is composed of rotation, scaling and translation. /// diff --git a/godot-core/src/builtin/vectors/vector_axis.rs b/godot-core/src/builtin/vectors/vector_axis.rs index e73a842be..2152a218d 100644 --- a/godot-core/src/builtin/vectors/vector_axis.rs +++ b/godot-core/src/builtin/vectors/vector_axis.rs @@ -5,6 +5,7 @@ */ use crate::builtin::{real, Vector2, Vector2i, Vector3, Vector3i, Vector4, Vector4i}; +use crate::obj::EngineEnum; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; @@ -62,6 +63,8 @@ macro_rules! swizzle { }}; } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + /// Trait that allows conversion from tuples to vectors. /// /// Is implemented instead of `From`/`Into` because it provides type inference. @@ -81,12 +84,28 @@ pub enum Vector2Axis { Y, } +impl EngineEnum for Vector2Axis { + fn try_from_ord(ord: i32) -> Option { + match ord { + 0 => Some(Self::X), + 1 => Some(Self::Y), + _ => None, + } + } + + fn ord(self) -> i32 { + self as i32 + } +} + // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector2Axis { ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + /// Enumerates the axes in a [`Vector3`]. // TODO auto-generate this, alongside all the other builtin type's enums #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] @@ -102,12 +121,29 @@ pub enum Vector3Axis { Z, } +impl EngineEnum for Vector3Axis { + fn try_from_ord(ord: i32) -> Option { + match ord { + 0 => Some(Self::X), + 1 => Some(Self::Y), + 2 => Some(Self::Z), + _ => None, + } + } + + fn ord(self) -> i32 { + self as i32 + } +} + // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector3Axis { ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + /// Enumerates the axes in a [`Vector4`]. #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] #[repr(i32)] @@ -125,12 +161,30 @@ pub enum Vector4Axis { W, } +impl EngineEnum for Vector4Axis { + fn try_from_ord(ord: i32) -> Option { + match ord { + 0 => Some(Self::X), + 1 => Some(Self::Y), + 2 => Some(Self::Z), + 3 => Some(Self::W), + _ => None, + } + } + + fn ord(self) -> i32 { + self as i32 + } +} + // SAFETY: // This type is represented as `Self` in Godot, so `*mut Self` is sound. unsafe impl GodotFfi for Vector4Axis { ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- + impl_vector_index!(Vector2, real, (x, y), Vector2Axis, (X, Y)); impl_vector_index!(Vector2i, i32, (x, y), Vector2Axis, (X, Y)); diff --git a/godot-core/src/engine.rs b/godot-core/src/engine.rs index 1e1d1d956..d93ba87f6 100644 --- a/godot-core/src/engine.rs +++ b/godot-core/src/engine.rs @@ -10,7 +10,6 @@ use crate::builtin::{GodotString, NodePath}; use crate::obj::dom::EngineDomain; use crate::obj::{Gd, GodotClass, Inherits}; -use resource_loader::CacheMode; pub use crate::gen::central::global; pub use crate::gen::classes::*; @@ -27,8 +26,6 @@ pub mod native { pub use crate::gen::native::*; } -use self::packed_scene::GenEditState; - /// Extension trait for convenience functions on `PackedScene` pub trait PackedSceneExt { /// ⚠️ Instantiates the scene as type `T`, panicking if not found or bad type. @@ -56,8 +53,7 @@ impl PackedSceneExt for PackedScene { where T: Inherits, { - self.instantiate(GenEditState::GEN_EDIT_STATE_DISABLED) - .and_then(|gd| gd.try_cast::()) + self.instantiate().and_then(|gd| gd.try_cast::()) } } @@ -224,10 +220,8 @@ where let type_hint = T::CLASS_NAME; ResourceLoader::singleton() - .load( - path.clone(), /* TODO unclone */ - type_hint.into(), - CacheMode::CACHE_MODE_REUSE, - ) + .load_ex(path.clone()) + .type_hint(type_hint.into()) + .done() // TODO unclone .and_then(|res| res.try_cast::()) } diff --git a/godot-core/src/lib.rs b/godot-core/src/lib.rs index 1d4c484a9..3e09e5239 100644 --- a/godot-core/src/lib.rs +++ b/godot-core/src/lib.rs @@ -35,9 +35,11 @@ pub mod engine; // Output of generated code. Mimics the file structure, symbols are re-exported. #[rustfmt::skip] -#[allow(unused_imports, dead_code, non_upper_case_globals, non_snake_case, clippy::too_many_arguments, clippy::let_and_return, clippy::new_ret_no_self)] +#[allow(unused_imports, dead_code, non_upper_case_globals, non_snake_case)] +#[allow(clippy::too_many_arguments, clippy::let_and_return, clippy::new_ret_no_self)] +#[allow(clippy::wrong_self_convention)] // to_string() is const #[allow(clippy::upper_case_acronyms)] // TODO remove this line once we transform names -#[allow(clippy::wrong_self_convention)] // TODO remove once to_string is const +#[allow(unreachable_code, clippy::unimplemented)] // TODO remove once #153 is implemented mod gen; #[doc(hidden)] diff --git a/itest/rust/build.rs b/itest/rust/build.rs index 27081d4d1..defcdce43 100644 --- a/itest/rust/build.rs +++ b/itest/rust/build.rs @@ -62,7 +62,7 @@ fn collect_inputs() -> Vec { push!(inputs; Vector4, Vector4, Vector4(-18.5, 24.75, -1.25, 777.875), Vector4::new(-18.5, 24.75, -1.25, 777.875)); push!(inputs; Vector2i, Vector2i, Vector2i(-2147483648, 2147483647), Vector2i::new(-2147483648, 2147483647)); push!(inputs; Vector3i, Vector3i, Vector3i(-1, -2147483648, 2147483647), Vector3i::new(-1, -2147483648, 2147483647)); - push!(inputs; Callable, Callable, Callable(), Callable::default()); + push!(inputs; Callable, Callable, Callable(), Callable::invalid()); // Data structures // TODO enable below, when GDScript has typed array literals, or find a hack with eval/lambdas diff --git a/itest/rust/src/array_test.rs b/itest/rust/src/array_test.rs index b247d1274..a190e5ba5 100644 --- a/itest/rust/src/array_test.rs +++ b/itest/rust/src/array_test.rs @@ -367,14 +367,11 @@ fn untyped_array_pass_to_godot_func() { #[itest] fn untyped_array_return_from_godot_func() { - use godot::engine::node::InternalMode; - use godot::engine::Node; - // There aren't many API functions that return an untyped array. let mut node = Node::new_alloc(); let mut child = Node::new_alloc(); child.set_name("child_node".into()); - node.add_child(child.share(), false, InternalMode::INTERNAL_MODE_DISABLED); + node.add_child(child.share()); node.queue_free(); // Do not leak even if the test fails. let result = node.get_node_and_resource("child_node".into()); @@ -408,15 +405,12 @@ fn typed_array_pass_to_godot_func() { #[itest] fn typed_array_return_from_godot_func() { - use godot::engine::node::InternalMode; - use godot::engine::Node; - let mut node = Node::new_alloc(); let mut child = Node::new_alloc(); child.set_name("child_node".into()); - node.add_child(child.share(), false, InternalMode::INTERNAL_MODE_DISABLED); + node.add_child(child.share()); node.queue_free(); // Do not leak even if the test fails. - let children = node.get_children(false); + let children = node.get_children(); assert_eq!(children, array![child]); } diff --git a/itest/rust/src/callable_test.rs b/itest/rust/src/callable_test.rs index dba903cae..a2ffd0968 100644 --- a/itest/rust/src/callable_test.rs +++ b/itest/rust/src/callable_test.rs @@ -50,10 +50,10 @@ fn callable_validity() { assert!(obj.callable("doesn't_exist").object().is_some()); // null object - assert!(!Callable::default().is_valid()); - assert!(Callable::default().is_null()); - assert!(!Callable::default().is_custom()); - assert!(Callable::default().object().is_none()); + assert!(!Callable::invalid().is_valid()); + assert!(Callable::invalid().is_null()); + assert!(!Callable::invalid().is_custom()); + assert!(Callable::invalid().object().is_none()); } #[itest] @@ -72,9 +72,9 @@ fn callable_object_method() { assert_eq!(callable.object_id(), Some(obj.instance_id())); assert_eq!(callable.method_name(), Some("foo".into())); - assert_eq!(Callable::default().object(), None); - assert_eq!(Callable::default().object_id(), None); - assert_eq!(Callable::default().method_name(), None); + assert_eq!(Callable::invalid().object(), None); + assert_eq!(Callable::invalid().object_id(), None); + assert_eq!(Callable::invalid().method_name(), None); } #[itest] @@ -91,7 +91,7 @@ fn callable_call() { // errors in godot but does not crash assert_eq!(callable.callv(varray!["string"]), Variant::nil()); - assert_eq!(Callable::default().callv(varray![1, 2, 3]), Variant::nil()); + assert_eq!(Callable::invalid().callv(varray![1, 2, 3]), Variant::nil()); } #[itest] diff --git a/itest/rust/src/node_test.rs b/itest/rust/src/node_test.rs index 5099ed647..76e4d54f8 100644 --- a/itest/rust/src/node_test.rs +++ b/itest/rust/src/node_test.rs @@ -6,7 +6,7 @@ use crate::{itest, TestContext}; use godot::builtin::{NodePath, Variant}; -use godot::engine::{global, node, Node, Node3D, NodeExt, PackedScene, SceneTree}; +use godot::engine::{global, Node, Node3D, NodeExt, PackedScene, SceneTree}; use godot::obj::Share; use std::str::FromStr; @@ -19,19 +19,11 @@ fn node_get_node() { let mut parent = Node3D::new_alloc(); parent.set_name("parent".into()); - parent.add_child( - child.share().upcast(), - false, - node::InternalMode::INTERNAL_MODE_DISABLED, - ); + parent.add_child(child.share().upcast()); let mut grandparent = Node::new_alloc(); grandparent.set_name("grandparent".into()); - grandparent.add_child( - parent.share().upcast(), - false, - node::InternalMode::INTERNAL_MODE_DISABLED, - ); + grandparent.add_child(parent.share().upcast()); // Directly on Gd let found = grandparent.get_node_as::(NodePath::from("parent/child")); @@ -72,11 +64,7 @@ fn node_scene_tree() { let mut parent = Node::new_alloc(); parent.set_name("parent".into()); - parent.add_child( - child.share(), - false, - node::InternalMode::INTERNAL_MODE_DISABLED, - ); + parent.add_child(child.share()); let mut scene = PackedScene::new(); let err = scene.pack(parent.share()); @@ -99,6 +87,6 @@ fn node_call_group(ctx: &TestContext) { let mut node = ctx.scene_tree.share(); let mut tree = node.get_tree().unwrap(); - node.add_to_group("group".into(), true); + node.add_to_group("group".into()); tree.call_group("group".into(), "set_name".into(), &[Variant::from("name")]); } diff --git a/itest/rust/src/object_test.rs b/itest/rust/src/object_test.rs index daa689719..37b1b4bfe 100644 --- a/itest/rust/src/object_test.rs +++ b/itest/rust/src/object_test.rs @@ -12,7 +12,6 @@ use godot::bind::{godot_api, GodotClass}; use godot::builtin::{ FromVariant, GodotString, StringName, ToVariant, Variant, VariantConversionError, Vector3, }; -use godot::engine::node::InternalMode; use godot::engine::{ file_access, Area2D, Camera3D, FileAccess, Node, Node3D, Object, RefCounted, RefCountedVirtual, }; @@ -658,9 +657,9 @@ fn object_get_scene_tree(ctx: &TestContext) { let node = Node3D::new_alloc(); let mut tree = ctx.scene_tree.share(); - tree.add_child(node.upcast(), false, InternalMode::INTERNAL_MODE_DISABLED); + tree.add_child(node.upcast()); - let count = tree.get_child_count(false); + let count = tree.get_child_count(); assert_eq!(count, 1); } // implicitly tested: node does not leak @@ -838,7 +837,7 @@ fn double_use_reference() { emitter .share() .upcast::() - .connect("do_use".into(), double_use.callable("use_1"), 0); + .connect("do_use".into(), double_use.callable("use_1")); let guard = double_use.bind(); diff --git a/itest/rust/src/signal_test.rs b/itest/rust/src/signal_test.rs index ed89de42c..36e6c5c5d 100644 --- a/itest/rust/src/signal_test.rs +++ b/itest/rust/src/signal_test.rs @@ -81,11 +81,9 @@ fn signals() { let signal_name = format!("signal_{i}_arg"); let receiver_name = format!("receive_{i}_arg"); - emitter.bind_mut().connect( - signal_name.clone().into(), - receiver.callable(receiver_name), - 0, - ); + emitter + .bind_mut() + .connect(signal_name.clone().into(), receiver.callable(receiver_name)); emitter.bind_mut().emit_signal(signal_name.into(), arg); diff --git a/itest/rust/src/virtual_methods_test.rs b/itest/rust/src/virtual_methods_test.rs index 636476597..c94be558a 100644 --- a/itest/rust/src/virtual_methods_test.rs +++ b/itest/rust/src/virtual_methods_test.rs @@ -14,7 +14,6 @@ use godot::builtin::{ PackedInt32Array, PackedStringArray, PackedVector2Array, PackedVector3Array, RealConv, StringName, ToVariant, Variant, VariantArray, Vector2, Vector3, }; -use godot::engine::node::InternalMode; use godot::engine::notify::NodeNotification; use godot::engine::resource_loader::CacheMode; use godot::engine::{ @@ -193,7 +192,7 @@ impl ResourceFormatLoaderVirtual for FormatLoaderTest { _path: GodotString, _original_path: GodotString, _use_sub_threads: bool, - _cache_mode: i64, + _cache_mode: i32, ) -> Variant { BoxMesh::new().to_variant() } @@ -241,11 +240,7 @@ fn test_ready(test_context: &TestContext) { // Add to scene tree let mut test_node = test_context.scene_tree.share(); - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); // _ready runs, increments implementation_value once. assert_eq!(obj.bind().implementation_value, 1); @@ -259,22 +254,14 @@ fn test_ready_multiple_fires(test_context: &TestContext) { let mut test_node = test_context.scene_tree.share(); // Add to scene tree - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); // _ready runs, increments implementation_value once. assert_eq!(obj.bind().implementation_value, 1); // Remove and re-add to scene tree test_node.remove_child(obj.share().upcast()); - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); // _ready does NOT run again, implementation_value should still be 1. assert_eq!(obj.bind().implementation_value, 1); @@ -288,22 +275,14 @@ fn test_ready_request_ready(test_context: &TestContext) { let mut test_node = test_context.scene_tree.share(); // Add to scene tree - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); // _ready runs, increments implementation_value once. assert_eq!(obj.bind().implementation_value, 1); // Remove and re-add to scene tree test_node.remove_child(obj.share().upcast()); - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); // _ready does NOT run again, implementation_value should still be 1. assert_eq!(obj.bind().implementation_value, 1); @@ -313,11 +292,7 @@ fn test_ready_request_ready(test_context: &TestContext) { // Remove and re-add to scene tree test_node.remove_child(obj.share().upcast()); - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); // _ready runs again since we asked it to; implementation_value should be 2. assert_eq!(obj.bind().implementation_value, 2); @@ -331,11 +306,7 @@ fn test_tree_enters_exits(test_context: &TestContext) { let mut test_node = test_context.scene_tree.share(); // Add to scene tree - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); assert_eq!(obj.bind().tree_enters, 1); assert_eq!(obj.bind().tree_exits, 0); @@ -343,11 +314,7 @@ fn test_tree_enters_exits(test_context: &TestContext) { test_node.remove_child(obj.share().upcast()); assert_eq!(obj.bind().tree_enters, 1); assert_eq!(obj.bind().tree_exits, 1); - test_node.add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_node.add_child(obj.share().upcast()); assert_eq!(obj.bind().tree_enters, 2); assert_eq!(obj.bind().tree_exits, 1); } @@ -389,18 +356,19 @@ fn test_virtual_method_with_return() { fn test_format_loader(_test_context: &TestContext) { let format_loader = Gd::::new_default(); let mut loader = ResourceLoader::singleton(); - loader.add_resource_format_loader(format_loader.share().upcast(), true); + loader + .add_resource_format_loader_ex(format_loader.share().upcast()) + .at_front(true) + .done(); let extensions = loader.get_recognized_extensions_for_type(FormatLoaderTest::resource_type()); let mut extensions_rust = format_loader.bind().get_recognized_extensions(); extensions_rust.push("tres".into()); assert_eq!(extensions, extensions_rust); let resource = loader - .load( - "path.extension".into(), - "".into(), - CacheMode::CACHE_MODE_IGNORE, - ) + .load_ex("path.extension".into()) + .cache_mode(CacheMode::CACHE_MODE_IGNORE) + .done() .unwrap(); assert!(resource.try_cast::().is_some()); @@ -413,26 +381,19 @@ fn test_input_event(test_context: &TestContext) { assert_eq!(obj.bind().event, None); let mut test_viewport = Window::new_alloc(); - test_context.scene_tree.share().add_child( - test_viewport.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_context + .scene_tree + .share() + .add_child(test_viewport.share().upcast()); - test_viewport.share().add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_viewport.share().add_child(obj.share().upcast()); let mut event = InputEventAction::new(); event.set_action("debug".into()); event.set_pressed(true); // We're running in headless mode, so Input.parse_input_event does not work - test_viewport - .share() - .push_input(event.share().upcast(), false); + test_viewport.share().push_input(event.share().upcast()); assert_eq!(obj.bind().event, Some(event.upcast::())); @@ -451,18 +412,13 @@ fn test_input_event_multiple(test_context: &TestContext) { } let mut test_viewport = Window::new_alloc(); - test_context.scene_tree.share().add_child( - test_viewport.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ); + test_context + .scene_tree + .share() + .add_child(test_viewport.share().upcast()); for obj in objs.iter() { - test_viewport.share().add_child( - obj.share().upcast(), - false, - InternalMode::INTERNAL_MODE_DISABLED, - ) + test_viewport.share().add_child(obj.share().upcast()) } let mut event = InputEventAction::new(); @@ -470,9 +426,7 @@ fn test_input_event_multiple(test_context: &TestContext) { event.set_pressed(true); // We're running in headless mode, so Input.parse_input_event does not work - test_viewport - .share() - .push_input(event.share().upcast(), false); + test_viewport.share().push_input(event.share().upcast()); for obj in objs.iter() { assert_eq!(obj.bind().event, Some(event.share().upcast::())); @@ -513,7 +467,7 @@ pub struct CollisionObject2DTest { #[godot_api] impl RigidBody2DVirtual for CollisionObject2DTest { - fn input_event(&mut self, viewport: Gd, _event: Gd, _shape_idx: i64) { + fn input_event(&mut self, viewport: Gd, _event: Gd, _shape_idx: i32) { self.input_event_called = true; self.viewport = Some(viewport); }