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

Support GDExtensionScriptInstanceInfo3 in 4.3 #849

Merged
merged 1 commit into from
Aug 12, 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
141 changes: 133 additions & 8 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 [`Object::get_method_argument_count`](crate::classes::Object::get_method_argument_count)
/// and `Callable::get_argument_count`.
TitanNano marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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 @@ -236,7 +248,10 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
set_func: Some(script_instance_info::set_property_func::<T>),
get_func: Some(script_instance_info::get_property_func::<T>),
get_property_list_func: Some(script_instance_info::get_property_list_func::<T>),
#[cfg(before_api = "4.3")]
free_property_list_func: Some(script_instance_info::free_property_list_func::<T>),
#[cfg(since_api = "4.3")]
free_property_list_func: Some(script_instance_info::free_property_list_func),

#[cfg(since_api = "4.2")]
get_class_category_func: None, // not yet implemented.
Expand All @@ -249,7 +264,10 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
get_property_state_func: Some(script_instance_info::get_property_state_func::<T>),

get_method_list_func: Some(script_instance_info::get_method_list_func::<T>),
#[cfg(before_api = "4.3")]
free_method_list_func: Some(script_instance_info::free_method_list_func::<T>),
#[cfg(since_api = "4.3")]
free_method_list_func: Some(script_instance_info::free_method_list_func),
get_property_type_func: Some(script_instance_info::get_property_type_func::<T>),
#[cfg(since_api = "4.2")]
validate_property_func: None, // not yet implemented.
Expand All @@ -274,14 +292,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 +323,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 +486,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 +566,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 +659,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 +701,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 @@ -676,11 +714,12 @@ mod script_instance_info {
return_pointer
}

/// Provides the same functionality as the function below, but for Godot 4.2 and lower.
///
/// # Safety
///
/// - `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`.
/// See latest version below.
#[cfg(before_api = "4.3")]
pub(super) unsafe extern "C" fn free_property_list_func<T: ScriptInstance>(
p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_prop_info: *const sys::GDExtensionPropertyInfo,
Expand All @@ -699,6 +738,27 @@ mod script_instance_info {
}
}

/// # Safety
/// - `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`.
#[cfg(since_api = "4.3")]
pub(super) unsafe extern "C" fn free_property_list_func(
_p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_prop_info: *const sys::GDExtensionPropertyInfo,
p_len: u32,
) {
// 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.
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
// on it.
unsafe { PropertyInfo::free_owned_property_sys(*info) };
}
}

/// # Safety
///
/// - `p_self` must point to a live immutable [`ScriptInstanceData<T>`] for the duration of this function call
Expand Down Expand Up @@ -822,11 +882,12 @@ mod script_instance_info {
bool_to_sys(has_method)
}

/// Provides the same functionality as the function below, but for Godot 4.2 and lower.
///
/// # Safety
///
/// - `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`.
/// See latest version below.
#[cfg(before_api = "4.3")]
pub(super) unsafe extern "C" fn free_method_list_func<T: ScriptInstance>(
p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_method_info: *const sys::GDExtensionMethodInfo,
Expand All @@ -845,6 +906,28 @@ mod script_instance_info {
}
}

/// # Safety
///
/// - `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`.
#[cfg(since_api = "4.3")]
pub(super) unsafe extern "C" fn free_method_list_func(
_p_instance: sys::GDExtensionScriptInstanceDataPtr,
p_method_info: *const sys::GDExtensionMethodInfo,
p_len: u32,
) {
// 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.
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
// it.
unsafe { MethodInfo::free_owned_method_sys(*info) };
}
}

/// # Safety
///
/// - `p_instance` must point to a live immutable [`ScriptInstanceData<T>`] for the duration of this function call
Expand Down Expand Up @@ -1108,4 +1191,46 @@ 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) }
// Can panic if the GdCell is currently mutably bound.
.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()
}
}
33 changes: 33 additions & 0 deletions godot-ffi/src/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,39 @@ 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_ptr();

(ptr, 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 be passed to this function exactly once and not used in any other context.
#[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
5 changes: 5 additions & 0 deletions itest/rust/src/builtin_tests/script/script_instance_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,9 @@ impl ScriptInstance for TestScriptInstance {
fn property_set_fallback(_this: SiMut<Self>, _name: StringName, _value: &Variant) -> bool {
false
}

#[cfg(since_api = "4.3")]
fn get_method_argument_count(&self, _method: StringName) -> Option<u32> {
None
}
}
Loading