Skip to content

Commit b938b41

Browse files
authored
Merge pull request #953 from godot-rust/feature/dyn-gd
`DynGd<T, D>` smart pointer for Rust-side dynamic dispatch
2 parents 385a765 + d48db7c commit b938b41

File tree

15 files changed

+734
-17
lines changed

15 files changed

+734
-17
lines changed

godot-core/src/classes/class_runtime.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,12 @@ pub(crate) fn ensure_object_alive(
7171
}
7272

7373
#[cfg(debug_assertions)]
74-
pub(crate) fn ensure_object_inherits(
75-
derived: ClassName,
76-
base: ClassName,
77-
instance_id: InstanceId,
78-
) -> bool {
74+
pub(crate) fn ensure_object_inherits(derived: ClassName, base: ClassName, instance_id: InstanceId) {
7975
if derived == base
8076
|| base == Object::class_name() // for Object base, anything inherits by definition
8177
|| is_derived_base_cached(derived, base)
8278
{
83-
return true;
79+
return;
8480
}
8581

8682
panic!(

godot-core/src/meta/args/object_arg.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::builtin::Variant;
99
use crate::meta::error::ConvertError;
1010
use crate::meta::{ClassName, FromGodot, GodotConvert, GodotFfiVariant, GodotType, ToGodot};
11-
use crate::obj::{bounds, Bounds, Gd, GodotClass, Inherits, RawGd};
11+
use crate::obj::{bounds, Bounds, DynGd, Gd, GodotClass, Inherits, RawGd};
1212
use crate::{obj, sys};
1313
use godot_ffi::{GodotFfi, GodotNullableFfi, PtrcallType};
1414
use std::ptr;
@@ -98,6 +98,25 @@ where
9898
}
9999
}
100100

101+
impl<T, U, D> AsObjectArg<T> for &DynGd<U, D>
102+
where
103+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
104+
U: Inherits<T>,
105+
D: ?Sized,
106+
{
107+
fn as_object_arg(&self) -> ObjectArg<T> {
108+
// Reuse Deref.
109+
let gd: &Gd<U> = self;
110+
<&Gd<U>>::as_object_arg(&gd)
111+
}
112+
113+
fn consume_arg(self) -> ObjectCow<T> {
114+
// Reuse Deref.
115+
let gd: &Gd<U> = self;
116+
<&Gd<U>>::consume_arg(gd)
117+
}
118+
}
119+
101120
impl<T, U> AsObjectArg<T> for Option<U>
102121
where
103122
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,

godot-core/src/obj/dyn_gd.rs

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use crate::obj::guards::DynGdRef;
9+
use crate::obj::{bounds, AsDyn, Bounds, DynGdMut, Gd, GodotClass, Inherits};
10+
use std::ops;
11+
12+
/// Smart pointer integrating Rust traits via `dyn` dispatch.
13+
///
14+
/// `DynGd<T, D>` extends a Godot object [`Gd<T>`] with functionality for Rust's trait dynamic dispatch. \
15+
/// In this context, the type parameters have the following meaning:
16+
/// - `T` is the Godot class.
17+
/// - `D` is a trait object `dyn Trait`, where `T: Trait`.
18+
///
19+
/// To register the `T` -> `D` relation with godot-rust, `T` must implement [`AsDyn<D>`]. This can be automated with the
20+
/// [`#[godot_dyn]`](../register/attr.godot_dyn.html) attribute macro.
21+
///
22+
/// # Construction and API
23+
/// You can convert between `Gd` and `DynGd` using [`Gd::into_dyn()`] and [`DynGd::into_gd()`]. The former sometimes needs an explicit
24+
/// `::<dyn Trait>` type argument, but can often be inferred.
25+
///
26+
/// The `DynGd` API is very close to `Gd`. In fact, both `Deref` and `DerefMut` are implemented for `DynGd` -> `Gd`, so you can access all the
27+
/// underlying `Gd` methods as well as Godot class APIs directly.
28+
///
29+
/// The main new parts are two methods [`dyn_bind()`][Self::dyn_bind] and [`dyn_bind_mut()`][Self::dyn_bind_mut]. These are very similar to `Gd`'s
30+
/// [`bind()`][Gd::bind] and [`bind_mut()`][Gd::bind_mut], but return a reference guard to the trait object `D` instead of the Godot class `T`.
31+
///
32+
/// # Example
33+
///
34+
/// ```no_run
35+
/// use godot::obj::{Gd, DynGd,NewGd};
36+
/// use godot::register::{godot_dyn, GodotClass};
37+
/// use godot::classes::RefCounted;
38+
///
39+
/// #[derive(GodotClass)]
40+
/// #[class(init)]
41+
/// struct Monster {
42+
/// #[init(val = 100)]
43+
/// hitpoints: u16,
44+
/// }
45+
///
46+
/// trait Health {
47+
/// fn is_alive(&self) -> bool;
48+
/// fn deal_damage(&mut self, damage: u16);
49+
/// }
50+
///
51+
/// // The #[godot_dyn] attribute macro registers the dynamic relation in godot-rust.
52+
/// // Traits are implemented as usual.
53+
/// #[godot_dyn]
54+
/// impl Health for Monster {
55+
/// fn is_alive(&self) -> bool {
56+
/// self.hitpoints > 0
57+
/// }
58+
///
59+
/// fn deal_damage(&mut self, damage: u16) {
60+
/// self.hitpoints = self.hitpoints.saturating_sub(damage);
61+
/// }
62+
/// }
63+
///
64+
/// // Create a Gd<Monster> and convert it -> DynGd<Monster, dyn Health>.
65+
/// let monster = Monster::new_gd();
66+
/// let dyn_monster = monster.into_dyn::<dyn Health>();
67+
///
68+
/// // Now upcast it to its base class -> type is DynGd<RefCounted, dyn Health>.
69+
/// let mut dyn_monster = dyn_monster.upcast::<RefCounted>();
70+
///
71+
/// // Due to RefCounted abstraction, you can no longer access concrete Monster properties.
72+
/// // However, the trait Health is still accessible through dyn_bind().
73+
/// assert!(dyn_monster.dyn_bind().is_alive());
74+
///
75+
/// // To mutate the object, call dyn_bind_mut(). Rust borrow rules apply.
76+
/// let mut guard = dyn_monster.dyn_bind_mut();
77+
/// guard.deal_damage(120);
78+
/// assert!(!guard.is_alive());
79+
/// ```
80+
pub struct DynGd<T, D>
81+
where
82+
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
83+
T: GodotClass,
84+
D: ?Sized,
85+
{
86+
// Potential optimizations: use single Gd; use Rc/Arc instead of Box+clone; store a downcast fn from Gd<T>; ...
87+
obj: Gd<T>,
88+
erased_obj: Box<dyn ErasedGd<D>>,
89+
}
90+
91+
impl<T, D> DynGd<T, D>
92+
where
93+
T: AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
94+
D: ?Sized,
95+
{
96+
pub(crate) fn from_gd(gd_instance: Gd<T>) -> Self {
97+
let erased_obj = Box::new(gd_instance.clone());
98+
99+
Self {
100+
obj: gd_instance,
101+
erased_obj,
102+
}
103+
}
104+
}
105+
106+
impl<T, D> DynGd<T, D>
107+
where
108+
// Again, T deliberately does not require AsDyn<D> here. See above.
109+
T: GodotClass,
110+
D: ?Sized,
111+
{
112+
/// Acquires a shared reference guard to the trait object `D`.
113+
///
114+
/// The resulting guard implements `Deref<Target = D>`, allowing shared access to the trait's methods.
115+
///
116+
/// See [`Gd::bind()`][Gd::bind] for borrow checking semantics and panics.
117+
pub fn dyn_bind(&self) -> DynGdRef<D> {
118+
self.erased_obj.dyn_bind()
119+
}
120+
121+
/// Acquires an exclusive reference guard to the trait object `D`.
122+
///
123+
/// The resulting guard implements `DerefMut<Target = D>`, allowing exclusive mutable access to the trait's methods.
124+
///
125+
/// See [`Gd::bind_mut()`][Gd::bind_mut] for borrow checking semantics and panics.
126+
pub fn dyn_bind_mut(&mut self) -> DynGdMut<D> {
127+
self.erased_obj.dyn_bind_mut()
128+
}
129+
130+
// Certain methods "overridden" from deref'ed Gd here, so they're more idiomatic to use.
131+
// Those taking self by value, like free(), must be overridden.
132+
133+
/// Upcast to a Godot base, while retaining the `D` trait object.
134+
///
135+
/// This is useful when you want to gather multiple objects under a common Godot base (e.g. `Node`), but still enable common functionality.
136+
/// The common functionality is still accessible through `D` even when upcasting.
137+
///
138+
/// See also [`Gd::upcast()`].
139+
pub fn upcast<Base>(self) -> DynGd<Base, D>
140+
where
141+
Base: GodotClass,
142+
T: Inherits<Base>,
143+
{
144+
DynGd {
145+
obj: self.obj.upcast::<Base>(),
146+
erased_obj: self.erased_obj,
147+
}
148+
}
149+
150+
/// Downgrades to a `Gd<T>` pointer, abandoning the `D` abstraction.
151+
#[must_use]
152+
pub fn into_gd(self) -> Gd<T> {
153+
self.obj
154+
}
155+
}
156+
157+
impl<T, D> DynGd<T, D>
158+
where
159+
T: GodotClass + Bounds<Memory = bounds::MemManual>,
160+
D: ?Sized,
161+
{
162+
pub fn free(self) {
163+
self.obj.free()
164+
}
165+
}
166+
167+
// Don't derive since that messes with bounds, and `.clone()` may silently fall back to deref'ed `Gd::clone()`.
168+
impl<T, D> Clone for DynGd<T, D>
169+
where
170+
T: GodotClass,
171+
D: ?Sized,
172+
{
173+
fn clone(&self) -> Self {
174+
Self {
175+
obj: self.obj.clone(),
176+
erased_obj: self.erased_obj.clone_box(),
177+
}
178+
}
179+
}
180+
181+
impl<T, D> ops::Deref for DynGd<T, D>
182+
where
183+
T: GodotClass,
184+
D: ?Sized,
185+
{
186+
type Target = Gd<T>;
187+
188+
fn deref(&self) -> &Self::Target {
189+
&self.obj
190+
}
191+
}
192+
193+
impl<T, D> ops::DerefMut for DynGd<T, D>
194+
where
195+
T: GodotClass,
196+
D: ?Sized,
197+
{
198+
fn deref_mut(&mut self) -> &mut Self::Target {
199+
&mut self.obj
200+
}
201+
}
202+
203+
// ----------------------------------------------------------------------------------------------------------------------------------------------
204+
// Type erasure
205+
206+
trait ErasedGd<D: ?Sized> {
207+
fn dyn_bind(&self) -> DynGdRef<D>;
208+
fn dyn_bind_mut(&mut self) -> DynGdMut<D>;
209+
210+
fn clone_box(&self) -> Box<dyn ErasedGd<D>>;
211+
}
212+
213+
impl<T, D> ErasedGd<D> for Gd<T>
214+
where
215+
T: AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
216+
D: ?Sized,
217+
{
218+
fn dyn_bind(&self) -> DynGdRef<D> {
219+
DynGdRef::from_guard::<T>(Gd::bind(self))
220+
}
221+
222+
fn dyn_bind_mut(&mut self) -> DynGdMut<D> {
223+
DynGdMut::from_guard::<T>(Gd::bind_mut(self))
224+
}
225+
226+
fn clone_box(&self) -> Box<dyn ErasedGd<D>> {
227+
Box::new(Gd::clone(self))
228+
}
229+
}
230+
231+
// ----------------------------------------------------------------------------------------------------------------------------------------------
232+
// Integration with Godot traits

godot-core/src/obj/gd.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use crate::meta::{
2020
ParamType, PropertyHintInfo, RefArg, ToGodot,
2121
};
2222
use crate::obj::{
23-
bounds, cap, Bounds, EngineEnum, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits, InstanceId,
24-
RawGd,
23+
bounds, cap, Bounds, DynGd, EngineEnum, GdDerefTarget, GdMut, GdRef, GodotClass, Inherits,
24+
InstanceId, RawGd,
2525
};
2626
use crate::private::callbacks;
2727
use crate::registry::property::{Export, Var};
@@ -384,7 +384,7 @@ impl<T: GodotClass> Gd<T> {
384384
/// object for further casts.
385385
pub fn try_cast<Derived>(self) -> Result<Gd<Derived>, Self>
386386
where
387-
Derived: GodotClass + Inherits<T>,
387+
Derived: Inherits<T>,
388388
{
389389
// Separate method due to more restrictive bounds.
390390
self.owned_cast()
@@ -396,7 +396,7 @@ impl<T: GodotClass> Gd<T> {
396396
/// If the class' dynamic type is not `Derived` or one of its subclasses. Use [`Self::try_cast()`] if you want to check the result.
397397
pub fn cast<Derived>(self) -> Gd<Derived>
398398
where
399-
Derived: GodotClass + Inherits<T>,
399+
Derived: Inherits<T>,
400400
{
401401
self.owned_cast().unwrap_or_else(|from_obj| {
402402
panic!(
@@ -431,6 +431,19 @@ impl<T: GodotClass> Gd<T> {
431431
}
432432
}
433433

434+
/// Upgrades to a `DynGd<T, D>` pointer, enabling the `D` abstraction.
435+
///
436+
/// The `D` parameter can typically be inferred when there is a single `AsDyn<...>` implementation for `T`. \
437+
/// Otherwise, use it as `gd.into_dyn::<dyn MyTrait>()`.
438+
#[must_use]
439+
pub fn into_dyn<D>(self) -> DynGd<T, D>
440+
where
441+
T: crate::obj::AsDyn<D> + Bounds<Declarer = bounds::DeclUser>,
442+
D: ?Sized,
443+
{
444+
DynGd::<T, D>::from_gd(self)
445+
}
446+
434447
/// Returns a callable referencing a method from this object named `method_name`.
435448
///
436449
/// This is shorter syntax for [`Callable::from_object_method(self, method_name)`][Callable::from_object_method].
@@ -694,17 +707,20 @@ impl<T: GodotClass> FromGodot for Gd<T> {
694707
}
695708

696709
impl<T: GodotClass> GodotType for Gd<T> {
710+
// Some #[doc(hidden)] are repeated despite already declared in trait; some IDEs suggest in auto-complete otherwise.
697711
type Ffi = RawGd<T>;
698712

699713
type ToFfi<'f>
700714
= RefArg<'f, RawGd<T>>
701715
where
702716
Self: 'f;
703717

718+
#[doc(hidden)]
704719
fn to_ffi(&self) -> Self::ToFfi<'_> {
705720
RefArg::new(&self.raw)
706721
}
707722

723+
#[doc(hidden)]
708724
fn into_ffi(self) -> Self::Ffi {
709725
self.raw
710726
}
@@ -717,7 +733,7 @@ impl<T: GodotClass> GodotType for Gd<T> {
717733
}
718734
}
719735

720-
fn class_name() -> crate::meta::ClassName {
736+
fn class_name() -> ClassName {
721737
T::class_name()
722738
}
723739

@@ -775,6 +791,7 @@ impl<T: GodotClass> ArrayElement for Option<Gd<T>> {
775791
}
776792

777793
impl<'r, T: GodotClass> AsArg<Gd<T>> for &'r Gd<T> {
794+
#[doc(hidden)] // Repeated despite already hidden in trait; some IDEs suggest this otherwise.
778795
fn into_arg<'cow>(self) -> CowArg<'cow, Gd<T>>
779796
where
780797
'r: 'cow, // Original reference must be valid for at least as long as the returned cow.

0 commit comments

Comments
 (0)