Skip to content

Commit 49c27de

Browse files
committed
Add OnEditor<T>, remove Export implementations for DynGd and Gd
- Restore Var implementation. - Allow to use `OnEditor<T>` for types that are represented by `BuiltinExport` (like converted enums represented in godot as i64 and whatnot) - Add proper bounds for `impl<T> BuiltinExport for Option<T>` - Remove `Sealed` for `OnEditor` since it uses Option implementation - Extend & tweak docs according to CR comments - Add information about skipping before-ready checks for `#[class(tool)]` - Include `compile_fail` doctests and examples.
1 parent 661c80f commit 49c27de

File tree

5 files changed

+120
-82
lines changed

5 files changed

+120
-82
lines changed

godot-core/src/meta/sealed.rs

Lines changed: 1 addition & 2 deletions
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, OnEditor, RawGd};
14+
use crate::obj::{DynGd, Gd, GodotClass, RawGd};
1515

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

godot-core/src/obj/dyn_gd.rs

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,51 @@ use std::{fmt, ops};
139139
///
140140
/// # `#[export]` for `DynGd<T, D>`
141141
///
142-
/// Exporting `DynGd<T, D>` is possible only via algebraic types such as [`OnEditor`] or [`Option`].
142+
/// Exporting `DynGd<T, D>` is possible only via [`OnEditor`] or [`Option`].
143143
/// `DynGd<T, D>` can also be exported directly as an element of an array such as `Array<DynGd<T, D>>`.
144144
///
145145
/// Since `DynGd<T, D>` expresses shared functionality `D` among classes inheriting `T`,
146146
/// `#[export]` for `DynGd<T, D>` where `T` is a concrete Rust class is not allowed.
147-
/// Consider using `Gd<T>` instead in such cases.
147+
///
148+
/// ```compile_fail
149+
///
150+
/// use godot::prelude::*;
151+
///
152+
/// trait Health { /* ... */ }
153+
///
154+
/// #[derive(GodotClass)]
155+
/// # #[class(init)]
156+
/// struct Monster { /* ... */ }
157+
///
158+
/// #[godot_dyn]
159+
/// impl Health for Monster { /* ... */ }
160+
///
161+
/// #[derive(GodotClass)]
162+
/// #[class(init, base = Node)]
163+
/// struct MyClass {
164+
/// #[export]
165+
/// dyn_concrete: Option<DynGd<Monster, dyn Health>>,
166+
/// }
167+
/// ```
168+
///
169+
/// Consider using `Gd<T>` instead in such cases:
170+
///
171+
/// ```
172+
/// use godot::prelude::*;
173+
///
174+
/// #[derive(GodotClass)]
175+
/// #[class(init, base = Node)]
176+
/// struct Monster { /* ... */ }
177+
///
178+
/// /* ... */
179+
///
180+
/// #[derive(GodotClass)]
181+
/// #[class(init, base = Node)]
182+
/// struct MyClass {
183+
/// #[export]
184+
/// dyn_concrete: Option<Gd<Monster>>,
185+
/// }
186+
/// ```
148187
///
149188
/// ## Node based classes
150189
///
@@ -154,7 +193,7 @@ use std::{fmt, ops};
154193
/// ## Resource based classes
155194
///
156195
/// `#[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`).
196+
/// (for example, `#[export] Option<DynGd<Resource, dyn MyTrait>>` won't include Rust classes with an Object base, even if they implement `MyTrait`).
158197
pub struct DynGd<T, D>
159198
where
160199
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
@@ -292,22 +331,6 @@ where
292331
pub fn into_gd(self) -> Gd<T> {
293332
self.obj
294333
}
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-
}
311334
}
312335

313336
impl<T, D> DynGd<T, D>
@@ -519,21 +542,18 @@ where
519542
}
520543
}
521544

522-
impl<T, D> Var for Option<DynGd<T, D>>
545+
impl<T, D> Var for DynGd<T, D>
523546
where
524547
T: GodotClass,
525548
D: ?Sized + 'static,
526549
{
527550
fn get_property(&self) -> Self::Via {
528-
self.as_ref().map(|this| this.get_property())
551+
self.obj.get_property()
529552
}
530553

531554
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-
}
555+
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
556+
*self = <Self as FromGodot>::from_godot(value);
537557
}
538558
}
539559

