Skip to content

Commit

Permalink
Support GDExtensionScriptInstanceInfo3 in 4.3
Browse files Browse the repository at this point in the history
  • Loading branch information
TitanNano committed Aug 11, 2024
1 parent b294625 commit 5d88935
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 4 deletions.
93 changes: 89 additions & 4 deletions godot-core/src/obj/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::meta::{MethodInfo, PropertyInfo};
use crate::obj::{Base, Gd, GodotClass};
use crate::sys;

#[cfg(before_api = "4.3")]
use self::bounded_ptr_list::BoundedPtrList;

/// Implement custom scripts that can be attached to objects in Godot.
Expand Down Expand Up @@ -153,17 +154,28 @@ pub trait ScriptInstance: Sized {
/// The engine may call this function if
/// [`IScriptExtension::is_placeholder_fallback_enabled`](crate::classes::IScriptExtension::is_placeholder_fallback_enabled) is enabled.
fn property_set_fallback(this: SiMut<Self>, name: StringName, value: &Variant) -> bool;

/// This function will be called to handle calls to [`Òbject::get_method_argument_count`](crate::classes::Object::get_method_argument_count)
/// and `Callable::get_argument_count`.
///
/// If [`None`] is returned the public methods will return `0`.
#[cfg(since_api = "4.3")]
fn get_method_argument_count(&self, _method: StringName) -> Option<u32>;
}

#[cfg(before_api = "4.2")]
type ScriptInstanceInfo = sys::GDExtensionScriptInstanceInfo;
#[cfg(since_api = "4.2")]
#[cfg(all(since_api = "4.2", before_api = "4.3"))]
type ScriptInstanceInfo = sys::GDExtensionScriptInstanceInfo2;
#[cfg(since_api = "4.3")]
type ScriptInstanceInfo = sys::GDExtensionScriptInstanceInfo3;

struct ScriptInstanceData<T: ScriptInstance> {
inner: GdCell<T>,
script_instance_ptr: *mut ScriptInstanceInfo,
#[cfg(before_api = "4.3")]
property_lists: BoundedPtrList<sys::GDExtensionPropertyInfo>,
#[cfg(before_api = "4.3")]
method_lists: BoundedPtrList<sys::GDExtensionMethodInfo>,
base: Base<T::Base>,
}
Expand Down Expand Up @@ -274,14 +286,21 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
get_language_func: Some(script_instance_info::get_language_func::<T>),

free_func: Some(script_instance_info::free_func::<T>),

#[cfg(since_api = "4.3")]
get_method_argument_count_func: Some(
script_instance_info::get_method_argument_count_func::<T>,
),
};

let instance_ptr = Box::into_raw(Box::new(gd_instance));

