Skip to content

Add OnEditor<T>, remove impl<T> Export for Gd<T> and DynGd<T, D> #1051

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

Merged
merged 4 commits into from
Mar 12, 2025
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
43 changes: 39 additions & 4 deletions godot-core/src/builtin/collections/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use crate::builtin::*;
use crate::meta;
use crate::meta::error::{ConvertError, FromGodotError, FromVariantError};
use crate::meta::{
element_godot_type_name, element_variant_type, ArrayElement, ArrayTypeInfo, AsArg, CowArg,
FromGodot, GodotConvert, GodotFfiVariant, GodotType, ParamType, PropertyHintInfo, RefArg,
ToGodot,
element_godot_type_name, element_variant_type, ArrayElement, ArrayTypeInfo, AsArg, ClassName,
CowArg, FromGodot, GodotConvert, GodotFfiVariant, GodotType, ParamType, PropertyHintInfo,
RefArg, ToGodot,
};
use crate::registry::property::{Export, Var};
use crate::obj::{bounds, Bounds, DynGd, Gd, GodotClass};
use crate::registry::property::{BuiltinExport, Export, Var};
use godot_ffi as sys;
use sys::{ffi_methods, interface_fn, GodotFfi};

Expand Down Expand Up @@ -1099,6 +1100,40 @@ where
}
}

impl<T: ArrayElement> BuiltinExport for Array<T> {}

impl<T> Export for Array<Gd<T>>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
{
fn export_hint() -> PropertyHintInfo {
PropertyHintInfo::export_array_element::<Gd<T>>()
}

#[doc(hidden)]
fn as_node_class() -> Option<ClassName> {
PropertyHintInfo::object_as_node_class::<T>()
}
Comment on lines +1114 to +1116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be #[doc(hidden)] everywhere. Unfortunately rustdoc doesn't seem to be smart enough to pick up the attribute from the trait and apply it to all impls.

}

/// `#[export]` for `Array<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
///
/// Consider exporting `Array<Gd<T>>` instead of `Array<DynGd<T, D>>` for user-declared GDExtension classes.
impl<T: GodotClass, D> Export for Array<DynGd<T, D>>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
D: ?Sized + 'static,
{
fn export_hint() -> PropertyHintInfo {
PropertyHintInfo::export_array_element::<DynGd<T, D>>()
}

#[doc(hidden)]
fn as_node_class() -> Option<ClassName> {
PropertyHintInfo::object_as_node_class::<T>()
}
}