@@ -579,12 +599,12 @@ where
579599
D: ?Sized + 'static,
580600
{
581601
fn get_property(&self) -> Self::Via {
582-
OnEditor::<DynGd<T, D>>::get_property_inner(self, <DynGd<T, D>>::get_property)
602+
OnEditor::<DynGd<T, D>>::get_property_inner(self)
583603
}
584604

585605
fn set_property(&mut self, value: Self::Via) {
586606
// `set_property` can't be delegated to Gd<T>, since we have to set `erased_obj` as well.
587-
OnEditor::<DynGd<T, D>>::set_property_inner(self, value, <DynGd<T, D>>::set_property)
607+
OnEditor::<DynGd<T, D>>::set_property_inner(self, value)
588608
}
589609
}
590610

godot-core/src/obj/gd.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -549,18 +549,6 @@ impl<T: GodotClass> Gd<T> {
549549
// Do not increment ref-count; assumed to be return value from FFI.
550550
sys::ptr_then(object_ptr, |ptr| Gd::from_obj_sys_weak(ptr))
551551
}
552-
553-
/// Implementation shared between `OnEditor<Gd<T>>` and `Option<Gd<T>`.
554-
#[doc(hidden)]
555-
fn set_property(&mut self, new_value: <Self as GodotConvert>::Via) {
556-
*self = FromGodot::from_godot(new_value);
557-
}
558-
559-
/// Implementation shared between `OnEditor<Gd<T>>` and `Option<Gd<T>`.
560-
#[doc(hidden)]
561-
fn get_property(&self) -> <Self as GodotConvert>::Via {
562-
self.to_godot()
563-
}
564552
}
565553

566554
/// _The methods in this impl block are only available for objects `T` that are manually managed,
@@ -907,17 +895,13 @@ impl<T: GodotClass> Clone for Gd<T> {
907895
}
908896
}
909897

