use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ops::{Deref, DerefMut};
use godot_ffi as sys;
use godot_ffi::is_main_thread;
use sys::{SysPtr as _, static_assert_eq_size_align};
use crate::builtin::{Callable, NodePath, StringName, Variant};
use crate::meta::error::{ConvertError, FromFfiError};
use crate::meta::shape::GodotShape;
use crate::meta::{
AsArg, ClassId, Element, FromGodot, GodotConvert, GodotNullableType, GodotType, RefArg, ToGodot,
};
use crate::obj::{
Bounds, DynGd, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits, InstanceId, OnEditor, RawGd,
WithBaseField, WithSignals, bounds, cap,
};
use crate::private::{PanicPayload, callbacks};
use crate::registry::class::try_dynify_object;
use crate::registry::info::PropertyHintInfo;
use crate::registry::property::{Export, SimpleVar, Var};
use crate::{classes, meta, out};
#[repr(C)] pub struct Gd<T: GodotClass> {
pub(crate) raw: RawGd<T>,
}
static_assert_eq_size_align!(
sys::GDExtensionObjectPtr,
sys::types::OpaqueObject,
"Godot FFI: pointer type `Object*` should have size advertised in JSON extension file"
);
impl<T> Gd<T>
where
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
{
pub fn from_init_fn<F>(init: F) -> Self
where
F: FnOnce(crate::obj::Base<T::Base>) -> T,
{
let object_ptr = callbacks::create_custom(init, true) .unwrap_or_else(|payload| PanicPayload::repanic(payload));
unsafe { Gd::from_obj_sys(object_ptr) }
}
pub fn from_object(user_object: T) -> Self {
Self::from_init_fn(move |_base| user_object)
}
pub fn bind(&self) -> GdRef<'_, T> {
self.raw.bind()
}
pub fn bind_mut(&mut self) -> GdMut<'_, T> {
self.raw.bind_mut()
}
}
impl<T: GodotClass> Gd<T> {
pub fn try_from_instance_id(instance_id: InstanceId) -> Result<Self, ConvertError> {
let ptr = classes::object_ptr_from_id(instance_id);
let untyped = unsafe { Gd::<classes::Object>::from_obj_sys_or_none(ptr)? };
untyped
.owned_cast::<T>()
.map_err(|obj| FromFfiError::WrongObjectType.into_error(obj))
}
#[doc(alias = "instance_from_id")]
pub fn from_instance_id(instance_id: InstanceId) -> Self {
Self::try_from_instance_id(instance_id).unwrap_or_else(|err| {
panic!(
"Instance ID {} does not belong to a valid object of class '{}': {}",
instance_id,
T::class_id(),
err
)
})
}
pub(crate) fn instance_id_or_none(&self) -> Option<InstanceId> {
let known_id = self.instance_id_unchecked();
if self.raw.is_instance_valid() {
Some(known_id)
} else {
None
}
}
pub fn instance_id(&self) -> InstanceId {
self.instance_id_or_none().unwrap_or_else(|| {
panic!(
"failed to call instance_id() on destroyed object; \
use instance_id_or_none() or keep your objects alive"
)
})
}
pub fn instance_id_unchecked(&self) -> InstanceId {
let instance_id = self.raw.instance_id_unchecked();
unsafe { instance_id.unwrap_unchecked() }
}
pub fn is_instance_valid(&self) -> bool {
self.raw.is_instance_valid()
}
pub(crate) fn dynamic_class_string(&self) -> StringName {
unsafe {
StringName::new_with_string_uninit(|ptr| {
let success = sys::interface_fn!(object_get_class_name)(
self.obj_sys().as_const(),
sys::get_library(),
ptr,
);
let success = sys::conv::bool_from_sys(success);
assert!(success, "failed to get class name for object {self:?}");
})
}
}
pub(crate) fn maybe_refcount(&self) -> Option<Result<usize, ()>> {
if !self.instance_id().is_ref_counted() {
return None;
}
let rc = self
.raw
.try_with_ref_counted(|refc| refc.get_reference_count());
Some(rc.map(|i| i as usize))
}
pub(crate) unsafe fn clone_weak(&self) -> Self {
unsafe { Gd::from_obj_sys_weak(self.obj_sys()) }
}
pub(crate) fn drop_weak(self) {
std::mem::forget(self);
}
#[cfg(feature = "trace")] #[doc(hidden)]
pub fn test_refcount(&self) -> Option<usize> {
self.maybe_refcount()
.transpose()
.expect("failed to obtain refcount")
}
pub fn upcast<Base>(self) -> Gd<Base>
where
Base: GodotClass,
T: Inherits<Base>,
{
self.owned_cast()
.expect("Upcast failed. This is a bug; please report it.")
}
#[doc(hidden)] pub fn upcast_object(self) -> Gd<classes::Object> {
self.owned_cast()
.expect("Upcast to Object failed. This is a bug; please report it.")
}
pub(crate) fn upcast_object_mut(&mut self) -> &mut classes::Object {
self.raw.as_object_mut()
}
pub fn upcast_ref<Base>(&self) -> &Base
where
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
T: Inherits<Base>,
{
unsafe { self.raw.as_upcast_ref::<Base>() }
}
pub fn upcast_mut<Base>(&mut self) -> &mut Base
where
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
T: Inherits<Base>,
{
unsafe { self.raw.as_upcast_mut::<Base>() }
}
pub fn try_cast<Derived>(self) -> Result<Gd<Derived>, Self>
where
Derived: Inherits<T>,
{
self.owned_cast()
}
pub fn cast<Derived>(self) -> Gd<Derived>
where
Derived: Inherits<T>,
{
self.owned_cast().unwrap_or_else(|from_obj| {
panic!(
"downcast from {from} to {to} failed; instance {from_obj:?}",
from = T::class_id(),
to = Derived::class_id(),
)
})
}
pub(crate) fn owned_cast<U>(self) -> Result<Gd<U>, Self>
where
U: GodotClass,
{
self.raw
.owned_cast()
.map(Gd::from_ffi)
.map_err(Self::from_ffi)
}
pub(crate) fn default_instance() -> Self
where
T: cap::GodotDefault,
{
unsafe {
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
let object_ptr = callbacks::create::<T>(std::ptr::null_mut(), sys::conv::SYS_TRUE);
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
let object_ptr = callbacks::create::<T>(std::ptr::null_mut());
Gd::from_obj_sys(object_ptr)
}
}
#[must_use]
pub fn into_dyn<D>(self) -> DynGd<T, D>
where
T: crate::obj::AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
D: ?Sized + 'static,
{
DynGd::<T, D>::from_gd(self)
}
pub fn try_dynify<D>(self) -> Result<DynGd<T, D>, Self>
where
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
D: ?Sized + 'static,
{
match try_dynify_object(self) {
Ok(dyn_gd) => Ok(dyn_gd),
Err((_convert_err, obj)) => Err(obj),
}
}
pub fn callable(&self, method_name: impl AsArg<StringName>) -> Callable {
Callable::from_object_method(self, method_name)
}
pub fn linked_callable<R, F>(
&self,
method_name: impl Into<crate::builtin::CowStr>,
rust_function: F,
) -> Callable
where
R: ToGodot,
F: 'static + FnMut(&[&Variant]) -> R,
{
Callable::from_linked_fn(method_name, self, rust_function)
}
pub(crate) unsafe fn from_obj_sys_or_none(
ptr: sys::GDExtensionObjectPtr,
) -> Result<Self, ConvertError> {
unsafe {
let obj = RawGd::from_obj_sys(ptr);
Self::try_from_ffi(obj)
}
}
pub(crate) unsafe fn from_obj_sys(ptr: sys::GDExtensionObjectPtr) -> Self {
sys::strict_assert!(
!ptr.is_null(),
"Gd::from_obj_sys() called with null pointer"
);
unsafe { Self::from_obj_sys_or_none(ptr) }.unwrap()
}
pub(crate) unsafe fn from_obj_sys_weak_or_none(
ptr: sys::GDExtensionObjectPtr,
) -> Result<Self, ConvertError> {
unsafe { Self::try_from_ffi(RawGd::from_obj_sys_weak(ptr)) }
}
pub(crate) unsafe fn from_obj_sys_weak(ptr: sys::GDExtensionObjectPtr) -> Self {
unsafe { Self::from_obj_sys_weak_or_none(ptr).unwrap() }
}
#[cfg(feature = "trace")] #[doc(hidden)]
pub unsafe fn __from_obj_sys_weak(ptr: sys::GDExtensionObjectPtr) -> Self {
unsafe { Self::from_obj_sys_weak(ptr) }
}
#[doc(hidden)]
pub fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
self.raw.obj_sys()
}
#[doc(hidden)]
pub fn script_sys(&self) -> sys::GDExtensionScriptLanguagePtr
where
T: Inherits<classes::ScriptLanguage>,
{
self.raw.script_sys()
}
#[doc(hidden)]
pub unsafe fn from_sys_init_opt(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Option<Self> {
let init_fn = |ptr| {
init_fn(sys::SysPtr::force_init(ptr));
};
let object_ptr = unsafe { super::raw_object_init(init_fn) };
sys::ptr_then(object_ptr, |ptr| unsafe { Gd::from_obj_sys_weak(ptr) })
}
pub fn run_deferred<F>(&mut self, mut_self_method: F)
where
T: WithBaseField,
F: FnOnce(&mut T) + 'static,
{
self.run_deferred_gd(move |mut gd| {
let mut guard = gd.bind_mut();
mut_self_method(&mut *guard);
});
}
pub fn run_deferred_gd<F>(&mut self, gd_function: F)
where
F: FnOnce(Gd<T>) + 'static,
{
let obj = self.clone();
assert!(
is_main_thread(),
"`run_deferred` must be called on the main thread"
);
let callable = Callable::from_once_fn("run_deferred", move |_| {
gd_function(obj);
});
callable.call_deferred(&[]);
}
}
impl<T> Gd<T>
where
T: GodotClass + Bounds<Memory = bounds::MemManual>,
{
pub fn free(self) {
let is_panic_unwind = std::thread::panicking();
let error_or_panic = |msg: String| {
if is_panic_unwind {
if crate::private::has_error_print_level(1) {
crate::godot_error!(
"Encountered 2nd panic in free() during panic unwind; will skip destruction:\n{msg}"
);
}
} else {
panic!("{}", msg);
}
};
use bounds::Declarer;
let ref_counted =
<<T as Bounds>::DynMemory as bounds::DynMemory>::is_ref_counted(&self.raw);
if ref_counted == Some(true) {
return error_or_panic(format!(
"Called free() on Gd<Object> which points to a RefCounted dynamic type; free() only supported for manually managed types\n\
Object: {self:?}"
));
}
if ref_counted != Some(false) || (cfg!(safeguards_balanced) && !self.is_instance_valid()) {
return error_or_panic("called free() on already destroyed object".to_string());
}
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
if !is_panic_unwind {
self.raw
.check_dynamic_type(&crate::meta::CallContext::gd::<T>("free"));
}
let bound = unsafe { T::Declarer::is_currently_bound(&self.raw) };
if bound {
return error_or_panic(
"called free() while a bind() or bind_mut() call is active".to_string(),
);
}
unsafe {
sys::interface_fn!(object_destroy)(self.raw.obj_sys());
}
self.drop_weak()
}
}
impl<T> Gd<T>
where
T: GodotClass + Bounds<Memory = bounds::MemRefCounted>,
{
pub fn try_to_unique(self) -> Result<Self, (Self, usize)> {
use crate::obj::bounds::DynMemory as _;
match <T as Bounds>::DynMemory::get_ref_count(&self.raw) {
Some(1) => Ok(self),
Some(ref_count) => Err((self, ref_count)),
None => unreachable!(),
}
}
}
impl<T> Gd<T>
where
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
{
pub fn null_arg() -> impl AsArg<Option<Gd<T>>> {
meta::NullArg(std::marker::PhantomData)
}
}
impl<T> Gd<T>
where
T: WithSignals,
{
pub fn signals(&self) -> T::SignalCollection<'_, T> {
T::__signals_from_external(self)
}
}
impl<T: GodotClass> Deref for Gd<T>
where
GdDerefTarget<T>: Bounds<Declarer = bounds::DeclEngine>,
{
type Target = GdDerefTarget<T>;
fn deref(&self) -> &Self::Target {
self.raw.as_target()
}
}
impl<T: GodotClass> DerefMut for Gd<T>
where
GdDerefTarget<T>: Bounds<Declarer = bounds::DeclEngine>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.raw.as_target_mut()
}
}
impl<T: GodotClass> GodotConvert for Gd<T> {
type Via = Gd<T>;
fn godot_shape() -> GodotShape {
use crate::meta::shape::ClassHeritage;
let heritage = if T::inherits::<classes::Resource>() {
ClassHeritage::Resource
} else if T::inherits::<classes::Node>() {
ClassHeritage::Node
} else {
ClassHeritage::Other
};
let class_id = T::class_id();
GodotShape::Class {
class_id,
heritage,
is_nullable: false,
}
}
}
impl<T: GodotClass> ToGodot for Gd<T> {
type Pass = meta::ByObject;
fn to_godot(&self) -> &Self {
self.raw.check_rtti("to_godot");
self
}
}
impl<T: GodotClass> FromGodot for Gd<T> {
fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
Ok(via)
}
}
impl<T: GodotClass> GodotType for Gd<T> {
type Ffi = RawGd<T>;
type ToFfi<'f>
= RefArg<'f, RawGd<T>>
where
Self: 'f;
#[doc(hidden)]
fn to_ffi(&self) -> Self::ToFfi<'_> {
RefArg::new(&self.raw)
}
#[doc(hidden)]
fn into_ffi(self) -> Self::Ffi {
self.raw
}
fn try_from_ffi(raw: Self::Ffi) -> Result<Self, ConvertError> {
if raw.is_null() {
Err(FromFfiError::NullRawGd.into_error(raw))
} else {
Ok(Self { raw })
}
}
fn qualifies_as_special_none(from_variant: &Variant) -> bool {
if let Ok(node_path) = from_variant.try_to::<NodePath>()
&& node_path.is_empty()
{
return true;
}
false
}
fn as_object_arg(&self) -> meta::ObjectArg<'_> {
meta::ObjectArg::from_gd(self)
}
}
impl<T: GodotClass> Element for Gd<T> {}
impl<T: GodotClass> GodotNullableType for Gd<T> {
fn ffi_null() -> RawGd<T> {
RawGd::null()
}
fn ffi_null_ref<'f>() -> RefArg<'f, RawGd<T>>
where
Self: 'f,
{
RefArg::null_ref()
}
fn ffi_is_null(ffi: &RawGd<T>) -> bool {
ffi.is_null()
}
}
impl<T: GodotClass> Element for Option<Gd<T>> {}
impl<T> Default for Gd<T>
where
T: cap::GodotDefault + Bounds<Memory = bounds::MemRefCounted>,
{
fn default() -> Self {
T::__godot_default()
}
}
impl<T: GodotClass> Clone for Gd<T> {
fn clone(&self) -> Self {
out!("Gd::clone");
Self {
raw: self.raw.clone(),
}
}
}
impl<T: GodotClass> SimpleVar for Gd<T> {}
impl<T> Export for Option<Gd<T>>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
Option<Gd<T>>: Var,
{
#[doc(hidden)]
fn as_node_class() -> Option<ClassId> {
PropertyHintInfo::object_as_node_class::<T>()
}
}
impl<T: GodotClass> Default for OnEditor<Gd<T>> {
fn default() -> Self {
OnEditor::gd_invalid()
}
}
impl<T> GodotConvert for OnEditor<Gd<T>>
where
T: GodotClass,
Option<<Gd<T> as GodotConvert>::Via>: GodotType,
{
type Via = Option<<Gd<T> as GodotConvert>::Via>;
fn godot_shape() -> GodotShape {
Gd::<T>::godot_shape()
}
}
impl<T> Var for OnEditor<Gd<T>>
where
T: GodotClass,
{
type PubType = <Gd<T> as GodotConvert>::Via;
fn var_get(field: &Self) -> Self::Via {
Self::get_property_inner(field)
}
fn var_set(field: &mut Self, value: Self::Via) {
Self::set_property_inner(field, value);
}
fn var_pub_get(field: &Self) -> Self::PubType {
Self::var_get(field).expect("generated #[var(pub)] getter: uninitialized OnEditor<Gd<T>>")
}
fn var_pub_set(field: &mut Self, value: Self::PubType) {
Self::var_set(field, Some(value))
}
}
impl<T> Export for OnEditor<Gd<T>>
where
Self: Var,
T: GodotClass + Bounds<Exportable = bounds::Yes>,
{
#[doc(hidden)]
fn as_node_class() -> Option<ClassId> {
PropertyHintInfo::object_as_node_class::<T>()
}
}
impl<T: GodotClass> PartialEq for Gd<T> {
fn eq(&self, other: &Self) -> bool {
self.instance_id() == other.instance_id()
}
}
impl<T: GodotClass> Eq for Gd<T> {}
impl<T: GodotClass> Display for Gd<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
classes::display_string(self, f)
}
}
impl<T: GodotClass> Debug for Gd<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
classes::debug_string(self, f, "Gd")
}
}
impl<T: GodotClass> std::hash::Hash for Gd<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.instance_id().hash(state);
}
}
impl<T: GodotClass> std::panic::UnwindSafe for Gd<T> {}
impl<T: GodotClass> std::panic::RefUnwindSafe for Gd<T> {}