Skip to content

Commit c6b2357

Browse files
committed
DynGd docs + free() tests
1 parent 3175190 commit c6b2357

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

godot-core/src/obj/dyn_gd.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ use std::{fmt, ops};
8282
/// guard.deal_damage(120);
8383
/// assert!(!guard.is_alive());
8484
/// ```
85+
///
86+
/// # Polymorphic `dyn` re-enrichment
87+
///
88+
/// When passing `DynGd<T, D>` to Godot, you will lose the `D` part of the type inside the engine, because Godot doesn't know about Rust traits.
89+
/// The trait methods won't be accessible through GDScript, either.
90+
///
91+
/// If you now receive the same object back from Godot, you can easily obtain it as `Gd<T>` -- but what if you need the original `DynGd<T, D>`?
92+
/// If `T` is concrete (i.e. directly implements `D`), then [`Gd::into_dyn()`] is of course possible. But in reality, you may have a polymorphic
93+
/// base class such as `RefCounted` and want to ensure that trait object `D` dispatches to the correct subclass, without manually checking every
94+
/// possible candidate.
95+
///
96+
/// To stay with the above example: let's say `Health` is implemented for both `Monster` and `Knight` classes. You now receive a
97+
/// `DynGd<RefCounted, dyn Health>`, which can represent either of the two classes. How can this work without trying to downcast to both?
98+
///
99+
/// godot-rust has a mechanism to re-enrich the `DynGd` with the correct trait object. Thanks to `#[godot_dyn]`, the library knows for which
100+
/// classes `Health` is implemented, and it can query the dynamic type of the object. Based on that type, it can find the `impl Health`
101+
/// implementation matching the correct class. Behind the scenes, everything is wired up correctly so that you can restore the original `DynGd`
102+
/// even after it has passed through Godot.
85103
pub struct DynGd<T, D>
86104
where
87105
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
@@ -226,6 +244,9 @@ where
226244
T: GodotClass + Bounds<Memory = bounds::MemManual>,
227245
D: ?Sized,
228246
{
247+
/// Destroy the manually-managed Godot object.
248+
///
249+
/// See [`Gd::free()`] for semantics and panics.
229250
pub fn free(self) {
230251
self.obj.free()
231252
}

itest/rust/src/object_tests/dyn_gd_test.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ fn dyn_gd_error_unregistered_trait() {
317317

318318
#[itest]
319319
fn dyn_gd_error_unimplemented_trait() {
320-
let obj = RefCounted::new_gd(); //Gd::from_object(RefcHealth { hp: 33 }).into_dyn::<dyn Health>();
320+
let obj = RefCounted::new_gd();
321321

322322
let variant = obj.to_variant();
323323
let back = variant.try_to::<DynGd<RefCounted, dyn Health>>();
@@ -329,6 +329,31 @@ fn dyn_gd_error_unimplemented_trait() {
329329
);
330330
}
331331

332+
#[itest]
333+
fn dyn_gd_free_while_dyn_bound() {
334+
let mut obj = foreign::NodeHealth::new_alloc().into_dyn();
335+
336+
{
337+
let copy = obj.clone();
338+
let _guard = obj.dyn_bind();
339+
340+
expect_panic("Cannot free while dyn_bind() guard is held", || {
341+
copy.free();
342+
});
343+
}
344+
{
345+
let copy = obj.clone();
346+
let _guard = obj.dyn_bind_mut();
347+
348+
expect_panic("Cannot free while dyn_bind_mut() guard is held", || {
349+
copy.free();
350+
});
351+
}
352+
353+
// Now allowed.
354+
obj.free();
355+
}
356+
332357
// ----------------------------------------------------------------------------------------------------------------------------------------------
333358
// Example symbols
334359

0 commit comments

Comments
 (0)