Skip to content
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

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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: GodotClass> Export for Array<Gd<T>>
where
T: 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, Declarer = bounds::DeclEngine>,
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())
}
}
3 changes: 2 additions & 1 deletion godot-core/src/meta/sealed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use crate::builtin::*;
use crate::meta;
use crate::meta::traits::{ArrayElement, GodotNullableFfi, GodotType};
use crate::obj::{DynGd, Gd, GodotClass, RawGd};
use crate::obj::{DynGd, Gd, GodotClass, OnEditor, RawGd};

pub trait Sealed {}
impl Sealed for Aabb {}
Expand Down Expand Up @@ -72,3 +72,4 @@ where
T::Ffi: GodotNullableFfi,
{
}
impl<T> Sealed for OnEditor<T> where T: GodotType {}
116 changes: 104 additions & 12 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 algebraic types such as [`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>` expresses shared functionality `D` among classes inheriting `T`,
/// `#[export]` for `DynGd<T, D>` where `T` is a concrete Rust class is not allowed.
/// Consider using `Gd<T>` instead in such cases.
///
/// ## 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] 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 @@ -273,6 +292,22 @@ where
pub fn into_gd(self) -> Gd<T> {
self.obj
}

/// Implementation shared between `OnEditor<DynGd<T, D>>` and `Option<DynGd<T, D>>`.
#[doc(hidden)]
fn set_property(&mut self, value: <Self as GodotConvert>::Via)
where
D: 'static,
{
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
*self = <Self as FromGodot>::from_godot(value);
}

/// Implementation shared between `OnEditor<DynGd<T, D>>` and `Option<DynGd<T, D>>`.
#[doc(hidden)]
fn get_property(&self) -> <Self as GodotConvert>::Via {
self.obj.to_godot()
}
}

impl<T, D> DynGd<T, D>
Expand Down Expand Up @@ -484,33 +519,90 @@ where
}
}

impl<T, D> Var for DynGd<T, D>
impl<T, D> Var for Option<DynGd<T, D>>
where
T: GodotClass,
D: ?Sized + 'static,
{
fn get_property(&self) -> Self::Via {
self.as_ref().map(|this| this.get_property())
}

fn set_property(&mut self, value: Self::Via) {
match (value, self.as_mut()) {
(Some(new_value), Some(current_value)) => current_value.set_property(new_value),
(Some(new_value), _) => *self = Some(<DynGd<T, D> as FromGodot>::from_godot(new_value)),
(None, _) => *self = None,
}
}
}

/// `#[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, Declarer = bounds::DeclEngine>,
D: ?Sized + 'static,
{
fn export_hint() -> PropertyHintInfo {
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.obj.get_property()
OnEditor::<DynGd<T, D>>::get_property_inner(self, <DynGd<T, D>>::get_property)
}

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 = <Self as FromGodot>::from_godot(value);
OnEditor::<DynGd<T, D>>::set_property_inner(self, value, <DynGd<T, D>>::set_property)
}
}

impl<T, D> Export for DynGd<T, D>
/// `#[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
T: GodotClass + Bounds<Exportable = bounds::Yes>,
OnEditor<DynGd<T, D>>: Var,
T: GodotClass + Bounds<Exportable = bounds::Yes, Declarer = bounds::DeclEngine>,
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> {
<Gd<T> as Export>::as_node_class()
PropertyHintInfo::object_as_node_class::<T>()
}
}
Loading
Loading