Skip to content

Commit 4c9f7bc

Browse files
authored
Merge pull request #606 from godot-rust/feature/virtual-func
`#[func(virtual)]`: override virtual Rust functions in GDScript
2 parents ddf0403 + 8734f1a commit 4c9f7bc

File tree

22 files changed

+807
-207
lines changed

22 files changed

+807
-207
lines changed

godot-bindings/src/godot_exe.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ pub(crate) fn read_godot_version(godot_bin: &Path) -> GodotVersion {
112112
let output = execute(cmd, "read Godot version");
113113
let stdout = std::str::from_utf8(&output.stdout).expect("convert Godot version to UTF-8");
114114

115-
match parse_godot_version(&stdout) {
115+
match parse_godot_version(stdout) {
116116
Ok(parsed) => {
117117
assert_eq!(
118118
parsed.major,

godot-codegen/src/special_cases/codegen_special_cases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ const SELECTED_CLASSES: &[&str] = &[
138138
"EditorPlugin",
139139
"Engine",
140140
"FileAccess",
141+
"GDScript",
141142
"HTTPRequest",
142143
"Image",
143144
"ImageTextureLayered",

godot-core/src/builtin/meta/registration/method.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,16 @@ impl ClassMethodInfo {
136136
default_argument_count: self.default_argument_count(),
137137
default_arguments: default_arguments_sys.as_mut_ptr(),
138138
};
139-
// SAFETY:
140-
// The lifetime of the data we use here is at least as long as this function's scope. So we can
139+
140+
if self.method_flags.is_set(MethodFlags::VIRTUAL) {
141+
self.register_virtual_class_method(method_info_sys, return_value_sys);
142+
} else {
143+
self.register_nonvirtual_class_method(method_info_sys);
144+
}
145+
}
146+
147+
fn register_nonvirtual_class_method(&self, method_info_sys: sys::GDExtensionClassMethodInfo) {
148+
// SAFETY: The lifetime of the data we use here is at least as long as this function's scope. So we can
141149
// safely call this function without issue.
142150
//
143151
// Null pointers will only be passed along if we indicate to Godot that they are unused.
@@ -150,6 +158,42 @@ impl ClassMethodInfo {
150158
}
151159
}
152160

161+
#[cfg(since_api = "4.3")]
162+
fn register_virtual_class_method(
163+
&self,
164+
normal_method_info: sys::GDExtensionClassMethodInfo,
165+
return_value_sys: sys::GDExtensionPropertyInfo, // passed separately because value, not pointer.
166+
) {
167+
// Copy everything possible from regular method info.
168+
let method_info_sys = sys::GDExtensionClassVirtualMethodInfo {
169+
name: normal_method_info.name,
170+
method_flags: normal_method_info.method_flags,
171+
return_value: return_value_sys,
172+
return_value_metadata: normal_method_info.return_value_metadata,
173+
argument_count: normal_method_info.argument_count,
174+
arguments: normal_method_info.arguments_info,
175+
arguments_metadata: normal_method_info.arguments_metadata,
176+
};
177+
178+
// SAFETY: Godot only needs arguments to be alive during the method call.
179+
unsafe {
180+
interface_fn!(classdb_register_extension_class_virtual_method)(
181+
sys::get_library(),
182+
self.class_name.string_sys(),
183+
std::ptr::addr_of!(method_info_sys),
184+
)
185+
}
186+
}
187+
188+
// Polyfill doing nothing.
189+
#[cfg(before_api = "4.3")]
190+
fn register_virtual_class_method(
191+
&self,
192+
_normal_method_info: sys::GDExtensionClassMethodInfo,
193+
_return_value_sys: sys::GDExtensionPropertyInfo,
194+
) {
195+
}
196+
153197
fn argument_count(&self) -> u32 {
154198
self.arguments
155199
.len()

godot-core/src/builtin/meta/signature.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ pub trait VarcallSignatureTuple: PtrcallSignatureTuple {
4949
varargs: &[Variant],
5050
) -> Self::Ret;
5151

52+
/// Outbound virtual call to a method overridden by a script attached to the object.
53+
///
54+
/// Returns `None` if the script does not override the method.
55+
#[cfg(since_api = "4.3")]
56+
unsafe fn out_script_virtual_call(
57+
// Separate parameters to reduce tokens in macro-generated API.
58+
class_name: &'static str,
59+
method_name: &'static str,
60+
method_sname_ptr: sys::GDExtensionConstStringNamePtr,
61+
object_ptr: sys::GDExtensionObjectPtr,
62+
args: Self::Params,
63+
) -> Self::Ret;
64+
5265
unsafe fn out_utility_ptrcall_varargs(
5366
utility_fn: UtilityFunctionBind,
5467
function_name: &'static str,
@@ -229,6 +242,45 @@ macro_rules! impl_varcall_signature_for_tuple {
229242
result.unwrap_or_else(|err| return_error::<Self::Ret>(&call_ctx, err))
230243
}
231244

245+
#[cfg(since_api = "4.3")]
246+
unsafe fn out_script_virtual_call(
247+
// Separate parameters to reduce tokens in macro-generated API.
248+
class_name: &'static str,
249+
method_name: &'static str,
250+
method_sname_ptr: sys::GDExtensionConstStringNamePtr,
251+
object_ptr: sys::GDExtensionObjectPtr,
252+
($($pn,)*): Self::Params,
253+
) -> Self::Ret {
254+
// Assumes that caller has previously checked existence of a virtual method.
255+
256+
let call_ctx = CallContext::outbound(class_name, method_name);
257+
//$crate::out!("out_script_virtual_call: {call_ctx}");
258+
259+
let object_call_script_method = sys::interface_fn!(object_call_script_method);
260+
let explicit_args = [
261+
$(
262+
GodotFfiVariant::ffi_to_variant(&into_ffi($pn)),
263+
)*
264+
];
265+
266+
let variant_ptrs = explicit_args.iter().map(Variant::var_sys_const).collect::<Vec<_>>();
267+
268+
let variant = Variant::from_var_sys_init(|return_ptr| {
269+
let mut err = sys::default_call_error();
270+
object_call_script_method(
271+
object_ptr,
272+
method_sname_ptr,
273+
variant_ptrs.as_ptr(),
274+
variant_ptrs.len() as i64,
275+
return_ptr,
276+
std::ptr::addr_of_mut!(err),
277+
);
278+
});
279+
280+
let result = <Self::Ret as FromGodot>::try_from_variant(&variant);
281+
result.unwrap_or_else(|err| return_error::<Self::Ret>(&call_ctx, err))
282+
}
283+
232284
// Note: this is doing a ptrcall, but uses variant conversions for it
233285
#[inline]
234286
unsafe fn out_utility_ptrcall_varargs(
@@ -257,6 +309,7 @@ macro_rules! impl_varcall_signature_for_tuple {
257309
result.unwrap_or_else(|err| return_error::<Self::Ret>(&call_ctx, err))
258310
}
259311

312+
260313
#[inline]
261314
fn format_args(args: &Self::Params) -> String {
262315
let mut string = String::new();

godot-core/src/builtin/string/string_name.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ impl StringName {
120120
fn string_sys = sys;
121121
}
122122

123+
#[doc(hidden)]
124+
pub fn string_sys_const(&self) -> sys::GDExtensionConstStringNamePtr {
125+
sys::to_const_ptr(self.string_sys())
126+
}
127+
123128
#[doc(hidden)]
124129
pub fn as_inner(&self) -> inner::InnerStringName {
125130
inner::InnerStringName::from_outer(self)

godot-core/src/obj/base.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ impl<T: GodotClass> Base<T> {
7979
pub fn as_gd(&self) -> &Gd<T> {
8080
&self.obj
8181
}
82+
83+
// Currently only used in outbound virtual calls (for scripts).
84+
#[doc(hidden)]
85+
pub fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
86+
self.obj.obj_sys()
87+
}
8288
}
8389

8490
impl<T: GodotClass> Debug for Base<T> {

godot-core/src/obj/gd.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,8 @@ impl<T: GodotClass> Gd<T> {
423423
Self::from_obj_sys_weak_or_none(ptr).unwrap()
424424
}
425425

426-
pub(crate) fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
426+
#[doc(hidden)]
427+
pub fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
427428
self.raw.obj_sys()
428429
}
429430

godot-core/src/obj/traits.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ pub trait EngineBitfield: Copy {
183183
Self::try_from_ord(ord)
184184
.unwrap_or_else(|| panic!("ordinal {ord} does not map to any valid bit flag"))
185185
}
186+
187+
// TODO consolidate API: named methods vs. | & ! etc.
188+
fn is_set(self, flag: Self) -> bool {
189+
self.ord() & flag.ord() != 0
190+
}
186191
}
187192

188193
/// Trait for enums that can be used as indices in arrays.

godot-core/src/private.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ where
130130
}
131131
}
132132

133+
#[cfg(since_api = "4.3")]
134+
pub unsafe fn has_virtual_script_method(
135+
object_ptr: sys::GDExtensionObjectPtr,
136+
method_sname: sys::GDExtensionConstStringNamePtr,
137+
) -> bool {
138+
sys::interface_fn!(object_has_script_method)(sys::to_const_ptr(object_ptr), method_sname) != 0
139+
}
140+
133141
pub fn flush_stdout() {
134142
use std::io::Write;
135143
std::io::stdout().flush().expect("flush stdout");

godot-ffi/src/extras.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ impl_as_uninit!(GDExtensionTypePtr, GDExtensionUninitializedTypePtr);
5858
// ----------------------------------------------------------------------------------------------------------------------------------------------
5959
// Helper functions
6060

61+
/// Differentiate from `sys::GDEXTENSION_CALL_ERROR_*` codes.
62+
pub const GODOT_RUST_CALL_ERROR: GDExtensionCallErrorType = 40;
63+
6164
#[doc(hidden)]
6265
#[inline]
6366
pub fn default_call_error() -> GDExtensionCallError {
@@ -105,6 +108,7 @@ pub fn panic_call_error(
105108
}
106109
GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL => "instance is null".to_string(),
107110
GDEXTENSION_CALL_ERROR_METHOD_NOT_CONST => "method is not const".to_string(), // not handled in Godot
111+
GODOT_RUST_CALL_ERROR => "godot-rust function call failed".to_string(),
108112
_ => format!("unknown reason (error code {error})"),
109113
};
110114

godot-macros/src/class/data_models/field_var.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,18 +194,21 @@ impl GetterSetterImpl {
194194
let export_token = make_method_registration(
195195
class_name,
196196
FuncDefinition {
197-
func: signature,
197+
signature,
198198
// Since we're analyzing a struct's field, we don't have access to the corresponding get/set function's
199199
// external (non-#[func]) attributes. We have to assume the function exists and has the name the user
200200
// gave us, with the expected signature.
201201
// Ideally, we'd be able to place #[cfg_attr] on #[var(get)] and #[var(set)] to be able to match a
202202
// #[cfg()] (for instance) placed on the getter/setter function, but that is not currently supported.
203203
external_attributes: Vec::new(),
204204
rename: None,
205+
is_virtual: false,
205206
has_gd_self: false,
206207
},
207208
);
208209

210+
let export_token = export_token.expect("getter/setter generation should not fail");
211+
209212
Self {
210213
function_name,
211214
function_impl,

0 commit comments

Comments
 (0)