910-
impl<T: GodotClass> Var for Option<Gd<T>> {
898+
impl<T: GodotClass> Var for Gd<T> {
911899
fn get_property(&self) -> Self::Via {
912-
self.as_ref().map(<Gd<T>>::get_property)
900+
self.to_godot()
913901
}
914902

915903
fn set_property(&mut self, value: Self::Via) {
916-
match (value, self.as_mut()) {
917-
(Some(new_value), Some(current_value)) => current_value.set_property(new_value),
918-
(Some(new_value), _) => *self = Some(<Gd<T> as FromGodot>::from_godot(new_value)),
919-
(None, _) => *self = None,
920-
}
904+
*self = FromGodot::from_godot(value)
921905
}
922906
}
923907

@@ -956,11 +940,11 @@ where
956940
OnEditor<Gd<T>>: GodotConvert<Via = Option<<Gd<T> as GodotConvert>::Via>>,
957941
{
958942
fn get_property(&self) -> Self::Via {
959-
OnEditor::<Gd<T>>::get_property_inner(self, <Gd<T>>::get_property)
943+
OnEditor::<Gd<T>>::get_property_inner(self)
960944
}
961945

962946
fn set_property(&mut self, value: Self::Via) {
963-
OnEditor::<Gd<T>>::set_property_inner(self, value, <Gd<T>>::set_property)
947+
OnEditor::<Gd<T>>::set_property_inner(self, value)
964948
}
965949
}
966950

godot-core/src/obj/oneditor.rs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::registry::property::{BuiltinExport, Export, Var};
1111
/// Exported property that must be initialized in the editor (or associated code) before use.
1212
///
1313
/// Allows to use `Gd<T>`, which by itself never holds null objects, as an `#[export]` that should not be null during runtime.
14-
/// As such, it can be used as a more ergonomic way of `Option<Gd<T>>` which _assumes_ initialization.
14+
/// As such, it can be used as a more ergonomic version of `Option<Gd<T>>` which _assumes_ initialization.
1515
///
1616
/// Panics during access if uninitialized.
1717
/// When used inside a node class, `OnEditor` checks if a value has been set before `ready()` is run, and panics otherwise.
@@ -147,6 +147,11 @@ use crate::registry::property::{BuiltinExport, Export, Var};
147147
/// }
148148
/// ```
149149
///
150+
/// # Using `OnEditor<T>` with `#[class(tool)]`
151+
///
152+
/// When used with `#[class(tool)]`, the before-ready checks are omitted.
153+
/// Otherwise, `OnEditor<T>` behaves the same — accessing an uninitialized value
154+
/// will cause a panic.
150155
pub struct OnEditor<T> {
151156
inner: OnEditorState<T>,
152157
}
@@ -160,7 +165,7 @@ pub(crate) enum OnEditorState<T> {
160165
Initialized(T),
161166
}
162167

163-
impl<T: GodotConvert + FromGodot + PartialEq> OnEditor<T> {
168+
impl<T: Var + FromGodot + PartialEq> OnEditor<T> {
164169
/// Initializes invalid `OnEditor<T>` with given value.
165170
///
166171
/// # Panics
@@ -183,7 +188,7 @@ impl<T: GodotConvert + FromGodot + PartialEq> OnEditor<T> {
183188
/// If this value is not changed in the editor, accessing it from Rust will cause a panic.
184189
pub fn new_invalid(val: T) -> Self
185190
where
186-
T: BuiltinExport,
191+
T::Via: BuiltinExport,
187192
{
188193
OnEditor {
189194
inner: OnEditorState::Uninitialized(val),
@@ -208,14 +213,11 @@ impl<T: GodotConvert + FromGodot + PartialEq> OnEditor<T> {
208213
}
209214

210215
/// `Var::get_property` implementation that works both for nullable and non-nullable types.
211-
pub(crate) fn get_property_inner(
212-
&self,
213-
get_property_fn: impl FnOnce(&T) -> T::Via,
214-
) -> Option<T::Via> {
216+
pub(crate) fn get_property_inner(&self) -> Option<T::Via> {
215217
match &self.inner {
216218
OnEditorState::Null => None,
217219
OnEditorState::Uninitialized(val) | OnEditorState::Initialized(val) => {
218-
Some(get_property_fn(val))
220+
Some(val.get_property())
219221
}
220222
}
221223
}
@@ -224,17 +226,11 @@ impl<T: GodotConvert + FromGodot + PartialEq> OnEditor<T> {
224226
///
225227
/// All the state transitions are valid, since it is being run only in the editor.
226228
/// See also [`Option::set_property()`].
227-
pub(crate) fn set_property_inner(
228-
&mut self,
229-
value: Option<T::Via>,
230-
set_property_fn: impl FnOnce(&mut T, T::Via),
231-
) where
232-
T::Via: PartialEq,
233-
{
229+
pub(crate) fn set_property_inner(&mut self, value: Option<T::Via>) {
234230
match (value, &mut self.inner) {
235231
(None, _) => self.inner = OnEditorState::Null,
236232
(Some(value), OnEditorState::Initialized(current_value)) => {
237-
set_property_fn(current_value, value)
233+
current_value.set_property(value);
238234
}
239235
(Some(value), OnEditorState::Null) => {
240236
self.inner = OnEditorState::Initialized(FromGodot::from_godot(value))
@@ -274,31 +270,33 @@ impl<T> std::ops::DerefMut for OnEditor<T> {
274270

275271
impl<T> GodotConvert for OnEditor<T>
276272
where
277-
T: GodotType + GodotConvert<Via = T> + BuiltinExport,
273+
T: GodotConvert,
274+
T::Via: GodotType + BuiltinExport,
278275
{
279276
type Via = T::Via;
280277
}
281278

282279
impl<T> Var for OnEditor<T>
283280
where
284-
OnEditor<T>: GodotConvert<Via = T>,
285-
T: GodotConvert<Via = T> + BuiltinExport + Var + FromGodot + PartialEq,
281+
OnEditor<T>: GodotConvert<Via = T::Via>,
282+
T: Var + FromGodot + PartialEq,
283+
T::Via: BuiltinExport,
286284
{
287285
fn get_property(&self) -> Self::Via {
288286
// Will never fail – `PrimitiveGodotType` can not be represented by the `OnEditorState::Null`.
289-
OnEditor::<T>::get_property_inner(self, T::get_property)
290-
.expect("DirectExport is not nullable.")
287+
OnEditor::<T>::get_property_inner(self).expect("DirectExport is not nullable.")
291288
}
292289

293-
fn set_property(&mut self, value: T) {
294-
OnEditor::<T>::set_property_inner(self, Some(value), T::set_property);
290+
fn set_property(&mut self, value: T::Via) {
291+
OnEditor::<T>::set_property_inner(self, Some(value));
295292
}
296293
}
297294

298295
impl<T> Export for OnEditor<T>
299296
where
300297
OnEditor<T>: Var,
301-
T: GodotConvert<Via = T> + BuiltinExport + Export,
298+
T: GodotConvert + Export,
299+
T::Via: BuiltinExport,
302300
{
303301
fn export_hint() -> PropertyHintInfo {
304302
T::export_hint()

godot-core/src/registry/property.rs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use crate::classes;
1111
use crate::global::PropertyHint;
1212
use godot_ffi as sys;
13-
use godot_ffi::VariantType;
13+
use godot_ffi::{GodotNullableFfi, VariantType};
1414
use std::fmt::Display;
1515

1616
use crate::meta::{ClassName, FromGodot, GodotConvert, GodotType, PropertyHintInfo, ToGodot};
@@ -88,14 +88,22 @@ pub trait Export: Var {
8888
/// Provides a few blanket implementations and, by itself, has no implications
8989
/// for the [`Var`] or [`Export`] traits.
9090
///
91-
/// Some Godot Types which are inherently non-nullable (e.g., `Gd<T>`),
92-
/// might have their value set to null by the editor. Additionally, Godot must generate
93-
/// initial, default value for such properties, causing memory leaks.
94-
///
95-
/// Non-algebraic types that don't implement `BuiltinExport` shouldn't be used directly as an `#[export]`
91+
/// Types which don't implement the `BuiltinExport` trait can't be used directly as an `#[export]`
9692
/// and must be handled using associated algebraic types, such as:
9793
/// * [`Option<T>`], which represents optional value that can be null when used.
9894
/// * [`OnEditor<T>`][crate::obj::OnEditor], which represents value that must not be null when used.
95+
// Some Godot Types which are inherently non-nullable (e.g., `Gd<T>`),
96+
// might have their value set to null by the editor. Additionally, Godot must generate
97+
// initial, default value for such properties, causing memory leaks.
98+
// Such `GodotType`s don't implement `BuiltinExport`.
99+
//
100+
// Note: This marker trait is required to create a blanket implementation
101+
// for `OnEditor<T>` where `T` is anything other than `GodotClass`.
102+
// An alternative approach would involve introducing an extra associated type
103+
// to `GodotType` trait. However, this would not be ideal — `GodotType` is used
104+
// in contexts unrelated to `#[export]`, and adding unnecessary complexity
105+
// should be avoided. Since Rust does not yet support specialization (i.e. negative trait bounds),
106+
// this `MarkerTrait` serves as the intended solution to recognize aforementioned types.
99107
pub trait BuiltinExport {}
100108

101109
/// This function only exists as a place to add doc-tests for the `Export` trait.
@@ -126,6 +134,30 @@ pub trait BuiltinExport {}
126134
/// }
127135
/// ```
128136
///
137+
/// Neither `Gd<T>` nor `DynGd<T, D>` can be used with an `#[export]` directly:
138+
///
139+
/// ```compile_fail
140+
/// use godot::prelude::*;
141+
///
142+
/// #[derive(GodotClass)]
143+
/// #[class(init, base = Node)]
144+
/// struct MyClass {
145+
/// #[export]
146+
/// editor_property: Gd<Resource>,
147+
/// }
148+
/// ```
149+
///
150+
/// ```compile_fail
151+
/// use godot::prelude::*;
152+
///
153+
/// #[derive(GodotClass)]
154+
/// #[class(init, base = Node)]
155+
/// struct MyClass {
156+
/// #[export]
157+
/// editor_property: DynGd<Node, dyn Display>,
158+
/// }
159+
/// ```
160+
///
129161
/// ```compile_fail
130162
/// use godot::prelude::*;
131163
///
@@ -175,7 +207,12 @@ where
175207
}
176208
}
177209

178-
impl<T> BuiltinExport for Option<T> {}
210+
impl<T> BuiltinExport for Option<T>
211+
where
212+
T: GodotType,
213+
T::Ffi: GodotNullableFfi,
214+
{
215+
}
179216

180217
// ----------------------------------------------------------------------------------------------------------------------------------------------
181218
// Export machinery

0 commit comments

Comments
 (0)