let data = ScriptInstanceData {
inner: GdCell::new(rust_instance),
script_instance_ptr: instance_ptr,
#[cfg(before_api = "4.3")]
property_lists: BoundedPtrList::new(),
#[cfg(before_api = "4.3")]
method_lists: BoundedPtrList::new(),
// SAFETY: The script instance is always freed before the base object is destroyed. The weak reference should therefore never be
// accessed after it has been freed.
Expand All @@ -298,9 +317,12 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
#[cfg(before_api = "4.2")]
let create_fn = sys::interface_fn!(script_instance_create);

#[cfg(since_api = "4.2")]
#[cfg(all(since_api = "4.2", before_api = "4.3"))]
let create_fn = sys::interface_fn!(script_instance_create2);

#[cfg(since_api = "4.3")]
let create_fn = sys::interface_fn!(script_instance_create3);

create_fn(
instance_ptr,
data_ptr as sys::GDExtensionScriptInstanceDataPtr,
Expand Down Expand Up @@ -458,6 +480,7 @@ impl<'a, T: ScriptInstance> DerefMut for SiMut<'a, T> {
}

// Encapsulate BoundedPtrList to help ensure safety.
#[cfg(before_api = "4.3")]
mod bounded_ptr_list {
use std::collections::HashMap;
use std::sync::Mutex;
Expand Down Expand Up @@ -537,6 +560,8 @@ mod script_instance_info {
use super::{ScriptInstance, ScriptInstanceData, SiMut};
use crate::meta::{MethodInfo, PropertyInfo};
use sys::conv::{bool_to_sys, SYS_FALSE, SYS_TRUE};
#[cfg(since_api = "4.3")]
use sys::conv::{ptr_list_from_sys, ptr_list_into_sys};

/// # Safety
///
Expand Down Expand Up @@ -628,10 +653,14 @@ mod script_instance_info {
})
.unwrap_or_default();

#[cfg(before_api = "4.3")]
let (list_ptr, list_length) = borrow_instance()
.property_lists
.list_into_sys(property_list);

#[cfg(since_api = "4.3")]
let (list_ptr, list_length) = ptr_list_into_sys(property_list);

// SAFETY: It is safe to assign a `u32` to `r_count`.
unsafe {
*r_count = list_length;
Expand Down Expand Up @@ -666,8 +695,11 @@ mod script_instance_info {
})
.unwrap_or_default();

#[cfg(before_api = "4.3")]
let (return_pointer, list_length) =
borrow_instance().method_lists.list_into_sys(method_list);
#[cfg(since_api = "4.3")]
let (return_pointer, list_length) = ptr_list_into_sys(method_list);

unsafe {
*r_count = list_length;
Expand All @@ -681,16 +713,22 @@ mod script_instance_info {
/// - `p_instance` must point to a live immutable [`ScriptInstanceData<T>`] for the duration of this function call
/// - `p_prop_info` must have been returned from a call to [`get_property_list_func`] called with the same `p_instance` pointer.
/// - `p_prop_info` must not have been mutated since the call to `get_property_list_func`.
#[allow(clippy::extra_unused_type_parameters)]
pub(super) unsafe extern "C" fn free_property_list_func<T: ScriptInstance>(
p_instance: sys::GDExtensionScriptInstanceDataPtr,
#[allow(unused_variables)] p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_prop_info: *const sys::GDExtensionPropertyInfo,
#[cfg(since_api = "4.3")] p_len: u32,
) {
// SAFETY: `p_instance` points to a live immutable `ScriptInstanceData<T>` for the duration of this call.
#[cfg(before_api = "4.3")]
let instance = unsafe { ScriptInstanceData::<T>::borrow_script_sys(p_instance) };

// SAFETY: `p_prop_info` was returned from a call to `list_into_sys`, and has not been mutated since. This is also the first call
// to `list_from_sys` with this pointer.
#[cfg(before_api = "4.3")]
let property_infos = unsafe { instance.property_lists.list_from_sys(p_prop_info) };
#[cfg(since_api = "4.3")]
let property_infos = unsafe { ptr_list_from_sys(p_prop_info, p_len) };

for info in property_infos.iter() {
// SAFETY: `info` was returned from a call to `into_owned_property_sys` and this is the first and only time this function is called
Expand Down Expand Up @@ -827,16 +865,22 @@ mod script_instance_info {
/// - `p_instance` must point to a live immutable [`ScriptInstanceData<T>`] for the duration of this function call
/// - `p_method_info` must have been returned from a call to [`get_method_list_func`] called with the same `p_instance` pointer.
/// - `p_method_info` must not have been mutated since the call to `get_method_list_func`.
#[allow(clippy::extra_unused_type_parameters)]
pub(super) unsafe extern "C" fn free_method_list_func<T: ScriptInstance>(
p_instance: sys::GDExtensionScriptInstanceDataPtr,
#[allow(unused_variables)] p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_method_info: *const sys::GDExtensionMethodInfo,
#[cfg(since_api = "4.3")] p_len: u32,
) {
// SAFETY: `p_instance` points to a live immutable `ScriptInstanceData<T>` for the duration of this call.
#[cfg(before_api = "4.3")]
let instance = unsafe { ScriptInstanceData::<T>::borrow_script_sys(p_instance) };

// SAFETY: `p_method_info` was returned from a call to `list_into_sys`, and has not been mutated since. This is also the first call
// to `list_from_sys` with this pointer.
#[cfg(before_api = "4.3")]
let method_infos = unsafe { instance.method_lists.list_from_sys(p_method_info) };
#[cfg(since_api = "4.3")]
let method_infos = unsafe { ptr_list_from_sys(p_method_info, p_len) };

for info in method_infos.iter() {
// SAFETY: `info` was returned from a call to `into_owned_method_sys`, and this is the first and only time we call this method on
Expand Down Expand Up @@ -1108,4 +1152,45 @@ mod script_instance_info {

bool_to_sys(result)
}

/// # Safety
///
/// - `p_instance` must point to a live immutable [`ScriptInstanceData<T>`] for the duration of this function call
/// - `p_method` has to point to a valid [`StringName`].
/// - `p_value` must be a valid [`sys::GDExtensionBool`] pointer.
#[cfg(since_api = "4.3")]
pub(super) unsafe extern "C" fn get_method_argument_count_func<T: ScriptInstance>(
p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_method: sys::GDExtensionConstStringNamePtr,
r_is_valid: *mut sys::GDExtensionBool,
) -> sys::GDExtensionInt {
// SAFETY: `p_method` is a valid [`StringName`] pointer.
let method = unsafe { StringName::new_from_string_sys(p_method) };
let ctx = || {
format!(
"error when calling {}::get_method_argument_count_func",
type_name::<T>()
)
};

let method_argument_count = handle_panic(ctx, || {
// SAFETY: `p_instance` points to a live immutable `ScriptInstanceData<T>` for the duration of this call.
unsafe { ScriptInstanceData::<T>::borrow_script_sys(p_instance) }
.borrow()
// this is user code and could cause a panic.
.get_method_argument_count(method)
})
// In case of a panic, handle_panic will print an error message. We will recover from the panic by falling back to the default value None.
.unwrap_or_default();

let (result, is_valid) = match method_argument_count {
Some(count) => (count, SYS_TRUE),
None => (0, SYS_FALSE),
};

// SAFETY: `r_is_valid` is assignable.
unsafe { *r_is_valid = is_valid };

result.into()
}
}
35 changes: 35 additions & 0 deletions godot-ffi/src/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ pub const fn bool_to_sys(value: bool) -> sys::GDExtensionBool {
value as sys::GDExtensionBool
}

/// Convert a list into a pointer + length pair. Should be used together with [`ptr_list_from_sys`].
///
/// If `list_from_sys` is not called on this list then that will cause a memory leak.
#[cfg(since_api = "4.3")]
pub fn ptr_list_into_sys<T>(list: Vec<T>) -> (*const T, u32) {
let len: u32 = list
.len()
.try_into()
.expect("list must have length that fits in u32");
let ptr = Box::leak(list.into_boxed_slice()).as_mut_ptr();

(ptr.cast_const(), len)
}

/// Get a list back from a previous call to [`ptr_list_into_sys`].
///
/// # Safety
/// - `ptr` must have been returned from a call to `list_into_sys`.
/// - `ptr` must not have been used in a call to this function before.
/// - `ptr` must not have been mutated since the call to `list_into_sys`.
/// - `ptr` must not be accessed after calling this function.
#[cfg(since_api = "4.3")]
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe fn ptr_list_from_sys<T>(ptr: *const T, len: u32) -> Box<[T]> {
let ptr: *mut T = ptr.cast_mut();
let len: usize = sys::conv::u32_to_usize(len);

// SAFETY: `ptr` was created in `list_into_sys` from a slice of length `len`.
// And has not been mutated since. It was mutable in the first place, but GDExtension API requires const ptr.
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, len) };

// SAFETY: This is the first call to this function, and the list will not be accessed again after this function call.
unsafe { Box::from_raw(slice) }
}

pub const SYS_TRUE: sys::GDExtensionBool = bool_to_sys(true);
pub const SYS_FALSE: sys::GDExtensionBool = bool_to_sys(false);

Expand Down

0 comments on commit 5d88935

Please sign in to comment.