Skip to content

Commit d4fb159

Browse files
authored
Merge pull request #1051 from Yarwin/add-on-editor
Add `OnEditor<T>`, remove `impl<T> Export for Gd<T>` and `DynGd<T, D>`
2 parents 61f4e19 + f339323 commit d4fb159

File tree

17 files changed

+817
-78
lines changed

17 files changed

+817
-78
lines changed

godot-core/src/builtin/collections/array.rs

+39-4
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ use crate::builtin::*;
1212
use crate::meta;
1313
use crate::meta::error::{ConvertError, FromGodotError, FromVariantError};
1414
use crate::meta::{
15-
element_godot_type_name, element_variant_type, ArrayElement, ArrayTypeInfo, AsArg, CowArg,
16-
FromGodot, GodotConvert, GodotFfiVariant, GodotType, ParamType, PropertyHintInfo, RefArg,
17-
ToGodot,
15+
element_godot_type_name, element_variant_type, ArrayElement, ArrayTypeInfo, AsArg, ClassName,
16+
CowArg, FromGodot, GodotConvert, GodotFfiVariant, GodotType, ParamType, PropertyHintInfo,
17+
RefArg, ToGodot,
1818
};
19-
use crate::registry::property::{Export, Var};
19+
use crate::obj::{bounds, Bounds, DynGd, Gd, GodotClass};
20+
use crate::registry::property::{BuiltinExport, Export, Var};
2021
use godot_ffi as sys;
2122
use sys::{ffi_methods, interface_fn, GodotFfi};
2223

@@ -1177,6 +1178,40 @@ where
11771178
}
11781179
}
11791180

1181+
impl<T: ArrayElement> BuiltinExport for Array<T> {}
1182+
1183+
impl<T> Export for Array<Gd<T>>
1184+
where
1185+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
1186+
{
1187+
fn export_hint() -> PropertyHintInfo {
1188+
PropertyHintInfo::export_array_element::<Gd<T>>()
1189+
}
1190+
1191+
#[doc(hidden)]
1192+
fn as_node_class() -> Option<ClassName> {
1193+
PropertyHintInfo::object_as_node_class::<T>()
1194+
}
1195+
}
1196+
1197+
/// `#[export]` for `Array<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
1198+
///
1199+
/// Consider exporting `Array<Gd<T>>` instead of `Array<DynGd<T, D>>` for user-declared GDExtension classes.
1200+
impl<T: GodotClass, D> Export for Array<DynGd<T, D>>
1201+
where
1202+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
1203+
D: ?Sized + 'static,
1204+
{
1205+
fn export_hint() -> PropertyHintInfo {
1206+
PropertyHintInfo::export_array_element::<DynGd<T, D>>()
1207+
}
1208+
1209+
#[doc(hidden)]
1210+
fn as_node_class() -> Option<ClassName> {
1211+
PropertyHintInfo::object_as_node_class::<T>()
1212+
}
1213+
}
1214+
11801215
impl<T: ArrayElement> Default for Array<T> {
11811216
#[inline]
11821217
fn default() -> Self {

godot-core/src/meta/property_info.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ use crate::global::{PropertyHint, PropertyUsageFlags};
1010
use crate::meta::{
1111
element_godot_type_name, ArrayElement, ClassName, GodotType, PackedArrayElement,
1212
};
13-
use crate::obj::{EngineBitfield, EngineEnum};
13+
use crate::obj::{bounds, Bounds, EngineBitfield, EngineEnum, GodotClass};
14+
use crate::registry::class::get_dyn_property_hint_string;
1415
use crate::registry::property::{Export, Var};
15-
use crate::sys;
16+
use crate::{classes, sys};
1617
use godot_ffi::VariantType;
1718

1819
/// Describes a property in Godot.
@@ -302,4 +303,42 @@ impl PropertyHintInfo {
302303
hint_string: GString::from(T::element_type_string()),
303304
}
304305
}
306+
307+
pub fn export_gd<T>() -> Self
308+
where
309+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
310+
{
311+
let hint = if T::inherits::<classes::Resource>() {
312+
PropertyHint::RESOURCE_TYPE
313+
} else if T::inherits::<classes::Node>() {
314+
PropertyHint::NODE_TYPE
315+
} else {
316+
unreachable!("classes not inheriting from Resource or Node should not be exportable")
317+
};
318+
319+
// Godot does this by default too; the hint is needed when the class is a resource/node,
320+
// but doesn't seem to make a difference otherwise.
321+
let hint_string = T::class_name().to_gstring();
322+
323+
Self { hint, hint_string }
324+
}
325+
326+
pub fn export_dyn_gd<T, D>() -> Self
327+
where
328+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
329+
D: ?Sized + 'static,
330+
{
331+
PropertyHintInfo {
332+
hint_string: GString::from(get_dyn_property_hint_string::<T, D>()),
333+
..PropertyHintInfo::export_gd::<T>()
334+
}
335+
}
336+
337+
#[doc(hidden)]
338+
pub fn object_as_node_class<T>() -> Option<ClassName>
339+
where
340+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
341+
{
342+
T::inherits::<classes::Node>().then(|| T::class_name())
343+
}
305344
}

