Skip to content

Commit 92b1e23

Browse files
committed
Implement Variant::call()
1 parent 0405dd9 commit 92b1e23

File tree

4 files changed

+116
-8
lines changed

4 files changed

+116
-8
lines changed

godot-codegen/src/class_generator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ fn make_return(
851851
let variant = Variant::#from_sys_init_method(|return_ptr| {
852852
let mut __err = sys::default_call_error();
853853
#varcall_invocation
854-
assert_eq!(__err.error, sys::GDEXTENSION_CALL_OK);
854+
sys::panic_on_call_error(&__err);
855855
});
856856
#return_expr
857857
}
@@ -863,7 +863,7 @@ fn make_return(
863863
let mut __err = sys::default_call_error();
864864
let return_ptr = std::ptr::null_mut();
865865
#varcall_invocation
866-
assert_eq!(__err.error, sys::GDEXTENSION_CALL_OK);
866+
sys::panic_on_call_error(&__err);
867867
}
868868
}
869869
(None, Some(RustTy::EngineClass { tokens, .. })) => {

godot-core/src/builtin/variant/mod.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use crate::builtin::GodotString;
7+
use crate::builtin::{GodotString, StringName};
88
use godot_ffi as sys;
99
use godot_ffi::GodotFfi;
1010
use std::{fmt, ptr};
@@ -91,12 +91,46 @@ impl Variant {
9191
}
9292
}
9393

