Skip to content

Commit

Permalink
Add #[func(virtual)] that allows scripts to override Rust methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Feb 13, 2024
1 parent 6c34515 commit bf5b711
Show file tree
Hide file tree
Showing 15 changed files with 626 additions and 166 deletions.
2 changes: 1 addition & 1 deletion godot-bindings/src/godot_exe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub(crate) fn read_godot_version(godot_bin: &Path) -> GodotVersion {
let output = execute(cmd, "read Godot version");
let stdout = std::str::from_utf8(&output.stdout).expect("convert Godot version to UTF-8");

match parse_godot_version(&stdout) {
match parse_godot_version(stdout) {
Ok(parsed) => {
assert_eq!(
parsed.major,
Expand Down
1 change: 1 addition & 0 deletions godot-codegen/src/special_cases/codegen_special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const SELECTED_CLASSES: &[&str] = &[
"EditorPlugin",
"Engine",
"FileAccess",
"GDScript",
"HTTPRequest",
"Image",
"ImageTextureLayered",
Expand Down
48 changes: 46 additions & 2 deletions godot-core/src/builtin/meta/registration/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,16 @@ impl ClassMethodInfo {
default_argument_count: self.default_argument_count(),
default_arguments: default_arguments_sys.as_mut_ptr(),
};
// SAFETY:
// The lifetime of the data we use here is at least as long as this function's scope. So we can

if self.method_flags.is_set(MethodFlags::VIRTUAL) {
self.register_virtual_class_method(method_info_sys, return_value_sys);
} else {
self.register_nonvirtual_class_method(method_info_sys);
}
}

fn register_nonvirtual_class_method(&self, method_info_sys: sys::GDExtensionClassMethodInfo) {
// SAFETY: The lifetime of the data we use here is at least as long as this function's scope. So we can
// safely call this function without issue.
//
// Null pointers will only be passed along if we indicate to Godot that they are unused.
Expand All @@ -150,6 +158,42 @@ impl ClassMethodInfo {
}
}

#[cfg(since_api = "4.3")]
fn register_virtual_class_method(
&self,
normal_method_info: sys::GDExtensionClassMethodInfo,
return_value_sys: sys::GDExtensionPropertyInfo, // passed separately because value, not pointer.
) {
// Copy everything possible from regular method info.
let method_info_sys = sys::GDExtensionClassVirtualMethodInfo {
name: normal_method_info.name,
method_flags: normal_method_info.method_flags,
return_value: return_value_sys,
return_value_metadata: normal_method_info.return_value_metadata,
argument_count: normal_method_info.argument_count,
arguments: normal_method_info.arguments_info,
arguments_metadata: normal_method_info.arguments_metadata,
};

// SAFETY: Godot only needs arguments to be alive during the method call.
unsafe {
interface_fn!(classdb_register_extension_class_virtual_method)(
sys::get_library(),
self.class_name.string_sys(),
std::ptr::addr_of!(method_info_sys),
)
}
}

// Polyfill doing nothing.
#[cfg(before_api = "4.3")]
fn register_virtual_class_method(
&self,
_normal_method_info: sys::GDExtensionClassMethodInfo,
_return_value_sys: sys::GDExtensionPropertyInfo,
) {
}

fn argument_count(&self) -> u32 {
self.arguments
.len()
Expand Down
53 changes: 53 additions & 0 deletions godot-core/src/builtin/meta/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@ pub trait VarcallSignatureTuple: PtrcallSignatureTuple {
varargs: &[Variant],
) -> Self::Ret;

/// Outbound virtual call to a method overridden by a script attached to the object.
///
/// Returns `None` if the script does not override the method.
#[cfg(since_api = "4.3")]
unsafe fn out_script_virtual_call(
// Separate parameters to reduce tokens in macro-generated API.
class_name: &'static str,
method_name: &'static str,
method_sname_ptr: sys::GDExtensionConstStringNamePtr,
object_ptr: sys::GDExtensionObjectPtr,
args: Self::Params,
) -> Self::Ret;

unsafe fn out_utility_ptrcall_varargs(
utility_fn: UtilityFunctionBind,
function_name: &'static str,
Expand Down Expand Up @@ -229,6 +242,45 @@ macro_rules! impl_varcall_signature_for_tuple {
result.unwrap_or_else(|err| return_error::<Self::Ret>(&call_ctx, err))
}

#[cfg(since_api = "4.3")]
unsafe fn out_script_virtual_call(
// Separate parameters to reduce tokens in macro-generated API.
class_name: &'static str,
method_name: &'static str,
method_sname_ptr: sys::GDExtensionConstStringNamePtr,
object_ptr: sys::GDExtensionObjectPtr,
($($pn,)*): Self::Params,
) -> Self::Ret {
// Assumes that caller has previously checked existence of a virtual method.

let call_ctx = CallContext::outbound(class_name, method_name);
//$crate::out!("out_script_virtual_call: {call_ctx}");

let object_call_script_method = sys::interface_fn!(object_call_script_method);
let explicit_args = [
$(
GodotFfiVariant::ffi_to_variant(&into_ffi($pn)),
)*
];

let variant_ptrs = explicit_args.iter().map(Variant::var_sys_const).collect::<Vec<_>>();

let variant = Variant::from_var_sys_init(|return_ptr| {
let mut err = sys::default_call_error();
object_call_script_method(
object_ptr,
method_sname_ptr,
variant_ptrs.as_ptr(),
variant_ptrs.len() as i64,
return_ptr,
std::ptr::addr_of_mut!(err),
);
});

let result = <Self::Ret as FromGodot>::try_from_variant(&variant);
result.unwrap_or_else(|err| return_error::<Self::Ret>(&call_ctx, err))
}

// Note: this is doing a ptrcall, but uses variant conversions for it
#[inline]
unsafe fn out_utility_ptrcall_varargs(
Expand Down Expand Up @@ -257,6 +309,7 @@ macro_rules! impl_varcall_signature_for_tuple {
result.unwrap_or_else(|err| return_error::<Self::Ret>(&call_ctx, err))
}


#[inline]
fn format_args(args: &Self::Params) -> String {
let mut string = String::new();
Expand Down
5 changes: 5 additions & 0 deletions godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ impl StringName {
fn string_sys = sys;
}

#[doc(hidden)]
pub fn string_sys_const(&self) -> sys::GDExtensionConstStringNamePtr {
sys::to_const_ptr(self.string_sys())
}

#[doc(hidden)]
pub fn as_inner(&self) -> inner::InnerStringName {
inner::InnerStringName::from_outer(self)
Expand Down
6 changes: 6 additions & 0 deletions godot-core/src/obj/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ impl<T: GodotClass> Base<T> {
pub fn as_gd(&self) -> &Gd<T> {
&self.obj
}

// Currently only used in outbound virtual calls (for scripts).
#[doc(hidden)]
pub fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
self.obj.obj_sys()
}
}

impl<T: GodotClass> Debug for Base<T> {
Expand Down
3 changes: 2 additions & 1 deletion godot-core/src/obj/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,8 @@ impl<T: GodotClass> Gd<T> {
Self::from_obj_sys_weak_or_none(ptr).unwrap()
}

pub(crate) fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
#[doc(hidden)]
pub fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
self.raw.obj_sys()
}

Expand Down
8 changes: 8 additions & 0 deletions godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ where
}
}

#[cfg(since_api = "4.3")]
pub unsafe fn has_virtual_script_method(
object_ptr: sys::GDExtensionObjectPtr,
method_sname: sys::GDExtensionConstStringNamePtr,
) -> bool {
sys::interface_fn!(object_has_script_method)(sys::to_const_ptr(object_ptr), method_sname) != 0
}

pub fn flush_stdout() {
use std::io::Write;
std::io::stdout().flush().expect("flush stdout");
Expand Down
5 changes: 4 additions & 1 deletion godot-macros/src/class/data_models/field_var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,21 @@ impl GetterSetterImpl {
let export_token = make_method_registration(
class_name,
FuncDefinition {
func: signature,
signature,
// Since we're analyzing a struct's field, we don't have access to the corresponding get/set function's
// external (non-#[func]) attributes. We have to assume the function exists and has the name the user
// gave us, with the expected signature.
// Ideally, we'd be able to place #[cfg_attr] on #[var(get)] and #[var(set)] to be able to match a
// #[cfg()] (for instance) placed on the getter/setter function, but that is not currently supported.
external_attributes: Vec::new(),
rename: None,
is_virtual: false,
has_gd_self: false,
},
);

let export_token = export_token.expect("getter/setter generation should not fail");

Self {
function_name,
function_impl,
Expand Down
Loading

0 comments on commit bf5b711

Please sign in to comment.