#![allow(clippy::missing_safety_doc)]
use std::any::Any;
use godot_ffi as sys;
use sys::conv::u32_to_usize;
use sys::interface_fn;
use crate::builder::ClassBuilder;
use crate::builtin::{StringName, Variant};
use crate::classes::Object;
use crate::obj::{AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass, bounds, cap};
use crate::private::{IntoVirtualMethodReceiver, PanicPayload, handle_panic};
use crate::registry::info::PropertyInfo;
use crate::registry::plugin::ErasedDynGd;
use crate::storage::{InstanceStorage, Storage, StorageRefCounted, as_storage};
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub unsafe extern "C" fn create<T: cap::GodotDefault>(
_class_userdata: *mut std::ffi::c_void,
_notify_postinitialize: sys::GDExtensionBool,
) -> sys::GDExtensionObjectPtr {
create_custom(
T::__godot_user_init,
sys::conv::bool_from_sys(_notify_postinitialize),
)
.unwrap_or(std::ptr::null_mut())
}
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub unsafe extern "C" fn create<T: cap::GodotDefault>(
_class_userdata: *mut std::ffi::c_void,
) -> sys::GDExtensionObjectPtr {
create_custom(T::__godot_user_init, true).unwrap_or(std::ptr::null_mut())
}
#[cfg(all(since_api = "4.4", before_api = "4.5"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.4", before_api = "4.5"))))]
pub unsafe extern "C" fn create_null<T>(
_class_userdata: *mut std::ffi::c_void,
_notify_postinitialize: sys::GDExtensionBool,
) -> sys::GDExtensionObjectPtr {
std::ptr::null_mut()
}
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub unsafe extern "C" fn create_null<T>(
_class_userdata: *mut std::ffi::c_void,
) -> sys::GDExtensionObjectPtr {
std::ptr::null_mut()
}
pub unsafe extern "C" fn recreate<T: cap::GodotDefault>(
_class_userdata: *mut std::ffi::c_void,
object: sys::GDExtensionObjectPtr,
) -> sys::GDExtensionClassInstancePtr {
create_rust_part_for_existing_godot_part(T::__godot_user_init, object, |_| {})
.unwrap_or(std::ptr::null_mut())
}
#[cfg(before_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.5")))]
pub unsafe extern "C" fn recreate_null<T>(
_class_userdata: *mut std::ffi::c_void,
_object: sys::GDExtensionObjectPtr,
) -> sys::GDExtensionClassInstancePtr {
std::ptr::null_mut()
}
pub(crate) fn create_custom<T, F>(
make_user_instance: F,
notify_postinitialize: bool,
) -> Result<sys::GDExtensionObjectPtr, PanicPayload>
where
T: GodotClass,
F: FnOnce(Base<T::Base>) -> T,
{
let base_class_name = T::Base::class_id();
let base_ptr = unsafe { sys::classdb_construct_object(base_class_name.string_sys()) };
let postinit = |base_ptr| {
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
if notify_postinitialize {
let mut obj = unsafe { Gd::<Object>::from_obj_sys_weak(base_ptr) };
obj.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
obj.drop_weak();
}
};
match create_rust_part_for_existing_godot_part(make_user_instance, base_ptr, postinit) {
Ok(_extension_ptr) => Ok(base_ptr),
Err(payload) => {
unsafe { interface_fn!(object_destroy)(base_ptr) };
Err(payload)
}
}
}
fn create_rust_part_for_existing_godot_part<T, F, P>(
make_user_instance: F,
base_ptr: sys::GDExtensionObjectPtr,
postinit: P,
) -> Result<sys::GDExtensionClassInstancePtr, PanicPayload>
where
T: GodotClass,
F: FnOnce(Base<T::Base>) -> T,
P: Fn(sys::GDExtensionObjectPtr),
{
let class_name = T::class_id();
let base = unsafe { Base::from_sys(base_ptr) };
let context = || format!("{class_name}::init()");
let code = || make_user_instance(unsafe { Base::from_base(&base) });
let user_instance = handle_panic(context, std::panic::AssertUnwindSafe(code))?;
let mut base_copy = unsafe { Base::from_base(&base) };
let instance = InstanceStorage::<T>::construct(user_instance, base);
let instance_rust_ptr = instance.into_raw();
let instance_ptr = instance_rust_ptr as sys::GDExtensionClassInstancePtr;
let binding_data_callbacks = crate::storage::nop_instance_callbacks();
unsafe {
interface_fn!(object_set_instance)(base_ptr, class_name.string_sys(), instance_ptr);
interface_fn!(object_set_instance_binding)(
base_ptr,
sys::get_library() as *mut std::ffi::c_void,
instance_ptr as *mut std::ffi::c_void,
&binding_data_callbacks,
);
}
postinit(base_ptr);
base_copy.mark_initialized();
Ok(instance_ptr)
}
pub unsafe extern "C" fn free<T: GodotClass>(
_class_user_data: *mut std::ffi::c_void,
instance: sys::GDExtensionClassInstancePtr,
) {
unsafe {
{
let storage = as_storage::<T>(instance);
storage.mark_destroyed_by_godot();
}
crate::storage::destroy_storage::<T>(instance);
}
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub unsafe extern "C" fn get_virtual<T: cap::ImplementsGodotVirtual>(
_class_user_data: *mut std::ffi::c_void,
name: sys::GDExtensionConstStringNamePtr,
hash: u32,
) -> sys::GDExtensionClassCallVirtual {
unsafe {
let borrowed_string = StringName::borrow_string_sys(name);
let method_name = borrowed_string.to_string();
T::__virtual_call(method_name.as_str(), hash)
}
}
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub unsafe extern "C" fn get_virtual<T: cap::ImplementsGodotVirtual>(
_class_user_data: *mut std::ffi::c_void,
name: sys::GDExtensionConstStringNamePtr,
) -> sys::GDExtensionClassCallVirtual {
let borrowed_string = StringName::borrow_string_sys(name);
let method_name = borrowed_string.to_string();
T::__virtual_call(method_name.as_str())
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub unsafe extern "C" fn default_get_virtual<T: UserClass>(
_class_user_data: *mut std::ffi::c_void,
name: sys::GDExtensionConstStringNamePtr,
hash: u32,
) -> sys::GDExtensionClassCallVirtual {
unsafe {
let borrowed_string = StringName::borrow_string_sys(name);
let method_name = borrowed_string.to_string();
T::__default_virtual_call(method_name.as_str(), hash)
}
}
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub unsafe extern "C" fn default_get_virtual<T: UserClass>(
_class_user_data: *mut std::ffi::c_void,
name: sys::GDExtensionConstStringNamePtr,
) -> sys::GDExtensionClassCallVirtual {
let borrowed_string = StringName::borrow_string_sys(name);
let method_name = borrowed_string.to_string();
T::__default_virtual_call(method_name.as_str())
}
pub unsafe extern "C" fn to_string<T: cap::GodotToString>(
instance: sys::GDExtensionClassInstancePtr,
is_valid: *mut sys::GDExtensionBool,
out_string: sys::GDExtensionStringPtr,
) {
unsafe {
let storage = as_storage::<T>(instance);
let string = T::__godot_to_string(T::Recv::instance(storage));
string.move_into_string_ptr(out_string);
*is_valid = sys::conv::SYS_TRUE;
}
}
pub unsafe extern "C" fn on_notification<T: cap::GodotNotification>(
instance: sys::GDExtensionClassInstancePtr,
what: i32,
_reversed: sys::GDExtensionBool,
) {
unsafe {
let storage = as_storage::<T>(instance);
let mut instance = storage.get_mut();
T::__godot_notification(&mut *instance, what);
}
}
pub unsafe extern "C" fn get_property<T: cap::GodotGet>(
instance: sys::GDExtensionClassInstancePtr,
name: sys::GDExtensionConstStringNamePtr,
ret: sys::GDExtensionVariantPtr,
) -> sys::GDExtensionBool {
unsafe {
let storage = as_storage::<T>(instance);
let instance = T::Recv::instance(storage);
let property = StringName::new_from_string_sys(name);
match T::__godot_get_property(instance, property) {
Some(value) => {
value.move_into_var_ptr(ret);
sys::conv::SYS_TRUE
}
None => sys::conv::SYS_FALSE,
}
}
}
pub unsafe extern "C" fn set_property<T: cap::GodotSet>(
instance: sys::GDExtensionClassInstancePtr,
name: sys::GDExtensionConstStringNamePtr,
value: sys::GDExtensionConstVariantPtr,
) -> sys::GDExtensionBool {
unsafe {
let storage = as_storage::<T>(instance);
let instance = T::Recv::instance(storage);
let property = StringName::new_from_string_sys(name);
let value = Variant::new_from_var_sys(value);
sys::conv::bool_to_sys(T::__godot_set_property(instance, property, value))
}
}
pub unsafe extern "C" fn reference<T: GodotClass>(instance: sys::GDExtensionClassInstancePtr) {
let storage = unsafe { as_storage::<T>(instance) };
storage.on_inc_ref();
}
pub unsafe extern "C" fn unreference<T: GodotClass>(instance: sys::GDExtensionClassInstancePtr) {
let storage = unsafe { as_storage::<T>(instance) };
storage.on_dec_ref();
}
pub unsafe extern "C" fn get_property_list<T: cap::GodotGetPropertyList>(
instance: sys::GDExtensionClassInstancePtr,
count: *mut u32,
) -> *const sys::GDExtensionPropertyInfo {
let storage = unsafe { as_storage::<T>(instance) };
let instance = T::Recv::instance(storage);
let property_list = T::__godot_get_property_list(instance);
let property_list_sys: Box<[sys::GDExtensionPropertyInfo]> = property_list
.into_iter()
.map(|prop| prop.into_owned_property_sys())
.collect();
unsafe {
*count = property_list_sys
.len()
.try_into()
.expect("property list cannot be longer than `u32::MAX`");
}
Box::leak(property_list_sys).as_mut_ptr()
}
pub unsafe extern "C" fn free_property_list<T: cap::GodotGetPropertyList>(
_instance: sys::GDExtensionClassInstancePtr,
list: *const sys::GDExtensionPropertyInfo,
count: u32,
) {
let list = list as *mut sys::GDExtensionPropertyInfo;
let property_list_slice = unsafe { std::slice::from_raw_parts_mut(list, u32_to_usize(count)) };
let property_list_sys = unsafe { Box::from_raw(property_list_slice) };
for property_info in property_list_sys.iter() {
unsafe {
crate::registry::info::PropertyInfo::free_owned_property_sys(*property_info);
}
}
}
unsafe fn raw_property_get_revert<T: cap::GodotPropertyGetRevert>(
instance: sys::GDExtensionClassInstancePtr,
property_name: sys::GDExtensionConstStringNamePtr,
) -> Option<Variant> {
let storage = unsafe { as_storage::<T>(instance) };
let instance = T::Recv::instance(storage);
let property = unsafe { StringName::borrow_string_sys(property_name) };
T::__godot_property_get_revert(instance, property.clone())
}
pub unsafe extern "C" fn property_can_revert<T: cap::GodotPropertyGetRevert>(
instance: sys::GDExtensionClassInstancePtr,
property_name: sys::GDExtensionConstStringNamePtr,
) -> sys::GDExtensionBool {
let revert = unsafe { raw_property_get_revert::<T>(instance, property_name) };
sys::conv::bool_to_sys(revert.is_some())
}
pub unsafe extern "C" fn property_get_revert<T: cap::GodotPropertyGetRevert>(
instance: sys::GDExtensionClassInstancePtr,
property_name: sys::GDExtensionConstStringNamePtr,
ret: sys::GDExtensionVariantPtr,
) -> sys::GDExtensionBool {
let Some(revert) = (unsafe { raw_property_get_revert::<T>(instance, property_name) }) else {
return sys::conv::SYS_FALSE;
};
unsafe {
revert.move_into_var_ptr(ret);
}
sys::conv::SYS_TRUE
}
pub unsafe extern "C" fn validate_property<T: cap::GodotValidateProperty>(
instance: sys::GDExtensionClassInstancePtr,
property_info_ptr: *mut sys::GDExtensionPropertyInfo,
) -> sys::GDExtensionBool {
let storage = unsafe { as_storage::<T>(instance) };
let instance = T::Recv::instance(storage);
let mut property_info = unsafe { PropertyInfo::new_from_sys(property_info_ptr) };
T::__godot_validate_property(instance, &mut property_info);
unsafe { property_info.move_into_property_info_ptr(property_info_ptr) };
sys::conv::SYS_TRUE
}
pub fn register_class_by_builder<T: cap::GodotRegisterClass>(_class_builder: &mut dyn Any) {
let mut class_builder = ClassBuilder::new();
T::__godot_register_class(&mut class_builder);
}
pub fn register_user_properties<T: cap::ImplementsGodotExports>(_class_builder: &mut dyn Any) {
T::__register_exports();
}
pub fn register_user_methods_constants<T: cap::ImplementsGodotApi>(_class_builder: &mut dyn Any) {
T::__register_methods();
T::__register_constants();
}
pub fn register_user_rpcs<T: cap::ImplementsGodotApi>(object: &mut dyn Any) {
T::__register_rpcs(object);
}
pub unsafe fn dynify_fn<T, D>(obj: Gd<Object>) -> ErasedDynGd
where
T: Inherits<Object> + AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
D: ?Sized + 'static,
{
let obj = unsafe { obj.try_cast::<T>().unwrap_unchecked() };
let obj = obj.into_dyn::<D>();
let obj = obj.upcast::<Object>();
ErasedDynGd {
boxed: Box::new(obj),
}
}