use std::{fmt, ops};
use godot_ffi::is_main_thread;
use crate::builtin::{Callable, Variant};
use crate::meta::error::ConvertError;
use crate::meta::shape::GodotShape;
use crate::meta::{ClassId, FromGodot, GodotConvert, ToGodot};
use crate::obj::guards::DynGdRef;
use crate::obj::{AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits, OnEditor, bounds};
use crate::registry::class::{get_dyn_implementor_class_ids, try_dynify_object};
use crate::registry::info::PropertyHintInfo;
use crate::registry::property::{Export, Var};
use crate::{meta, sys};
pub struct DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
obj: Gd<T>,
erased_obj: Box<dyn ErasedGd<D>>,
}
impl<T, D> DynGd<T, D>
where
T: AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
D: ?Sized + 'static,
{
pub(crate) fn from_gd(gd_instance: Gd<T>) -> Self {
let erased_obj = Box::new(gd_instance.clone());
Self {
obj: gd_instance,
erased_obj,
}
}
}
impl<T, D> DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
pub fn dyn_bind(&self) -> DynGdRef<'_, D> {
self.erased_obj.dyn_bind()
}
pub fn dyn_bind_mut(&mut self) -> DynGdMut<'_, D> {
self.erased_obj.dyn_bind_mut()
}
pub fn upcast<Base>(self) -> DynGd<Base, D>
where
Base: GodotClass,
T: Inherits<Base>,
{
DynGd {
obj: self.obj.upcast::<Base>(),
erased_obj: self.erased_obj,
}
}
pub fn try_cast<Derived>(self) -> Result<DynGd<Derived, D>, Self>
where
Derived: Inherits<T>,
{
match self.obj.try_cast::<Derived>() {
Ok(obj) => Ok(DynGd {
obj,
erased_obj: self.erased_obj,
}),
Err(obj) => Err(DynGd {
obj,
erased_obj: self.erased_obj,
}),
}
}
pub fn cast<Derived>(self) -> DynGd<Derived, D>
where
Derived: Inherits<T>,
{
self.try_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) unsafe fn cast_unchecked<Derived>(self) -> DynGd<Derived, D>
where
Derived: GodotClass,
{
let cast_obj = self.obj.owned_cast::<Derived>();
let cast_obj = unsafe { cast_obj.unwrap_unchecked() };
DynGd {
obj: cast_obj,
erased_obj: self.erased_obj,
}
}
#[must_use]
pub fn into_gd(self) -> Gd<T> {
self.obj
}
pub fn null_arg() -> impl meta::AsArg<Option<DynGd<T, D>>> {
meta::NullArg(std::marker::PhantomData)
}
pub fn run_deferred<F>(&mut self, mut_self_method: F)
where
F: FnOnce(&mut D) + 'static,
{
self.run_deferred_gd(|mut gd| {
let mut guard = gd.dyn_bind_mut();
mut_self_method(&mut *guard);
});
}
pub fn run_deferred_gd<F>(&mut self, gd_function: F)
where
F: FnOnce(DynGd<T, D>) + '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, D> DynGd<T, D>
where
T: GodotClass + Bounds<Memory = bounds::MemManual>,
D: ?Sized + 'static,
{
pub fn free(self) {
self.obj.free()
}
}
impl<T, D> Clone for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
fn clone(&self) -> Self {
Self {
obj: self.obj.clone(),
erased_obj: self.erased_obj.clone_box(),
}
}
}
impl<T, D> PartialEq for DynGd<T, D>
where
T: GodotClass,
D: ?Sized,
{
fn eq(&self, other: &Self) -> bool {
self.obj == other.obj
}
}
impl<T, D> Eq for DynGd<T, D>
where
T: GodotClass,
D: ?Sized,
{
}
impl<T, D> std::hash::Hash for DynGd<T, D>
where
T: GodotClass,
D: ?Sized,
{
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.obj.hash(state);
}
}
impl<T, D> ops::Deref for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
type Target = Gd<T>;
fn deref(&self) -> &Self::Target {
&self.obj
}
}
impl<T, D> ops::DerefMut for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.obj
}
}
impl<T, D> fmt::Debug for DynGd<T, D>
where
T: GodotClass,
D: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let trt = sys::short_type_name::<D>();
crate::classes::debug_string_with_trait::<T>(self, f, "DynGd", &trt)
}
}
impl<T, D> fmt::Display for DynGd<T, D>
where
T: GodotClass,
D: ?Sized,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::classes::display_string(self, f)
}
}
trait ErasedGd<D>
where
D: ?Sized + 'static,
{
fn dyn_bind(&self) -> DynGdRef<'_, D>;
fn dyn_bind_mut(&mut self) -> DynGdMut<'_, D>;
fn clone_box(&self) -> Box<dyn ErasedGd<D>>;
}
impl<T, D> ErasedGd<D> for Gd<T>
where
T: AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
D: ?Sized + 'static,
{
fn dyn_bind(&self) -> DynGdRef<'_, D> {
DynGdRef::from_guard::<T>(Gd::bind(self))
}
fn dyn_bind_mut(&mut self) -> DynGdMut<'_, D> {
DynGdMut::from_guard::<T>(Gd::bind_mut(self))
}
fn clone_box(&self) -> Box<dyn ErasedGd<D>> {
Box::new(Gd::clone(self))
}
}
impl<T, D> GodotConvert for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
type Via = Gd<T>;
fn godot_shape() -> GodotShape {
use crate::classes;
use crate::meta::shape::ClassHeritage;
let heritage = if T::inherits::<classes::Resource>() {
ClassHeritage::DynResource {
implementors: get_dyn_implementor_class_ids::<T, D>(),
}
} 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, D> ToGodot for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
type Pass = <Gd<T> as ToGodot>::Pass;
fn to_godot(&self) -> &Self::Via {
self.obj.to_godot()
}
fn to_variant(&self) -> Variant {
self.obj.to_variant()
}
}
impl<T, D> FromGodot for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
match try_dynify_object(via) {
Ok(dyn_gd) => Ok(dyn_gd),
Err((from_godot_err, obj)) => Err(from_godot_err.into_error(obj)),
}
}
}
impl<T, D> meta::Element for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
}
impl<T, D> meta::Element for Option<DynGd<T, D>>
where
T: GodotClass,
D: ?Sized + 'static,
{
}
impl<T, D> Var for DynGd<T, D>
where
T: GodotClass,
D: ?Sized + 'static,
{
type PubType = Self;
fn var_get(field: &Self) -> Self::Via {
<Gd<T> as Var>::var_get(&field.obj)
}
fn var_set(field: &mut Self, value: Self::Via) {
*field = <Self as FromGodot>::from_godot(value);
}
fn var_pub_get(field: &Self) -> Self::PubType {
field.clone()
}
fn var_pub_set(field: &mut Self, value: Self::PubType) {
*field = value;
}
}
impl<T, D> Export for Option<DynGd<T, D>>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
D: ?Sized + 'static,
{
#[doc(hidden)]
fn as_node_class() -> Option<ClassId> {
PropertyHintInfo::object_as_node_class::<T>()
}
}
impl<T, D> Default for OnEditor<DynGd<T, D>>
where
T: GodotClass,
D: ?Sized + 'static,
{
fn default() -> Self {
OnEditor::gd_invalid()
}
}
impl<T, D> GodotConvert for OnEditor<DynGd<T, D>>
where
T: GodotClass,
D: ?Sized + 'static,
{
type Via = Option<<DynGd<T, D> as GodotConvert>::Via>;
fn godot_shape() -> GodotShape {
DynGd::<T, D>::godot_shape()
}
}
impl<T, D> Var for OnEditor<DynGd<T, D>>
where
T: GodotClass,
D: ?Sized + 'static,
{
type PubType = <DynGd<T, D> 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<DynGd<T, D>>")
}
fn var_pub_set(field: &mut Self, value: Self::PubType) {
Self::var_set(field, Some(value))
}
}
impl<T, D> Export for OnEditor<DynGd<T, D>>
where
Self: Var,
T: GodotClass + Bounds<Exportable = bounds::Yes>,
D: ?Sized + 'static,
{
#[doc(hidden)]
fn as_node_class() -> Option<ClassId> {
PropertyHintInfo::object_as_node_class::<T>()
}
}