diff --git a/godot-codegen/src/special_cases/special_cases.rs b/godot-codegen/src/special_cases/special_cases.rs index 5cb65482f..42b6e6971 100644 --- a/godot-codegen/src/special_cases/special_cases.rs +++ b/godot-codegen/src/special_cases/special_cases.rs @@ -62,6 +62,16 @@ pub fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ct | ("VisualShaderNodeComment", "set_description") | ("VisualShaderNodeComment", "get_description") => true, + + // Workaround for methods unexposed in Release mode, see https://github.com/godotengine/godot/pull/100317 + // and https://github.com/godotengine/godot/pull/100328. + #[cfg(not(debug_assertions))] + | ("CollisionShape2D", "set_debug_color") + | ("CollisionShape2D", "get_debug_color") + | ("CollisionShape3D", "set_debug_color") + | ("CollisionShape3D", "get_debug_color") + | ("CollisionShape3D", "set_debug_fill_enabled") + | ("CollisionShape3D", "get_debug_fill_enabled") => true, // Thread APIs #[cfg(not(feature = "experimental-threads"))] @@ -241,6 +251,14 @@ pub fn is_builtin_method_exposed(builtin_ty: &TyName, godot_method_name: &str) - // NodePath + // Callable + | ("Callable", "call") + | ("Callable", "call_deferred") + | ("Callable", "bind") + | ("Callable", "get_bound_arguments") + | ("Callable", "rpc") + | ("Callable", "rpc_id") + // (add more builtin types below) => true, _ => false diff --git a/godot-core/src/builtin/callable.rs b/godot-core/src/builtin/callable.rs index b82f6bcf1..580a78a80 100644 --- a/godot-core/src/builtin/callable.rs +++ b/godot-core/src/builtin/callable.rs @@ -335,6 +335,21 @@ impl Callable { self.as_inner().is_valid() } + pub fn unbind(&self, args: usize) -> Callable { + self.as_inner().unbind(args as i64) + } + + #[cfg(since_api = "4.3")] + #[doc(alias = "get_argument_count")] + pub fn arg_len(&self) -> usize { + self.as_inner().get_argument_count() as usize + } + + #[doc(alias = "get_bound_arguments_count")] + pub fn bound_args_len(&self) -> i64 { + self.as_inner().get_bound_arguments_count() + } + #[doc(hidden)] pub fn as_inner(&self) -> inner::InnerCallable { inner::InnerCallable::from_outer(self) diff --git a/godot-core/src/classes/class_runtime.rs b/godot-core/src/classes/class_runtime.rs index ee7bd5450..a6f72a1ea 100644 --- a/godot-core/src/classes/class_runtime.rs +++ b/godot-core/src/classes/class_runtime.rs @@ -8,8 +8,11 @@ //! Runtime checks and inspection of Godot classes. use crate::builtin::{GString, StringName}; +#[cfg(debug_assertions)] use crate::classes::{ClassDb, Object}; -use crate::meta::{CallContext, ClassName}; +use crate::meta::CallContext; +#[cfg(debug_assertions)] +use crate::meta::ClassName; use crate::obj::{bounds, Bounds, Gd, GodotClass, InstanceId}; use crate::sys; diff --git a/godot-core/src/meta/error/convert_error.rs b/godot-core/src/meta/error/convert_error.rs index 24e8eede6..da7198564 100644 --- a/godot-core/src/meta/error/convert_error.rs +++ b/godot-core/src/meta/error/convert_error.rs @@ -176,6 +176,7 @@ pub(crate) enum FromGodotError { }, /// Special case of `BadArrayType` where a custom int type such as `i8` cannot hold a dynamic `i64` value. + #[cfg(debug_assertions)] BadArrayTypeInt { expected: ArrayTypeInfo, value: i64 }, /// InvalidEnum is also used by bitfields. @@ -236,6 +237,7 @@ impl fmt::Display for FromGodotError { "expected array of class {exp_class}, got array of class {act_class}" ) } + #[cfg(debug_assertions)] Self::BadArrayTypeInt { expected, value } => { write!( f, diff --git a/godot-core/src/private.rs b/godot-core/src/private.rs index dea6ad9af..653e89d64 100644 --- a/godot-core/src/private.rs +++ b/godot-core/src/private.rs @@ -21,7 +21,9 @@ use crate::global::godot_error; use crate::meta::error::CallError; use crate::meta::CallContext; use crate::sys; -use std::sync::{atomic, Arc, Mutex}; +use std::sync::atomic; +#[cfg(debug_assertions)] +use std::sync::{Arc, Mutex}; use sys::Global; // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -412,6 +414,7 @@ where #[cfg(not(debug_assertions))] { + let _ = error_context; // Unused warning. let msg = extract_panic_message(err); let msg = format_panic_message(msg); diff --git a/itest/rust/src/builtin_tests/containers/callable_test.rs b/itest/rust/src/builtin_tests/containers/callable_test.rs index fda35cb68..0ba912d0a 100644 --- a/itest/rust/src/builtin_tests/containers/callable_test.rs +++ b/itest/rust/src/builtin_tests/containers/callable_test.rs @@ -6,10 +6,13 @@ */ use godot::builtin::inner::InnerCallable; -use godot::builtin::{varray, Callable, GString, StringName, Variant}; -use godot::classes::{Node2D, Object}; +use godot::builtin::{ + array, varray, Array, Callable, GString, NodePath, StringName, Variant, VariantArray, +}; +use godot::classes::{Node2D, Object, RefCounted}; +use godot::init::GdextBuild; use godot::meta::ToGodot; -use godot::obj::{NewAlloc, NewGd}; +use godot::obj::{Gd, NewAlloc, NewGd}; use godot::register::{godot_api, GodotClass}; use std::hash::Hasher; use std::sync::atomic::{AtomicU32, Ordering}; @@ -33,6 +36,11 @@ impl CallableTestObj { fn bar(&self, b: i32) -> GString { b.to_variant().stringify() } + + #[func] + fn baz(&self, a: i32, b: GString, c: Array, d: Gd) -> VariantArray { + varray![a, b, c, d] + } } #[itest] @@ -86,7 +94,7 @@ fn callable_object_method() { } #[itest] -fn callable_call() { +fn callable_callv() { let obj = CallableTestObj::new_gd(); let callable = obj.callable("foo"); @@ -106,6 +114,31 @@ fn callable_call() { assert_eq!(Callable::invalid().callv(&varray![1, 2, 3]), Variant::nil()); } +#[cfg(since_api = "4.2")] +#[itest] +fn callable_call() { + let obj = CallableTestObj::new_gd(); + let callable = obj.callable("foo"); + + assert_eq!(obj.bind().value, 0); + callable.call(&[10.to_variant()]); + assert_eq!(obj.bind().value, 10); + + // Too many arguments: this call fails, its logic is not applied. + // In the future, panic should be propagated to caller. + callable.call(&[20.to_variant(), 30.to_variant()]); + assert_eq!(obj.bind().value, 10); + + // TODO(bromeon): this causes a Rust panic, but since call() is routed to Godot, the panic is handled at the FFI boundary. + // Can there be a way to notify the caller about failed calls like that? + assert_eq!(callable.call(&["string".to_variant()]), Variant::nil()); + + assert_eq!( + Callable::invalid().call(&[1.to_variant(), 2.to_variant(), 3.to_variant()]), + Variant::nil() + ); +} + #[itest] fn callable_call_return() { let obj = CallableTestObj::new_gd(); @@ -151,24 +184,92 @@ fn callable_bindv() { ); } -// This is also testing that using works at all. -#[itest] #[cfg(since_api = "4.2")] -fn callable_varargs() { - // TODO: Replace with proper apis instead of using `InnerCallable`. - use godot::builtin::inner; +#[itest] +fn callable_bind() { let obj = CallableTestObj::new_gd(); let callable = obj.callable("bar"); - let inner_callable = inner::InnerCallable::from_outer(&callable); - let callable_bound = inner_callable.bind(&[10.to_variant()]); - let inner_callable_bound = inner::InnerCallable::from_outer(&callable_bound); + let callable_bound = callable.bind(&[10.to_variant()]); assert_eq!( - inner_callable_bound.call(&[]), + callable_bound.call(&[]), 10.to_variant().stringify().to_variant() ); } +#[cfg(since_api = "4.2")] +#[itest] +fn callable_unbind() { + let obj = CallableTestObj::new_gd(); + let callable = obj.callable("bar"); + let callable_unbound = callable.unbind(3); + + assert_eq!( + callable_unbound.call(&[ + 121.to_variant(), + 20.to_variant(), + 30.to_variant(), + 40.to_variant() + ]), + 121.to_variant().stringify().to_variant() + ); +} + +#[cfg(since_api = "4.3")] +#[itest] +fn callable_arg_len() { + let obj = CallableTestObj::new_gd(); + + assert_eq!(obj.callable("foo").arg_len(), 1); + assert_eq!(obj.callable("bar").arg_len(), 1); + assert_eq!(obj.callable("baz").arg_len(), 4); + assert_eq!(obj.callable("foo").unbind(10).arg_len(), 11); + assert_eq!( + obj.callable("baz") + .bind(&[10.to_variant(), "hello".to_variant()]) + .arg_len(), + 2 + ); +} + +#[itest] +fn callable_bound_args_len() { + let obj = CallableTestObj::new_gd(); + + assert_eq!(obj.callable("foo").bound_args_len(), 0); + assert_eq!(obj.callable("foo").bindv(&varray![10]).bound_args_len(), 1); + #[cfg(since_api = "4.3")] + assert_eq!( + obj.callable("foo").unbind(28).bound_args_len(), + if GdextBuild::since_api("4.4") { 0 } else { -28 } + ); + #[cfg(since_api = "4.3")] + assert_eq!( + obj.callable("foo") + .bindv(&varray![10]) + .unbind(5) + .bound_args_len(), + if GdextBuild::since_api("4.4") { 1 } else { -4 } + ); +} + +#[itest] +fn callable_get_bound_arguments() { + let obj = CallableTestObj::new_gd(); + + let a: i32 = 10; + let b: &str = "hello!"; + let c: Array = array!["my/node/path"]; + let d: Gd = RefCounted::new_gd(); + + let callable = obj.callable("baz"); + let callable_bound = callable.bindv(&varray![a, b, c, d]); + + assert_eq!(callable_bound.get_bound_arguments(), varray![a, b, c, d]); +} + +// TODO: Add tests for `Callable::rpc` and `Callable::rpc_id`. + // Testing https://github.com/godot-rust/gdext/issues/410 #[derive(GodotClass)]