godot-core/src/obj/dyn_gd.rs

+81-8
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use crate::builtin::{GString, Variant};
8+
use crate::builtin::Variant;
99
use crate::meta::error::ConvertError;
1010
use crate::meta::{ClassName, FromGodot, GodotConvert, PropertyHintInfo, ToGodot};
1111
use crate::obj::guards::DynGdRef;
12-
use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits};
12+
use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits, OnEditor};
1313
use crate::registry::class::{get_dyn_property_hint_string, try_dynify_object};
1414
use crate::registry::property::{object_export_element_type_string, Export, Var};
1515
use crate::{meta, sys};
@@ -136,6 +136,25 @@ use std::{fmt, ops};
136136
/// godot-rust achieves this thanks to the registration done by `#[godot_dyn]`: the library knows for which classes `Health` is implemented,
137137
/// 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.
138138
/// Behind the scenes, everything is wired up correctly so that you can restore the original `DynGd` even after it has passed through Godot.
139+
///
140+
/// # `#[export]` for `DynGd<T, D>`
141+
///
142+
/// Exporting `DynGd<T, D>` is possible only via [`OnEditor`] or [`Option`].
143+
/// `DynGd<T, D>` can also be exported directly as an element of an array such as `Array<DynGd<T, D>>`.
144+
///
145+
/// Since `DynGd<T, D>` represents shared functionality `D` across classes inheriting from `T`,
146+
/// consider using `#[export] Gd<T>` instead of `#[export] DynGd<T, D>`
147+
/// in cases when `T` is a concrete Rust `GodotClass`.
148+
///
149+
/// ## Node based classes
150+
///
151+
/// `#[export]` for a `DynGd<T, D>` works identically to `#[export]` `Gd<T>` for `T` inheriting Node classes.
152+
/// Godot will report an error if the conversion fails, but it will only do so when accessing the given value.
153+
///
154+
/// ## Resource based classes
155+
///
156+
/// `#[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`
157+
/// (for example, `#[export] Option<DynGd<Resource, dyn MyTrait>>` won't include Rust classes with an Object base, even if they implement `MyTrait`).
139158
pub struct DynGd<T, D>
140159
where
141160
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
@@ -499,18 +518,72 @@ where
499518
}
500519
}
501520

502-
impl<T, D> Export for DynGd<T, D>
521+
/// `#[export]` for `Option<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
522+
///
523+
/// Consider exporting `Option<Gd<T>>` instead of `Option<DynGd<T, D>>` for user-declared GDExtension classes.
524+
impl<T, D> Export for Option<DynGd<T, D>>
503525
where
504526
T: GodotClass + Bounds<Exportable = bounds::Yes>,
505527
D: ?Sized + 'static,
506528
{
507529
fn export_hint() -> PropertyHintInfo {
508-
PropertyHintInfo {
509-
hint_string: GString::from(get_dyn_property_hint_string::<T, D>()),
510-
..<Gd<T> as Export>::export_hint()
511-
}
530+
PropertyHintInfo::export_dyn_gd::<T, D>()
531+
}
532+
533+
#[doc(hidden)]
534+
fn as_node_class() -> Option<ClassName> {
535+
PropertyHintInfo::object_as_node_class::<T>()
512536
}
537+
}
538+
539+
impl<T, D> Default for OnEditor<DynGd<T, D>>
540+
where
541+
T: GodotClass,
542+
D: ?Sized + 'static,
543+
{
544+
fn default() -> Self {
545+
OnEditor::gd_invalid()
546+
}
547+
}
548+
549+
impl<T, D> GodotConvert for OnEditor<DynGd<T, D>>
550+
where
551+
T: GodotClass,
552+
D: ?Sized,
553+
{
554+
type Via = Option<<DynGd<T, D> as GodotConvert>::Via>;
555+
}
556+
557+
impl<T, D> Var for OnEditor<DynGd<T, D>>
558+
where
559+
T: GodotClass,
560+
D: ?Sized + 'static,
561+
{
562+
fn get_property(&self) -> Self::Via {
563+
Self::get_property_inner(self)
564+
}
565+
566+
fn set_property(&mut self, value: Self::Via) {
567+
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
568+
Self::set_property_inner(self, value)
569+
}
570+
}
571+
572+
/// `#[export]` for `OnEditor<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
573+
///
574+
/// Consider exporting `OnEditor<Gd<T>>` instead of `OnEditor<DynGd<T, D>>` for user-declared GDExtension classes.
575+
impl<T, D> Export for OnEditor<DynGd<T, D>>
576+
where
577+
Self: Var,
578+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
579+
D: ?Sized + 'static,
580+
{
581+
fn export_hint() -> PropertyHintInfo {
582+
PropertyHintInfo::export_dyn_gd::<T, D>()
583+
}
584+
585+
#[doc(hidden)]
513586
fn as_node_class() -> Option<ClassName> {
514-
<Gd<T> as Export>::as_node_class()
587+
PropertyHintInfo::object_as_node_class::<T>()
515588
}
516589
}

