Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#[func(virtual)]: override virtual Rust functions in GDScript #606

Merged
merged 7 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
5 changes: 5 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ pub trait EngineBitfield: Copy {
Self::try_from_ord(ord)
.unwrap_or_else(|| panic!("ordinal {ord} does not map to any valid bit flag"))
}

// TODO consolidate API: named methods vs. | & ! etc.
fn is_set(self, flag: Self) -> bool {
self.ord() & flag.ord() != 0
}
}

/// Trait for enums that can be used as indices in arrays.
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
4 changes: 4 additions & 0 deletions godot-ffi/src/extras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ impl_as_uninit!(GDExtensionTypePtr, GDExtensionUninitializedTypePtr);
// ----------------------------------------------------------------------------------------------------------------------------------------------
// Helper functions

/// Differentiate from `sys::GDEXTENSION_CALL_ERROR_*` codes.
pub const GODOT_RUST_CALL_ERROR: GDExtensionCallErrorType = 40;

#[doc(hidden)]
#[inline]
pub fn default_call_error() -> GDExtensionCallError {
Expand Down Expand Up @@ -105,6 +108,7 @@ pub fn panic_call_error(
}
GDEXTENSION_CALL_ERROR_INSTANCE_IS_NULL => "instance is null".to_string(),
GDEXTENSION_CALL_ERROR_METHOD_NOT_CONST => "method is not const".to_string(), // not handled in Godot
GODOT_RUST_CALL_ERROR => "godot-rust function call failed".to_string(),
_ => format!("unknown reason (error code {error})"),
};

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
Loading