Skip to content

Commit 48162fa

Browse files
committed
Add OnEditor<T>, remove Export and Var implementations for DynGd and Gd`
- Add `OnEditor<T>` - a wrapper that allows to export non-nullable types which must be nullable in the Editor. - Remove Export and Var implementations for `DynGd<T, D>` and `Gd<T>` - it should be provided by algebraic types instead (OnEditor and Option). - Add marker trait `BuiltinExport` which informs if given type can be safely and conveniently used in a `#[export]` directly. - Create implementations for `OnEditor<Gd<T>>` and `OnEditor<DynGd<D, T>>`. - Create proper blanket implementation for Godot Types other than Gd<T> - Inform user about available use cases for `OnEditor<T>` in associated docs - Move Gd<T>::export_info to PropertyHintInfo::export_gd. - Add before_ready check for OnEditor fields to panic and warn user in case if values haven't been set. - FIX: `Option<Gd<T>>` and `OnEditor<Gd<T>>` can no longer be used as an Export for Resource-based classes for `T` Inheriting Node.
1 parent 5bf9f8d commit 48162fa

File tree

18 files changed

+811
-78
lines changed

18 files changed

+811
-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

@@ -1099,6 +1100,40 @@ where
10991100
}
11001101
}
11011102

1103+
impl<T: ArrayElement> BuiltinExport for Array<T> {}
1104+
1105+
impl<T: GodotClass> Export for Array<Gd<T>>
1106+
where
1107+
T: Bounds<Exportable = bounds::Yes>,
1108+
{
1109+
fn export_hint() -> PropertyHintInfo {
1110+
PropertyHintInfo::export_array_element::<Gd<T>>()
1111+
}
1112+
1113+
#[doc(hidden)]
1114+
fn as_node_class() -> Option<ClassName> {
1115+
PropertyHintInfo::object_as_node_class::<T>()
1116+
}
1117+
}
1118+
1119+
/// `#[export]` for `Array<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
1120+
///
1121+
/// Consider exporting `Array<Gd<T>>` instead of `Array<DynGd<T, D>>` for user-declared GDExtension classes.
1122+
impl<T: GodotClass, D> Export for Array<DynGd<T, D>>
1123+
where
1124+
T: GodotClass + Bounds<Exportable = bounds::Yes, Declarer = bounds::DeclEngine>,
1125+
D: ?Sized + 'static,
1126+
{
1127+
fn export_hint() -> PropertyHintInfo {
1128+
PropertyHintInfo::export_array_element::<DynGd<T, D>>()
1129+
}
1130+
1131+
#[doc(hidden)]
1132+
fn as_node_class() -> Option<ClassName> {
1133+
PropertyHintInfo::object_as_node_class::<T>()
1134+
}
1135+
}
1136+
11021137
impl<T: ArrayElement> Default for Array<T> {
11031138
#[inline]
11041139
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/meta/sealed.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use crate::builtin::*;
1212
use crate::meta;
1313
use crate::meta::traits::{ArrayElement, GodotNullableFfi, GodotType};
14-
use crate::obj::{DynGd, Gd, GodotClass, RawGd};
14+
use crate::obj::{DynGd, Gd, GodotClass, OnEditor, RawGd};
1515

1616
pub trait Sealed {}
1717
impl Sealed for Aabb {}
@@ -72,3 +72,4 @@ where
7272
T::Ffi: GodotNullableFfi,
7373
{
7474
}
75+
impl<T> Sealed for OnEditor<T> where T: GodotType {}

godot-core/src/obj/dyn_gd.rs

+104-12
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 algebraic types such as [`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>` expresses shared functionality `D` among classes inheriting `T`,
146+
/// `#[export]` for `DynGd<T, D>` where `T` is a concrete Rust class is not allowed.
147+
/// Consider using `Gd<T>` instead in such cases.
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] 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).
@@ -273,6 +292,22 @@ where
273292
pub fn into_gd(self) -> Gd<T> {
274293
self.obj
275294
}
295+
296+
/// Implementation shared between `OnEditor<DynGd<T, D>>` and `Option<DynGd<T, D>>`.
297+
#[doc(hidden)]
298+
fn set_property(&mut self, value: <Self as GodotConvert>::Via)
299+
where
300+
D: 'static,
301+
{
302+
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
303+
*self = <Self as FromGodot>::from_godot(value);
304+
}
305+
306+
/// Implementation shared between `OnEditor<DynGd<T, D>>` and `Option<DynGd<T, D>>`.
307+
#[doc(hidden)]
308+
fn get_property(&self) -> <Self as GodotConvert>::Via {
309+
self.obj.to_godot()
310+
}
276311
}
277312