godot-core/src/obj/gd.rs

+45-19
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@ use godot_ffi as sys;
1212
use sys::{static_assert_eq_size_align, SysPtr as _};
1313

1414
use crate::builtin::{Callable, NodePath, StringName, Variant};
15-
use crate::global::PropertyHint;
1615
use crate::meta::error::{ConvertError, FromFfiError};
1716
use crate::meta::{
1817
ArrayElement, AsArg, CallContext, ClassName, CowArg, FromGodot, GodotConvert, GodotType,
1918
ParamType, PropertyHintInfo, RefArg, ToGodot,
2019
};
2120
use crate::obj::{
2221
bounds, cap, Bounds, DynGd, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits, InstanceId,
23-
RawGd, WithSignals,
22+
OnEditor, RawGd, WithSignals,
2423
};
2524
use crate::private::callbacks;
2625
use crate::registry::property::{object_export_element_type_string, Export, Var};
@@ -896,8 +895,6 @@ impl<T: GodotClass> Clone for Gd<T> {
896895
}
897896
}
898897

899-
// 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
900-
// may otherwise try to set the object to a null value.
901898
impl<T: GodotClass> Var for Gd<T> {
902899
fn get_property(&self) -> Self::Via {
903900
self.to_godot()
@@ -908,34 +905,63 @@ impl<T: GodotClass> Var for Gd<T> {
908905
}
909906
}
910907

911-
impl<T> Export for Gd<T>
908+
impl<T> Export for Option<Gd<T>>
912909
where
913910
T: GodotClass + Bounds<Exportable = bounds::Yes>,
911+
Option<Gd<T>>: Var,
914912
{
915913
fn export_hint() -> PropertyHintInfo {
916-
let hint = if T::inherits::<classes::Resource>() {
917-
PropertyHint::RESOURCE_TYPE
918-
} else if T::inherits::<classes::Node>() {
919-
PropertyHint::NODE_TYPE
920-
} else {
921-
unreachable!("classes not inheriting from Resource or Node should not be exportable")
922-
};
914+
PropertyHintInfo::export_gd::<T>()
915+
}
923916

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

928-
PropertyHintInfo { hint, hint_string }
923+
impl<T: GodotClass> Default for OnEditor<Gd<T>> {
924+
fn default() -> Self {
925+
OnEditor::gd_invalid()
926+
}
927+
}
928+
929+
impl<T> GodotConvert for OnEditor<Gd<T>>
930+
where
931+
T: GodotClass,
932+
Option<<Gd<T> as GodotConvert>::Via>: GodotType,
933+
{
934+
type Via = Option<<Gd<T> as GodotConvert>::Via>;
935+
}
936+
937+
impl<T> Var for OnEditor<Gd<T>>
938+
where
939+
T: GodotClass,
940+
{
941+
fn get_property(&self) -> Self::Via {
942+
Self::get_property_inner(self)
943+
}
944+
945+
fn set_property(&mut self, value: Self::Via) {
946+
Self::set_property_inner(self, value)
947+
}
948+
}
949+
950+
impl<T> Export for OnEditor<Gd<T>>
951+
where
952+
Self: Var,
953+
T: GodotClass + Bounds<Exportable = bounds::Yes>,
954+
{
955+
fn export_hint() -> PropertyHintInfo {
956+
PropertyHintInfo::export_gd::<T>()
929957
}
930958

931959
#[doc(hidden)]
932960
fn as_node_class() -> Option<ClassName> {
933-
T::inherits::<classes::Node>().then(|| T::class_name())
961+
PropertyHintInfo::object_as_node_class::<T>()
934962
}
935963
}
936964

937-
// Trait impls Property, Export and TypeStringHint for Option<Gd<T>> are covered by blanket impl for Option<T>
938-
939965
impl<T: GodotClass> PartialEq for Gd<T> {
940966
/// ⚠️ Returns whether two `Gd` pointers point to the same object.
941967
///

godot-core/src/obj/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod dyn_gd;
1616
mod gd;
1717
mod guards;
1818
mod instance_id;
19+
mod oneditor;
1920
mod onready;
2021
mod raw_gd;
2122
mod traits;
@@ -27,6 +28,7 @@ pub use dyn_gd::DynGd;
2728
pub use gd::*;
2829
pub use guards::{BaseMut, BaseRef, DynGdMut, DynGdRef, GdMut, GdRef};
2930
pub use instance_id::*;
31+
pub use oneditor::*;
3032
pub use onready::*;
3133
pub use raw_gd::*;
3234
pub use traits::*;

0 commit comments

Comments
 (0)