Skip to content

Commit 1625cac

Browse files
committed
Clean up callable_test
1 parent 7b8823d commit 1625cac

File tree

2 files changed

+98
-75
lines changed

2 files changed

+98
-75
lines changed

godot-core/src/builtin/callable.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,10 @@ impl Callable {
385385
self.as_inner().is_valid()
386386
}
387387

388+
/// Returns a copy of the callable, ignoring `args` user arguments.
389+
///
390+
/// Despite its name, this does **not** directly undo previous `bind()` calls. See
391+
/// [Godot docs](https://docs.godotengine.org/en/latest/classes/class_callable.html#class-callable-method-unbind) for up-to-date semantics.
388392
pub fn unbind(&self, args: usize) -> Callable {
389393
self.as_inner().unbind(args as i64)
390394
}
@@ -394,8 +398,12 @@ impl Callable {
394398
self.as_inner().get_argument_count() as usize
395399
}
396400

401+
/// Get number of bound arguments.
402+
///
403+
/// Note: for Godot < 4.4, this function returns incorrect results when applied on a callable that used `unbind()`.
404+
/// See [#98713](https://github.com/godotengine/godot/pull/98713) for details.
397405
pub fn get_bound_arguments_count(&self) -> usize {
398-
// To consistently return usize, we already fix the bug https://github.com/godotengine/godot/pull/98713 before Godot 4.4.
406+
// This does NOT fix the bug before Godot 4.4, just cap it at zero. unbind() will still erroneously decrease the bound arguments count.
399407
let alleged_count = self.as_inner().get_bound_arguments_count();
400408

401409
alleged_count.max(0) as usize

itest/rust/src/builtin_tests/containers/callable_test.rs

+89-74
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,18 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use godot::builtin::inner::InnerCallable;
8+
use crate::framework::itest;
99
use godot::builtin::{
10-
array, varray, Array, Callable, GString, NodePath, StringName, Variant, VariantArray,
10+
array, varray, Array, Callable, GString, NodePath, StringName, Variant, VariantArray, Vector2,
1111
};
1212
use godot::classes::{Node2D, Object, RefCounted};
13+
use godot::init::GdextBuild;
1314
use godot::meta::ToGodot;
1415
use godot::obj::{Gd, NewAlloc, NewGd};
1516
use godot::register::{godot_api, GodotClass};
1617
use std::hash::Hasher;
1718
use std::sync::atomic::{AtomicU32, Ordering};
1819

19-
use crate::framework::itest;
20-
2120
#[derive(GodotClass)]
2221
#[class(init, base=RefCounted)]
2322
struct CallableTestObj {
@@ -27,43 +26,38 @@ struct CallableTestObj {
2726
#[godot_api]
2827
impl CallableTestObj {
2928
#[func]
30-
fn foo(&mut self, a: i32) {
31-
self.value = a;
29+
fn stringify_int(&self, int: i32) -> GString {
30+
int.to_variant().stringify()
3231
}
3332

3433
#[func]
35-
fn bar(&self, b: i32) -> GString {
36-
b.to_variant().stringify()
34+
fn assign_int(&mut self, int: i32) {
35+
self.value = int;
3736
}
3837

39-
#[func]
40-
fn baz(&self, a: i32, b: GString, c: Array<NodePath>, d: Gd<RefCounted>) -> VariantArray {
38+
#[func] // static
39+
fn concat_array(a: i32, b: GString, c: Array<NodePath>, d: Gd<RefCounted>) -> VariantArray {
4140
varray![a, b, c, d]
4241
}
43-
44-
#[func]
45-
fn static_function(c: i32) -> GString {
46-
c.to_variant().stringify()
47-
}
4842
}
4943

5044
#[itest]
5145
fn callable_validity() {
5246
let obj = CallableTestObj::new_gd();
5347

54-
// non-null object, valid method
55-
assert!(obj.callable("foo").is_valid());
56-
assert!(!obj.callable("foo").is_null());
57-
assert!(!obj.callable("foo").is_custom());
58-
assert!(obj.callable("foo").object().is_some());
48+
// Non-null object, valid method.
49+
assert!(obj.callable("assign_int").is_valid());
50+
assert!(!obj.callable("assign_int").is_null());
51+
assert!(!obj.callable("assign_int").is_custom());
52+
assert!(obj.callable("assign_int").object().is_some());
5953

60-
// non-null object, invalid method
61-
assert!(!obj.callable("doesn't_exist").is_valid());
62-
assert!(!obj.callable("doesn't_exist").is_null());
63-
assert!(!obj.callable("doesn't_exist").is_custom());
64-
assert!(obj.callable("doesn't_exist").object().is_some());
54+
// Non-null object, invalid method.
55+
assert!(!obj.callable("doesnt_exist").is_valid());
56+
assert!(!obj.callable("doesnt_exist").is_null());
57+
assert!(!obj.callable("doesnt_exist").is_custom());
58+
assert!(obj.callable("doesnt_exist").object().is_some());
6559

66-
// null object
60+
// Null object.
6761
assert!(!Callable::invalid().is_valid());
6862
assert!(Callable::invalid().is_null());
6963
assert!(!Callable::invalid().is_custom());
@@ -75,19 +69,27 @@ fn callable_validity() {
7569
#[itest]
7670
fn callable_hash() {
7771
let obj = CallableTestObj::new_gd();
78-
assert_eq!(obj.callable("foo").hash(), obj.callable("foo").hash());
79-
assert_ne!(obj.callable("foo").hash(), obj.callable("bar").hash());
72+
assert_eq!(
73+
obj.callable("assign_int").hash(),
74+
obj.callable("assign_int").hash()
75+
);
76+
77+
// Not guaranteed, but unlikely.
78+
assert_ne!(
79+
obj.callable("assign_int").hash(),
80+
obj.callable("stringify_int").hash()
81+
);
8082
}
8183

8284
#[itest]
8385
fn callable_object_method() {
8486
let object = CallableTestObj::new_gd();
8587
let object_id = object.instance_id();
86-
let callable = object.callable("foo");
88+
let callable = object.callable("assign_int");
8789

8890
assert_eq!(callable.object(), Some(object.clone().upcast::<Object>()));
8991
assert_eq!(callable.object_id(), Some(object_id));
90-
assert_eq!(callable.method_name(), Some("foo".into()));
92+
assert_eq!(callable.method_name(), Some("assign_int".into()));
9193

9294
// Invalidating the object still returns the old ID, however not the object.
9395
drop(object);
@@ -97,7 +99,7 @@ fn callable_object_method() {
9799

98100
#[itest]
99101
fn callable_static() {
100-
let callable = Callable::from_local_static("CallableTestObj", "static_function");
102+
let callable = Callable::from_local_static("CallableTestObj", "concat_array");
101103

102104
// Test current behavior in <4.4 and >=4.4. Although our API explicitly leaves it unspecified, we then notice change in implementation.
103105
if cfg!(since_api = "4.4") {
@@ -107,13 +109,21 @@ fn callable_static() {
107109
} else {
108110
assert!(callable.object().is_some());
109111
assert!(callable.object_id().is_some());
110-
assert_eq!(callable.method_name(), Some("static_function".into()));
111-
assert_eq!(callable.to_string(), "GDScriptNativeClass::static_function");
112+
assert_eq!(callable.method_name(), Some("concat_array".into()));
113+
assert_eq!(callable.to_string(), "GDScriptNativeClass::concat_array");
112114
}
113115

114116
// Calling works consistently everywhere.
115-
let result = callable.callv(&varray![12345]);
116-
assert_eq!(result, "12345".to_variant());
117+
let result = callable.callv(&varray![
118+
10,
119+
"hello",
120+
&array![&NodePath::from("my/node/path")],
121+
RefCounted::new_gd()
122+
]);
123+
124+
let result = result.to::<VariantArray>();
125+
assert_eq!(result.len(), 4);
126+
assert_eq!(result.at(0), 10.to_variant());
117127

118128
#[cfg(since_api = "4.3")]
119129
assert_eq!(callable.get_argument_count(), 0); // Consistently doesn't work :)
@@ -122,7 +132,7 @@ fn callable_static() {
122132
#[itest]
123133
fn callable_callv() {
124134
let obj = CallableTestObj::new_gd();
125-
let callable = obj.callable("foo");
135+
let callable = obj.callable("assign_int");
126136

127137
assert_eq!(obj.bind().value, 0);
128138
callable.callv(&varray![10]);
@@ -143,20 +153,18 @@ fn callable_callv() {
143153
#[cfg(since_api = "4.2")]
144154
#[itest]
145155
fn callable_call() {
156+
// See callable_callv() for future improvements.
157+
146158
let obj = CallableTestObj::new_gd();
147-
let callable = obj.callable("foo");
159+
let callable = obj.callable("assign_int");
148160

149161
assert_eq!(obj.bind().value, 0);
150162
callable.call(&[10.to_variant()]);
151163
assert_eq!(obj.bind().value, 10);
152164

153-
// Too many arguments: this call fails, its logic is not applied.
154-
// In the future, panic should be propagated to caller.
155165
callable.call(&[20.to_variant(), 30.to_variant()]);
156166
assert_eq!(obj.bind().value, 10);
157167

158-
// TODO(bromeon): this causes a Rust panic, but since call() is routed to Godot, the panic is handled at the FFI boundary.
159-
// Can there be a way to notify the caller about failed calls like that?
160168
assert_eq!(callable.call(&["string".to_variant()]), Variant::nil());
161169

162170
assert_eq!(
@@ -168,40 +176,43 @@ fn callable_call() {
168176
#[itest]
169177
fn callable_call_return() {
170178
let obj = CallableTestObj::new_gd();
171-
let callable = obj.callable("bar");
179+
let callable = obj.callable("stringify_int");
172180

173181
assert_eq!(
174182
callable.callv(&varray![10]),
175183
10.to_variant().stringify().to_variant()
176184
);
177-
// Errors in Godot, but should not crash.
185+
186+
// Causes error in Godot, but should not crash.
178187
assert_eq!(callable.callv(&varray!["string"]), Variant::nil());
179188
}
180189

190+
#[cfg(since_api = "4.2")]
181191
#[itest]
182192
fn callable_call_engine() {
183193
let obj = Node2D::new_alloc();
184194
let cb = Callable::from_object_method(&obj, "set_position");
185-
let inner: InnerCallable = cb.as_inner();
186195

187-
assert!(!inner.is_null());
188-
assert_eq!(inner.get_object_id(), obj.instance_id().to_i64());
189-
assert_eq!(inner.get_method(), StringName::from("set_position"));
196+
assert!(!cb.is_null());
197+
assert_eq!(cb.object_id(), Some(obj.instance_id()));
198+
assert_eq!(cb.method_name(), Some(StringName::from("set_position")));
190199

191-
// TODO once varargs is available
192-
// let pos = Vector2::new(5.0, 7.0);
193-
// inner.call(&[pos.to_variant()]);
194-
// assert_eq!(obj.get_position(), pos);
195-
//
196-
// inner.bindv(array);
200+
let pos = Vector2::new(5.0, 7.0);
201+
cb.call(&[pos.to_variant()]);
202+
assert_eq!(obj.get_position(), pos);
203+
204+
let pos = Vector2::new(1.0, 23.0);
205+
let bound = cb.bind(&[pos.to_variant()]);
206+
bound.call(&[]);
207+
assert_eq!(obj.get_position(), pos);
197208

198209
obj.free();
199210
}
200211

201212
#[itest]
202213
fn callable_bindv() {
203214
let obj = CallableTestObj::new_gd();
204-
let callable = obj.callable("bar");
215+
let callable = obj.callable("stringify_int");
205216
let callable_bound = callable.bindv(&varray![10]);
206217

207218
assert_eq!(
@@ -214,7 +225,7 @@ fn callable_bindv() {
214225
#[itest]
215226
fn callable_bind() {
216227
let obj = CallableTestObj::new_gd();
217-
let callable = obj.callable("bar");
228+
let callable = obj.callable("stringify_int");
218229
let callable_bound = callable.bind(&[10.to_variant()]);
219230

220231
assert_eq!(
@@ -227,7 +238,7 @@ fn callable_bind() {
227238
#[itest]
228239
fn callable_unbind() {
229240
let obj = CallableTestObj::new_gd();
230-
let callable = obj.callable("bar");
241+
let callable = obj.callable("stringify_int");
231242
let callable_unbound = callable.unbind(3);
232243

233244
assert_eq!(
@@ -243,34 +254,39 @@ fn callable_unbind() {
243254

244255
#[cfg(since_api = "4.3")]
245256
#[itest]
246-
fn callable_arg_len() {
257+
fn callable_get_argument_count() {
247258
let obj = CallableTestObj::new_gd();
248259

249-
assert_eq!(obj.callable("foo").get_argument_count(), 1);
250-
assert_eq!(obj.callable("bar").get_argument_count(), 1);
251-
assert_eq!(obj.callable("baz").get_argument_count(), 4);
252-
assert_eq!(obj.callable("foo").unbind(10).get_argument_count(), 11);
260+
let assign_int = obj.callable("assign_int");
261+
assert_eq!(assign_int.get_argument_count(), 1);
262+
assert_eq!(assign_int.unbind(10).get_argument_count(), 11);
263+
264+
assert_eq!(obj.callable("stringify_int").get_argument_count(), 1);
265+
266+
let concat_array = obj.callable("concat_array");
267+
assert_eq!(concat_array.get_argument_count(), 4);
253268
assert_eq!(
254-
obj.callable("baz")
269+
concat_array
255270
.bind(&[10.to_variant(), "hello".to_variant()])
256271
.get_argument_count(),
257272
2
258273
);
259274
}
260275

261276
#[itest]
262-
fn callable_bound_args_len() {
263-
// Note: bug regarding get_bound_arguments_count() returning negative numbers has been fixed in godot-rust also for older versions.
264-
277+
fn callable_get_bound_arguments_count() {
265278
let obj = CallableTestObj::new_gd();
266-
let original = obj.callable("foo");
279+
let original = obj.callable("assign_int");
267280

268281
assert_eq!(original.get_bound_arguments_count(), 0);
269282
assert_eq!(original.unbind(28).get_bound_arguments_count(), 0);
270283

271284
let with_1_arg = original.bindv(&varray![10]);
272285
assert_eq!(with_1_arg.get_bound_arguments_count(), 1);
273-
assert_eq!(with_1_arg.unbind(5).get_bound_arguments_count(), 1);
286+
287+
// Note: bug regarding get_bound_arguments_count() before 4.4; godot-rust caps at 0.
288+
let expected = if GdextBuild::since_api("4.4") { 1 } else { 0 };
289+
assert_eq!(with_1_arg.unbind(5).get_bound_arguments_count(), expected);
274290
}
275291

276292
#[itest]
@@ -288,10 +304,7 @@ fn callable_get_bound_arguments() {
288304
assert_eq!(callable_bound.get_bound_arguments(), varray![a, b, c, d]);
289305
}
290306

291-
// TODO: Add tests for `Callable::rpc` and `Callable::rpc_id`.
292-
293-
// Testing https://github.com/godot-rust/gdext/issues/410
294-
307+
// Regression test for https://github.com/godot-rust/gdext/issues/410.
295308
#[derive(GodotClass)]
296309
#[class(init, base = Node)]
297310
pub struct CallableRefcountTest {}
@@ -407,7 +420,8 @@ pub mod custom_callable {
407420
fn callable_custom_with_err() {
408421
let callable_with_err =
409422
Callable::from_local_fn("on_error_doesnt_crash", |_args: &[&Variant]| Err(()));
410-
// Errors in Godot, but should not crash.
423+
424+
// Causes error in Godot, but should not crash.
411425
assert_eq!(callable_with_err.callv(&varray![]), Variant::nil());
412426
}
413427

@@ -538,6 +552,9 @@ pub mod custom_callable {
538552
assert_eq!(1, received.load(Ordering::SeqCst));
539553
}
540554

555+
// ------------------------------------------------------------------------------------------------------------------------------------------
556+
// Helper structs and functions for custom callables
557+
541558
struct Adder {
542559
sum: i32,
543560

@@ -644,5 +661,3 @@ pub mod custom_callable {
644661
}
645662
}
646663
}
647-
648-
// ----------------------------------------------------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)