impl<T: ArrayElement> Default for Array<T> {
#[inline]
fn default() -> Self {
Expand Down
43 changes: 41 additions & 2 deletions godot-core/src/meta/property_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ use crate::global::{PropertyHint, PropertyUsageFlags};
use crate::meta::{
element_godot_type_name, ArrayElement, ClassName, GodotType, PackedArrayElement,
};
use crate::obj::{EngineBitfield, EngineEnum};
use crate::obj::{bounds, Bounds, EngineBitfield, EngineEnum, GodotClass};
use crate::registry::class::get_dyn_property_hint_string;
use crate::registry::property::{Export, Var};
use crate::sys;
use crate::{classes, sys};
use godot_ffi::VariantType;

/// Describes a property in Godot.
Expand Down Expand Up @@ -302,4 +303,42 @@ impl PropertyHintInfo {
hint_string: GString::from(T::element_type_string()),
}
}

pub fn export_gd<T>() -> Self
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
{
let hint = if T::inherits::<classes::Resource>() {
PropertyHint::RESOURCE_TYPE
} else if T::inherits::<classes::Node>() {
PropertyHint::NODE_TYPE
} else {
unreachable!("classes not inheriting from Resource or Node should not be exportable")
};

// Godot does this by default too; the hint is needed when the class is a resource/node,
// but doesn't seem to make a difference otherwise.
let hint_string = T::class_name().to_gstring();

Self { hint, hint_string }
}

pub fn export_dyn_gd<T, D>() -> Self
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
D: ?Sized + 'static,
{
PropertyHintInfo {
hint_string: GString::from(get_dyn_property_hint_string::<T, D>()),
..PropertyHintInfo::export_gd::<T>()
}
}

#[doc(hidden)]
pub fn object_as_node_class<T>() -> Option<ClassName>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
{
T::inherits::<classes::Node>().then(|| T::class_name())
}
}
89 changes: 81 additions & 8 deletions godot-core/src/obj/dyn_gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::builtin::{GString, Variant};
use crate::builtin::Variant;
use crate::meta::error::ConvertError;
use crate::meta::{ClassName, FromGodot, GodotConvert, PropertyHintInfo, ToGodot};
use crate::obj::guards::DynGdRef;
use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits};
use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits, OnEditor};
use crate::registry::class::{get_dyn_property_hint_string, try_dynify_object};
use crate::registry::property::{object_export_element_type_string, Export, Var};
use crate::{meta, sys};
Expand Down Expand Up @@ -136,6 +136,25 @@ use std::{fmt, ops};
/// godot-rust achieves this thanks to the registration done by `#[godot_dyn]`: the library knows for which classes `Health` is implemented,
/// and it can query the dynamic type of the object. Based on that type, it can find the `impl Health` implementation matching the correct class.
/// Behind the scenes, everything is wired up correctly so that you can restore the original `DynGd` even after it has passed through Godot.
///
/// # `#[export]` for `DynGd<T, D>`
///
/// Exporting `DynGd<T, D>` is possible only via [`OnEditor`] or [`Option`].
/// `DynGd<T, D>` can also be exported directly as an element of an array such as `Array<DynGd<T, D>>`.
///
/// Since `DynGd<T, D>` represents shared functionality `D` across classes inheriting from `T`,
/// consider using `#[export] Gd<T>` instead of `#[export] DynGd<T, D>`
/// in cases when `T` is a concrete Rust `GodotClass`.
///
/// ## Node based classes
///
/// `#[export]` for a `DynGd<T, D>` works identically to `#[export]` `Gd<T>` for `T` inheriting Node classes.
/// Godot will report an error if the conversion fails, but it will only do so when accessing the given value.
///
/// ## Resource based classes
///
/// `#[export]` for a `DynGd<T, D>` allows you to limit the available choices to implementors of a given trait `D` whose base inherits the specified `T`
/// (for example, `#[export] Option<DynGd<Resource, dyn MyTrait>>` won't include Rust classes with an Object base, even if they implement `MyTrait`).
pub struct DynGd<T, D>
where
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
Expand Down Expand Up @@ -499,18 +518,72 @@ where
}
}