278313
impl<T, D> DynGd<T, D>
@@ -484,33 +519,90 @@ where
484519
}
485520
}
486521

487-
impl<T, D> Var for DynGd<T, D>
522+
impl<T, D> Var for Option<DynGd<T, D>>
523+
where
524+
T: GodotClass,
525+
D: ?Sized + 'static,
526+
{
527+
fn get_property(&self) -> Self::Via {
528+
self.as_ref().map(|this| this.get_property())
529+
}
530+
531+
fn set_property(&mut self, value: Self::Via) {
532+
match (value, self.as_mut()) {
533+
(Some(new_value), Some(current_value)) => current_value.set_property(new_value),
534+
(Some(new_value), _) => *self = Some(<DynGd<T, D> as FromGodot>::from_godot(new_value)),
535+
(None, _) => *self = None,
536+
}
537+
}
538+
}
539+
540+
/// `#[export]` for `Option<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
541+
///
542+
/// Consider exporting `Option<Gd<T>>` instead of `Option<DynGd<T, D>>` for user-declared GDExtension classes.
543+
impl<T, D> Export for Option<DynGd<T, D>>
544+
where
545+
T: GodotClass + Bounds<Exportable = bounds::Yes, Declarer = bounds::DeclEngine>,
546+
D: ?Sized + 'static,
547+
{
548+
fn export_hint() -> PropertyHintInfo {
549+
PropertyHintInfo::export_dyn_gd::<T, D>()
550+
}
551+
552+
#[doc(hidden)]
553+
fn as_node_class() -> Option<ClassName> {
554+
PropertyHintInfo::object_as_node_class::<T>()
555+
}
556+
}
557+
558+
impl<T, D> Default for OnEditor<DynGd<T, D>>
559+
where
560+
T: GodotClass,
561+
D: ?Sized + 'static,
562+
{
563+
fn default() -> Self {
564+
OnEditor::gd_invalid()
565+
}
566+
}
567+
568+
impl<T, D> GodotConvert for OnEditor<DynGd<T, D>>
569+
where
570+
T: GodotClass,
571+
D: ?Sized,
572+
{
573+
type Via = Option<<DynGd<T, D> as GodotConvert>::Via>;
574+
}
575+
576+
impl<T, D> Var for OnEditor<DynGd<T, D>>
488577
where
489578
T: GodotClass,
490579
D: ?Sized + 'static,
491580
{
492581
fn get_property(&self) -> Self::Via {
493-
self.obj.get_property()
582+
OnEditor::<DynGd<T, D>>::get_property_inner(self, <DynGd<T, D>>::get_property)
494583
}
495584

496585
fn set_property(&mut self, value: Self::Via) {
497586
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
498-
*self = <Self as FromGodot>::from_godot(value);
587+
OnEditor::<DynGd<T, D>>::set_property_inner(self, value, <DynGd<T, D>>::set_property)
499588
}
500589
}
501590

502-
impl<T, D> Export for DynGd<T, D>
591+
/// `#[export]` for `OnEditor<DynGd<T, D>>` is available only for `T` being Engine class (such as Node or Resource).
592+
///
593+
/// Consider exporting `OnEditor<Gd<T>>` instead of `OnEditor<DynGd<T, D>>` for user-declared GDExtension classes.
594+
impl<T, D> Export for OnEditor<DynGd<T, D>>
503595
where
504-
T: GodotClass + Bounds<Exportable = bounds::Yes>,
596+
OnEditor<DynGd<T, D>>: Var,
597+
T: GodotClass + Bounds<Exportable = bounds::Yes, Declarer = bounds::DeclEngine>,
505598
D: ?Sized + 'static,
506599
{
507600
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-
}
601+
PropertyHintInfo::export_dyn_gd::<T, D>()
512602
}
603+
604+
#[doc(hidden)]
513605
fn as_node_class() -> Option<ClassName> {
514-
<Gd<T> as Export>::as_node_class()
606+
PropertyHintInfo::object_as_node_class::<T>()
515607
}
516608
}

0 commit comments

Comments
 (0)