use std::{fmt, ptr};
use godot_ffi as sys;
use sys::{ExtVariantType, GodotFfi, PtrcallType, interface_fn};
use crate::builtin::{Variant, VariantType};
#[cfg(safeguards_balanced)] #[cfg_attr(published_docs, doc(cfg(safeguards_balanced)))]
use crate::meta::CallContext;
use crate::meta::error::{ConvertError, FromVariantError};
use crate::meta::shape::GodotShape;
use crate::meta::{FromGodot, GodotConvert, GodotFfiVariant, GodotType, RefArg, ToGodot};
use crate::obj::bounds::{Declarer, DynMemory as _};
use crate::obj::casts::CastSuccess;
use crate::obj::rtti::ObjectRtti;
use crate::obj::{Bounds, Gd, GdDerefTarget, GdMut, GdRef, GodotClass, InstanceId, bounds};
use crate::storage::{InstanceCache, InstanceStorage, Storage};
use crate::{classes, meta, out};
#[repr(C)]
#[doc(hidden)]
pub struct RawGd<T: GodotClass> {
pub(super) obj: *mut T,
cached_rtti: Option<ObjectRtti>,
cached_storage_ptr: <<T as Bounds>::Declarer as Declarer>::InstanceCache,
}
impl<T: GodotClass> RawGd<T> {
pub(super) unsafe fn from_obj_sys_weak(obj: sys::GDExtensionObjectPtr) -> Self {
let rtti = if obj.is_null() {
None
} else {
let raw_id = unsafe { interface_fn!(object_get_instance_id)(obj) };
let instance_id = InstanceId::try_from_u64(raw_id)
.expect("null instance ID when constructing object; this very likely causes UB");
Some(ObjectRtti::of::<T>(instance_id))
};
Self {
obj: obj.cast::<T>(),
cached_rtti: rtti,
cached_storage_ptr: InstanceCache::null(),
}
}
pub(super) unsafe fn from_obj_sys(obj: sys::GDExtensionObjectPtr) -> Self {
unsafe { Self::from_obj_sys_weak(obj).with_inc_refcount() }
}
fn with_inc_refcount(mut self) -> Self {
T::DynMemory::maybe_init_ref(&mut self);
self
}
pub(crate) fn is_null(&self) -> bool {
self.obj.is_null() || self.cached_rtti.is_none()
}
pub(crate) fn null() -> Self {
Self {
obj: ptr::null_mut(),
cached_rtti: None,
cached_storage_ptr: InstanceCache::null(),
}
}
pub(crate) fn instance_id_unchecked(&self) -> Option<InstanceId> {
self.cached_rtti.as_ref().map(|rtti| rtti.instance_id())
}
pub(crate) fn is_instance_valid(&self) -> bool {
self.cached_rtti
.as_ref()
.is_some_and(|rtti| rtti.instance_id().lookup_validity())
}
fn is_cast_valid<U>(&self) -> bool
where
U: GodotClass,
{
self.is_null() || self.as_object_ref().is_class(&U::class_id().to_gstring())
}
pub(super) fn owned_cast<U>(self) -> Result<RawGd<U>, Self>
where
U: GodotClass,
{
if !self.is_cast_valid::<U>() {
return Err(self);
}
match self.ffi_cast::<U>() {
Ok(success) => Ok(success.into_dest(self)),
Err(_) => Err(self),
}
}
pub(super) fn ffi_cast<U>(&self) -> Result<CastSuccess<T, U>, ()>
where
U: GodotClass,
{
if self.is_null() {
return Ok(CastSuccess::null());
}
self.check_rtti("ffi_cast");
let cast_object_ptr = unsafe {
let class_tag = interface_fn!(classdb_get_class_tag)(U::class_id().string_sys());
interface_fn!(object_cast_to)(self.obj_sys(), class_tag)
};
if cast_object_ptr.is_null() {
return Err(());
}
let weak = unsafe { RawGd::from_obj_sys_weak(cast_object_ptr) };
Ok(CastSuccess::from_weak(weak))
}
pub fn with_ref_counted<R>(&self, apply: impl Fn(&mut classes::RefCounted) -> R) -> R {
match self.try_with_ref_counted(apply) {
Ok(result) => result,
Err(()) if self.is_null() => {
panic!(
"RawGd::with_ref_counted(): expected to inherit RefCounted, encountered null pointer"
);
}
Err(()) => {
let gd_ref = unsafe { self.as_non_null() };
let class = gd_ref.dynamic_class_string();
panic!(
"Operation not permitted for object of class {class}:\n\
class is either not RefCounted, or currently in construction/destruction phase"
);
}
}
}
#[expect(clippy::result_unit_err)]
pub fn try_with_ref_counted<R>(
&self,
apply: impl Fn(&mut classes::RefCounted) -> R,
) -> Result<R, ()> {
let mut ref_counted = self.ffi_cast::<classes::RefCounted>()?;
let return_val = apply(ref_counted.as_dest_mut().as_target_mut());
Ok(return_val)
}
pub(crate) unsafe fn as_non_null(&self) -> &Gd<T> {
sys::strict_assert!(
!self.is_null(),
"RawGd::as_non_null() called on null pointer; this is UB"
);
unsafe { std::mem::transmute::<&RawGd<T>, &Gd<T>>(self) }
}
pub(crate) fn as_object_ref(&self) -> &classes::Object {
unsafe { self.as_upcast_ref() }
}
pub(crate) fn as_object_mut(&mut self) -> &mut classes::Object {
unsafe { self.as_upcast_mut() }
}
pub(super) unsafe fn as_upcast_ref<Base>(&self) -> &Base
where
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
{
unsafe {
self.ensure_valid_upcast::<Base>();
std::mem::transmute::<&Self, &Base>(self)
}
}
pub(super) unsafe fn as_upcast_mut<Base>(&mut self) -> &mut Base
where
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
{
unsafe {
self.ensure_valid_upcast::<Base>();
std::mem::transmute::<&mut Self, &mut Base>(self)
}
}
pub(super) fn as_target(&self) -> &GdDerefTarget<T>
where
GdDerefTarget<T>: Bounds<Declarer = bounds::DeclEngine>,
{
unsafe { self.as_upcast_ref::<GdDerefTarget<T>>() }
}
pub(super) fn as_target_mut(&mut self) -> &mut GdDerefTarget<T>
where
GdDerefTarget<T>: Bounds<Declarer = bounds::DeclEngine>,
{
unsafe { self.as_upcast_mut::<GdDerefTarget<T>>() }
}
#[allow(clippy::extra_unused_type_parameters)]
fn ensure_valid_upcast<Base>(&self)
where
Base: GodotClass,
{
self.check_rtti("upcast_ref");
sys::balanced_assert!(!self.is_null(), "cannot upcast null object refs");
#[cfg(safeguards_strict)]
{
let ffi_dest = self.ffi_cast::<Base>().expect("failed FFI upcast");
let direct_id = self.instance_id_unchecked().expect("direct_id null");
let ffi_id = ffi_dest
.as_dest_ref()
.instance_id_unchecked()
.expect("ffi_id null");
assert_eq!(
direct_id, ffi_id,
"upcast_ref: direct and FFI IDs differ. This is a bug, please report to godot-rust maintainers."
);
}
}
#[cfg(safeguards_balanced)] #[cfg_attr(published_docs, doc(cfg(safeguards_balanced)))]
pub(crate) fn check_rtti(&self, method_name: &'static str) {
let call_ctx = CallContext::gd::<T>(method_name);
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
self.check_dynamic_type(&call_ctx);
let instance_id = unsafe { self.instance_id_unchecked().unwrap_unchecked() };
classes::ensure_object_alive(instance_id, self.obj_sys(), &call_ctx);
}
#[cfg(not(safeguards_balanced))] #[cfg_attr(published_docs, doc(cfg(not(safeguards_balanced))))]
pub(crate) fn check_rtti(&self, _method_name: &'static str) {
}
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
#[inline]
pub(crate) fn check_dynamic_type(&self, call_ctx: &CallContext<'static>) {
assert!(
!self.is_null(),
"internal bug: {call_ctx}: cannot call method on null object",
);
let rtti = self.cached_rtti.as_ref();
let rtti = unsafe { rtti.unwrap_unchecked() };
rtti.check_type::<T>();
}
#[doc(hidden)]
#[inline]
pub fn validated_object(&self) -> ValidatedObject {
ValidatedObject::validate(self)
}
pub(crate) fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
self.obj as sys::GDExtensionObjectPtr
}
pub(super) fn script_sys(&self) -> sys::GDExtensionScriptLanguagePtr
where
T: super::Inherits<crate::classes::ScriptLanguage>,
{
self.obj.cast()
}
}
impl<T> RawGd<T>
where
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
{
pub(crate) fn bind(&self) -> GdRef<'_, T> {
self.check_rtti("bind");
GdRef::from_guard(self.storage().unwrap().get())
}
pub(crate) fn bind_mut(&mut self) -> GdMut<'_, T> {
self.check_rtti("bind_mut");
GdMut::from_guard(self.storage().unwrap().get_mut())
}
pub(crate) fn storage(&self) -> Option<&InstanceStorage<T>> {
unsafe { self.storage_unbounded() }
}
pub(crate) unsafe fn storage_unbounded<'b>(&self) -> Option<&'b InstanceStorage<T>> {
unsafe {
let binding = self.resolve_instance_ptr();
sys::ptr_then(binding, |binding| crate::private::as_storage::<T>(binding))
}
}
fn resolve_instance_ptr(&self) -> sys::GDExtensionClassInstancePtr {
if self.is_null() {
return ptr::null_mut();
}
let cached = self.cached_storage_ptr.get();
if !cached.is_null() {
return cached;
}
let callbacks = crate::storage::nop_instance_callbacks();
let token = unsafe { sys::get_library() };
let token = token.cast::<std::ffi::c_void>();
let binding = unsafe {
interface_fn!(object_get_instance_binding)(self.obj_sys(), token, &callbacks)
};
let ptr: sys::GDExtensionClassInstancePtr = binding.cast();
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
crate::classes::ensure_binding_not_null::<T>(ptr);
self.cached_storage_ptr.set(ptr);
ptr
}
}
unsafe impl<T> GodotFfi for RawGd<T>
where
T: GodotClass,
{
const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::OBJECT);
#[allow(unsafe_op_in_unsafe_fn)] unsafe fn new_from_sys(ptr: sys::GDExtensionConstTypePtr) -> Self {
Self::from_obj_sys_weak(ptr as sys::GDExtensionObjectPtr)
}
#[allow(unsafe_op_in_unsafe_fn)] unsafe fn new_with_uninit(init: impl FnOnce(sys::GDExtensionUninitializedTypePtr)) -> Self {
let obj = raw_object_init(init);
Self::from_obj_sys_weak(obj)
}
#[allow(unsafe_op_in_unsafe_fn)] unsafe fn new_with_init(init: impl FnOnce(sys::GDExtensionTypePtr)) -> Self {
Self::new_with_uninit(|return_ptr| init(sys::SysPtr::force_init(return_ptr)))
}
fn sys(&self) -> sys::GDExtensionConstTypePtr {
self.obj.cast()
}
fn sys_mut(&mut self) -> sys::GDExtensionTypePtr {
self.obj.cast()
}
fn as_arg_ptr(&self) -> sys::GDExtensionConstTypePtr {
object_as_arg_ptr(&self.obj)
}
unsafe fn from_arg_ptr(ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) -> Self {
if ptr.is_null() {
return Self::null();
}
let obj_ptr = if T::DynMemory::pass_as_ref(call_type) {
unsafe { interface_fn!(ref_get_object)(ptr as sys::GDExtensionRefPtr) }
} else {
unsafe { *(ptr as *mut sys::GDExtensionObjectPtr) }
};
unsafe { Self::from_obj_sys(obj_ptr) }
}
unsafe fn move_return_ptr(self, ptr: sys::GDExtensionTypePtr, call_type: PtrcallType) {
if T::DynMemory::pass_as_ref(call_type) {
unsafe { interface_fn!(ref_set_object)(ptr as sys::GDExtensionRefPtr, self.obj_sys()) };
} else {
unsafe { ptr::write(ptr as *mut *mut T, self.obj) };
std::mem::forget(self);
}
}
}
impl<T: GodotClass> GodotConvert for RawGd<T> {
type Via = Self;
fn godot_shape() -> GodotShape {
Gd::<T>::godot_shape()
}
}
impl<T: GodotClass> ToGodot for RawGd<T> {
type Pass = meta::ByRef;
fn to_godot(&self) -> &Self::Via {
self
}
}
impl<T: GodotClass> FromGodot for RawGd<T> {
fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
Ok(via)
}
}
impl<T: GodotClass> GodotType for RawGd<T> {
type Ffi = Self;
type ToFfi<'f>
= RefArg<'f, RawGd<T>>
where
Self: 'f;
fn to_ffi(&self) -> Self::ToFfi<'_> {
RefArg::new(self)
}
fn into_ffi(self) -> Self::Ffi {
self
}
fn try_from_ffi(ffi: Self::Ffi) -> Result<Self, ConvertError> {
Ok(ffi)
}
}
impl<T: GodotClass> GodotFfiVariant for RawGd<T> {
fn ffi_to_variant(&self) -> Variant {
object_ffi_to_variant(self)
}
fn ffi_from_variant(variant: &Variant) -> Result<Self, ConvertError> {
let variant_type = variant.get_type();
if variant_type != VariantType::OBJECT {
return Err(FromVariantError::BadType {
expected: VariantType::OBJECT,
actual: variant_type,
}
.into_error(variant.clone()));
}
if !variant.is_object_alive() {
return Err(FromVariantError::DeadObject.into_error(variant.clone()));
}
let raw = unsafe {
RawGd::<classes::Object>::new_with_uninit(|self_ptr| {
let converter = sys::builtin_fn!(object_from_variant);
converter(self_ptr, sys::SysPtr::force_mut(variant.var_sys()));
})
};
raw.with_inc_refcount().owned_cast().map_err(|raw| {
FromVariantError::WrongClass {
expected: T::class_id(),
}
.into_error(raw)
})
}
}
impl<T: GodotClass> Drop for RawGd<T> {
fn drop(&mut self) {
out!("RawGd::drop: {self:?}");
let is_last = unsafe { T::DynMemory::maybe_dec_ref(self) }; if is_last {
unsafe {
interface_fn!(object_destroy)(self.obj_sys());
}
}
}
}
impl<T: GodotClass> Clone for RawGd<T> {
fn clone(&self) -> Self {
out!("RawGd::clone: {self:?} (before clone)");
let cloned = if self.is_null() {
Self::null()
} else {
self.check_rtti("clone");
let copy = Self {
obj: self.obj,
cached_rtti: self.cached_rtti.clone(),
cached_storage_ptr: self.cached_storage_ptr.clone(),
};
copy.with_inc_refcount()
};
out!(" {self:?} (after clone)");
cloned
}
}
impl<T: GodotClass> fmt::Debug for RawGd<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
classes::debug_string_nullable(self, f, "RawGd")
}
}
#[doc(hidden)]
pub struct ValidatedObject {
object_ptr: sys::GDExtensionObjectPtr,
}
impl ValidatedObject {
#[doc(hidden)]
#[inline]
pub fn validate<T: GodotClass>(raw_gd: &RawGd<T>) -> Self {
raw_gd.check_rtti("validated_object");
Self {
object_ptr: raw_gd.obj_sys(),
}
}
#[inline]
pub fn object_ptr(opt: Option<&Self>) -> sys::GDExtensionObjectPtr {
opt.map(|v| v.object_ptr).unwrap_or(ptr::null_mut())
}
}
#[doc(hidden)]
pub unsafe fn raw_object_init(
init_fn: impl FnOnce(sys::GDExtensionUninitializedTypePtr),
) -> sys::GDExtensionObjectPtr {
let mut object_ptr: sys::GDExtensionObjectPtr = ptr::null_mut();
let return_ptr: *mut sys::GDExtensionObjectPtr = ptr::addr_of_mut!(object_ptr);
init_fn(return_ptr as sys::GDExtensionUninitializedTypePtr);
object_ptr
}
pub(crate) fn object_ffi_to_variant<T: GodotFfi>(self_: &T) -> Variant {
unsafe {
Variant::new_with_var_uninit(|variant_ptr| {
let converter = sys::builtin_fn!(object_to_variant);
let type_ptr = self_.sys();
converter(
variant_ptr,
ptr::addr_of!(type_ptr) as sys::GDExtensionTypePtr,
);
})
}
}
pub(crate) fn object_as_arg_ptr<F>(_object_ptr_field: &*mut F) -> sys::GDExtensionConstTypePtr {
ptr::addr_of!(*_object_ptr_field) as sys::GDExtensionConstTypePtr
}