94-
// TODO test
95-
#[allow(unused_mut)]
94+
/// ⚠️ Calls the specified `method` with the given `args`.
95+
///
96+
/// Supports `Object` as well as built-ins with methods (e.g. `Array`, `Vector3`, `GodotString`, etc).
97+
///
98+
/// # Panics
99+
/// * If `self` is not a variant type which supports method calls.
100+
/// * If the method does not exist or the signature is not compatible with the passed arguments.
101+
/// * If the call causes an error.
102+
#[inline]
103+
pub fn call(&self, method: impl Into<StringName>, args: &[Variant]) -> Variant {
104+
self.call_inner(method.into(), args)
105+
}
106+
107+
fn call_inner(&self, method: StringName, args: &[Variant]) -> Variant {
108+
let args_sys: Vec<_> = args.iter().map(|v| v.var_sys_const()).collect();
109+
let mut error = sys::default_call_error();
110+
111+
#[allow(unused_mut)]
112+
let mut result = Variant::nil();
113+
114+
unsafe {
115+
interface_fn!(variant_call)(
116+
self.var_sys(),
117+
method.string_sys(),
118+
args_sys.as_ptr(),
119+
args_sys.len() as i64,
120+
result.var_sys(),
121+
ptr::addr_of_mut!(error),
122+
)
123+
};
124+
125+
sys::panic_on_call_error(&error);
126+
result
127+
}
128+
96129
pub fn evaluate(&self, rhs: &Variant, op: VariantOperator) -> Option<Variant> {
97130
let op_sys = op.sys();
98131
let mut is_valid = false as u8;
99132

133+
#[allow(unused_mut)]
100134
let mut result = Variant::nil();
101135
unsafe {
102136
interface_fn!(variant_evaluate)(

godot-ffi/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ unsafe fn unwrap_ref_unchecked_mut<T>(opt: &mut Option<T>) -> &mut T {
140140
}
141141

142142
#[doc(hidden)]
143+
#[inline]
143144
pub fn default_call_error() -> GDExtensionCallError {
144145
GDExtensionCallError {
145146
error: GDEXTENSION_CALL_OK,
@@ -148,6 +149,32 @@ pub fn default_call_error() -> GDExtensionCallError {
148149
}
149150
}
150151

152+
#[doc(hidden)]
153+
#[inline]
154+
pub fn panic_on_call_error(err: &GDExtensionCallError) {
155+
let actual = err.error;
156+
157+
assert_eq!(
158+
actual,
159+
GDEXTENSION_CALL_OK,
160+
"encountered Godot error code {}",
161+
call_error_to_string(actual)
162+
);
163+
}
164+
165+
fn call_error_to_string(err: GDExtensionCallErrorType) -> &'static str {
166+
match err {
167+
GDEXTENSION_CALL_OK => "OK",
168+
GDEXTENSION_CALL_ERROR_INVALID_METHOD => "ERROR_INVALID_METHOD",
169+
GDEXTENSION_CALL_ERROR_INVALID_ARGUMENT => "ERROR_INVALID_ARGUMENT",
170+
GDEXTENSION_CALL_ERROR_TOO_MANY_ARGUMENTS => "ERROR_TOO_MANY_ARGUMENTS",
171+
GDEXTENSION_CALL_ERROR_TOO_FEW_ARGUMENTS => "ERROR_TOO_FEW_ARGUMENTS",
172+
GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL => "ERROR_INSTANCE_IS_NULL",
173+
GDEXTENSION_CALL_ERROR_METHOD_NOT_CONST => "ERROR_METHOD_NOT_CONST",
174+
_ => "(unknown)",
175+
}
176+
}
177+
151178
#[macro_export]
152179
#[doc(hidden)]
153180
macro_rules! builtin_fn {

itest/rust/src/variant_test.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
55
*/
66

7-
use crate::itest;
7+
use crate::{expect_panic, itest};
88
use godot::builtin::{
99
FromVariant, GodotString, NodePath, StringName, ToVariant, Variant, Vector2, Vector3,
1010
};
11-
use godot::engine::Node3D;
11+
use godot::engine::Node2D;
1212
use godot::obj::InstanceId;
1313
use godot::prelude::{Array, Dictionary, VariantConversionError};
1414
use godot::sys::{GodotFfi, VariantOperator, VariantType};
@@ -96,6 +96,53 @@ fn variant_equal() {
9696
equal(gstr("String"), 33, false);
9797
}
9898

99+
#[itest]
100+
fn variant_call() {
101+
use godot::obj::Share;
102+
let node2d = Node2D::new_alloc();
103+
let variant = Variant::from(node2d.share());
104+
105+
// Object
106+
let position = Vector2::new(4.0, 5.0);
107+
let result = variant.call("set_position", &[position.to_variant()]);
108+
assert!(result.is_nil());
109+
110+
let result = variant.call("get_position", &[]);
111+
assert_eq!(result.try_to::<Vector2>(), Ok(position));
112+
113+
let result = variant.call("to_string", &[]);
114+
assert_eq!(result.get_type(), VariantType::String);
115+
116+
// Array
117+
let array = godot::builtin::varray![1, "hello", false];
118+
let result = array.to_variant().call("size", &[]);
119+
assert_eq!(result, 3.to_variant());
120+
121+
// String
122+
let string = GodotString::from("move_local_x");
123+
let result = string.to_variant().call("capitalize", &[]);
124+
assert_eq!(result, "Move Local X".to_variant());
125+
126+
// Vector2
127+
let vector = Vector2::new(5.0, 3.0);
128+
let vector_rhs = Vector2::new(1.0, -1.0);
129+
let result = vector.to_variant().call("dot", &[vector_rhs.to_variant()]);
130+
assert_eq!(result, 2.0.to_variant());
131+
132+
// Error cases
133+
expect_panic("Variant::call on non-existent method", || {
134+
variant.call("gut_position", &[]);
135+
});
136+
expect_panic("Variant::call with bad signature", || {
137+
variant.call("set_position", &[]);
138+
});
139+
expect_panic("Variant::call with non-object variant (int)", || {
140+
Variant::from(77).call("to_string", &[]);
141+
});
142+
143+
node2d.free();
144+
}
145+
99146
#[rustfmt::skip]
100147
#[itest]
101148
fn variant_evaluate() {
@@ -190,7 +237,7 @@ fn variant_sys_conversion() {
190237
fn variant_null_object_is_nil() {
191238
use godot::sys;
192239

193-
let mut node = Node3D::new_alloc();
240+
let mut node = Node2D::new_alloc();
194241
let node_path = NodePath::from("res://NonExisting.tscn");
195242

196243
// Simulates an object that is returned but null

0 commit comments

Comments
 (0)