impl<T, D> Export for DynGd<T, D>
/// `#[export]` for `Option<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
///
/// Consider exporting `Option<Gd<T>>` instead of `Option<DynGd<T, D>>` for user-declared GDExtension classes.
impl<T, D> Export for Option<DynGd<T, D>>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
D: ?Sized + 'static,
{
fn export_hint() -> PropertyHintInfo {
PropertyHintInfo {
hint_string: GString::from(get_dyn_property_hint_string::<T, D>()),
..<Gd<T> as Export>::export_hint()
}
PropertyHintInfo::export_dyn_gd::<T, D>()
}

#[doc(hidden)]
fn as_node_class() -> Option<ClassName> {
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,
{
type Via = Option<<DynGd<T, D> as GodotConvert>::Via>;
}

impl<T, D> Var for OnEditor<DynGd<T, D>>
where
T: GodotClass,
D: ?Sized + 'static,
{
fn get_property(&self) -> Self::Via {
Self::get_property_inner(self)
}

fn set_property(&mut self, value: Self::Via) {
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
Self::set_property_inner(self, value)
}
}

/// `#[export]` for `OnEditor<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
///
/// Consider exporting `OnEditor<Gd<T>>` instead of `OnEditor<DynGd<T, D>>` for user-declared GDExtension classes.
impl<T, D> Export for OnEditor<DynGd<T, D>>
where
Self: Var,
T: GodotClass + Bounds<Exportable = bounds::Yes>,
D: ?Sized + 'static,
{
fn export_hint() -> PropertyHintInfo {
PropertyHintInfo::export_dyn_gd::<T, D>()
}

#[doc(hidden)]
fn as_node_class() -> Option<ClassName> {
<Gd<T> as Export>::as_node_class()
PropertyHintInfo::object_as_node_class::<T>()
}
}
64 changes: 45 additions & 19 deletions godot-core/src/obj/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ use godot_ffi as sys;
use sys::{static_assert_eq_size_align, SysPtr as _};

use crate::builtin::{Callable, NodePath, StringName, Variant};
use crate::global::PropertyHint;
use crate::meta::error::{ConvertError, FromFfiError};
use crate::meta::{
ArrayElement, AsArg, CallContext, ClassName, CowArg, FromGodot, GodotConvert, GodotType,
ParamType, PropertyHintInfo, RefArg, ToGodot,
};
use crate::obj::{
bounds, cap, Bounds, DynGd, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits, InstanceId,
RawGd, WithSignals,
OnEditor, RawGd, WithSignals,
};
use crate::private::callbacks;
use crate::registry::property::{object_export_element_type_string, Export, Var};
Expand Down Expand Up @@ -896,8 +895,6 @@ impl<T: GodotClass> Clone for Gd<T> {
}
}

// TODO: Do we even want to implement `Var` and `Export` for `Gd<T>`? You basically always want to use `Option<Gd<T>>` because the editor
// may otherwise try to set the object to a null value.
impl<T: GodotClass> Var for Gd<T> {
fn get_property(&self) -> Self::Via {
self.to_godot()
Expand All @@ -908,34 +905,63 @@ impl<T: GodotClass> Var for Gd<T> {
}
}

impl<T> Export for Gd<T>
impl<T> Export for Option<Gd<T>>
where
T: GodotClass + Bounds<Exportable = bounds::Yes>,
Option<Gd<T>>: Var,
{
fn export_hint() -> PropertyHintInfo {
let hint = if T::inherits::<classes::Resource>() {
PropertyHint::RESOURCE_TYPE
} else if T::inherits::<classes::Node>() {
PropertyHint::NODE_TYPE
} else {
unreachable!("classes not inheriting from Resource or Node should not be exportable")
};
PropertyHintInfo::export_gd::<T>()
}

// Godot does this by default too; the hint is needed when the class is a resource/node,
// but doesn't seem to make a difference otherwise.
let hint_string = T::class_name().to_gstring();
#[doc(hidden)]
fn as_node_class() -> Option<ClassName> {
PropertyHintInfo::object_as_node_class::<T>()
}
}

PropertyHintInfo { hint, hint_string }
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>;
}

impl<T> Var for OnEditor<Gd<T>>
where
T: GodotClass,
{
fn get_property(&self) -> Self::Via {
Self::get_property_inner(self)
}

fn set_property(&mut self, value: Self::Via) {
Self::set_property_inner(self, value)
}
}

impl<T> Export for OnEditor<Gd<T>>
where
Self: Var,
T: GodotClass + Bounds<Exportable = bounds::Yes>,
{
fn export_hint() -> PropertyHintInfo {
PropertyHintInfo::export_gd::<T>()
}

#[doc(hidden)]
fn as_node_class() -> Option<ClassName> {
T::inherits::<classes::Node>().then(|| T::class_name())
PropertyHintInfo::object_as_node_class::<T>()
}
}

// Trait impls Property, Export and TypeStringHint for Option<Gd<T>> are covered by blanket impl for Option<T>

impl<T: GodotClass> PartialEq for Gd<T> {
/// ⚠️ Returns whether two `Gd` pointers point to the same object.
///
Expand Down
2 changes: 2 additions & 0 deletions godot-core/src/obj/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod dyn_gd;
mod gd;
mod guards;
mod instance_id;
mod oneditor;
mod onready;
mod raw_gd;
mod traits;
Expand All @@ -27,6 +28,7 @@ pub use dyn_gd::DynGd;
pub use gd::*;
pub use guards::{BaseMut, BaseRef, DynGdMut, DynGdRef, GdMut, GdRef};
pub use instance_id::*;
pub use oneditor::*;
pub use onready::*;
pub use raw_gd::*;
pub use traits::*;
Expand Down
Loading
Loading