Skip to content

Commit

Permalink
Merge pull request #969 from godot-rust/doc/lib-dyngd
Browse files Browse the repository at this point in the history
Improve docs in `DynGd` (re-enrichment) + Cargo features
  • Loading branch information
Bromeon authored Dec 13, 2024
2 parents 4bc92e8 + ef46572 commit 1dbc0aa
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 14 deletions.
55 changes: 45 additions & 10 deletions godot-core/src/obj/dyn_gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use std::{fmt, ops};
/// [`#[godot_dyn]`](../register/attr.godot_dyn.html) attribute macro.
///
/// # Construction and API
///
/// You can convert between `Gd` and `DynGd` using [`Gd::into_dyn()`] and [`DynGd::into_gd()`]. The former sometimes needs an explicit
/// `::<dyn Trait>` type argument, but can often be inferred.
///
Expand Down Expand Up @@ -88,18 +89,52 @@ use std::{fmt, ops};
/// 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.
/// The trait methods won't be accessible through GDScript, either.
///
/// 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>`?
/// 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
/// base class such as `RefCounted` and want to ensure that trait object `D` dispatches to the correct subclass, without manually checking every
/// possible candidate.
/// When _receiving_ objects from Godot, the [`FromGodot`] trait is used to convert values to their Rust counterparts. `FromGodot` allows you to
/// use types in `#[func]` parameters or extract elements from arrays, among others. If you now receive a trait-enabled object back from Godot,
/// you can easily obtain it as `Gd<T>` -- but what if you need the original `DynGd<T, D>` back? If `T` is concrete and directly implements `D`,
/// then [`Gd::into_dyn()`] is of course possible. But in reality, you may have a polymorphic base class such as `RefCounted` or `Node` and
/// want to ensure that trait object `D` dispatches to the correct subclass, without manually checking every possible candidate.
///
/// To stay with the above example: let's say `Health` is implemented for two classes `Monster` and `Knight`. You now have a
/// `DynGd<RefCounted, dyn Health>`, which can represent either of the two classes. We pass this to Godot (e.g. as a `Variant`), and then back.
///
/// ```no_run
/// # use godot::prelude::*;
/// trait Health { /* ... */ }
///
/// #[derive(GodotClass)]
/// # #[class(init)]
/// struct Monster { /* ... */ }
/// #[godot_dyn]
/// impl Health for Monster { /* ... */ }
///
/// #[derive(GodotClass)]
/// # #[class(init)]
/// struct Knight { /* ... */ }
/// #[godot_dyn]
/// impl Health for Knight { /* ... */ }
///
/// // Let's construct a DynGd, and pass it to Godot as a Variant.
/// # let runtime_condition = true;
/// let variant = if runtime_condition {
/// // DynGd<Knight, dyn Health>
/// Knight::new_gd().into_dyn::<dyn Health>().to_variant()
/// } else {
/// // DynGd<Monster, dyn Health>
/// Monster::new_gd().into_dyn::<dyn Health>().to_variant()
/// };
///
/// // Now convert back into a DynGd -- but we don't know the concrete type.
/// // We can still represent it as DynGd<RefCounted, dyn Health>.
/// let dyn_gd: DynGd<RefCounted, dyn Health> = variant.to();
/// // Now work with the abstract object as usual.
/// ```
///
/// To stay with the above example: let's say `Health` is implemented for both `Monster` and `Knight` classes. You now receive a
/// `DynGd<RefCounted, dyn Health>`, which can represent either of the two classes. How can this work without trying to downcast to both?
/// When converting from Godot back into `DynGd`, we say that the `dyn Health` trait object is _re-enriched_.
///
/// godot-rust has a mechanism to re-enrich the `DynGd` with the correct trait object. Thanks to `#[godot_dyn]`, the library knows for which
/// classes `Health` is implemented, 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. Behind the scenes, everything is wired up correctly so that you can restore the original `DynGd`
/// even after it has passed through Godot.
/// godot-rust achieves this thanks to the registration done by `#[godot_dyn]`: the library knows for which classes `Health` is implemented,
/// 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.
/// Behind the scenes, everything is wired up correctly so that you can restore the original `DynGd` even after it has passed through Godot.
pub struct DynGd<T, D>
where
// T does _not_ require AsDyn<D> here. Otherwise, it's impossible to upcast (without implementing the relation for all base classes).
Expand Down
1 change: 1 addition & 0 deletions godot-macros/src/class/godot_dyn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub fn attribute_godot_dyn(input_decl: venial::Item) -> ParseResult<TokenStream>
dyn_trait_typeid: std::any::TypeId::of::<dyn #trait_path>(),
erased_dynify_fn: {
fn dynify_fn(obj: ::godot::obj::Gd<::godot::classes::Object>) -> #prv::ErasedDynGd {
// SAFETY: runtime class type is statically known here and linked to the `class_name` field of the plugin.
let obj = unsafe { obj.try_cast::<#class_path>().unwrap_unchecked() };
let obj = obj.into_dyn::<dyn #trait_path>();
let obj = obj.upcast::<::godot::classes::Object>();
Expand Down
17 changes: 13 additions & 4 deletions godot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,24 @@
//!
//! * **`experimental-threads`**
//!
//! Experimental threading support. This enables `Send`/`Sync` traits for `Gd<T>` and makes the guard types `Gd`/`GdMut` aware of
//! multithreaded references. The safety aspects are not ironed out yet; there is a high risk of unsoundness at the moment.
//! Experimental threading support. This adds synchronization to access the user instance in `Gd<T>` and disables several single-thread checks.
//! The safety aspects are not ironed out yet; there is a high risk of unsoundness at the moment.
//! As this evolves, it is very likely that the API becomes stricter.<br><br>
//!
//! * **`experimental-wasm`**
//!
//! Support for WebAssembly exports is still a work-in-progress and is not yet well tested. This feature is in place for users
//! to explicitly opt in to any instabilities or rough edges that may result. Due to a limitation in Godot, it might currently not
//! work Firefox browser.<br><br>
//! to explicitly opt in to any instabilities or rough edges that may result.
//!
//! By default, Wasm threads are enabled and require the flag `"-C", "link-args=-sUSE_PTHREADS=1"` in the `wasm32-unknown-unknown` target.
//! This must be kept in sync with Godot's Web export settings (threading support enabled). To disable it, use **additionally* the feature
//! `experimental-wasm-nothreads`.<br><br>
//!
//! * **`experimental-wasm-nothreads`**
//!
//! Requires the `experimental-wasm` feature. Disables threading support for WebAssembly exports. This needs to be kept in sync with
//! Godot's Web export setting (threading support disabled), and must _not_ use the `"-C", "link-args=-sUSE_PTHREADS=1"` flag in the
//! `wasm32-unknown-unknown` target.<br><br>
//!
//! * **`codegen-rustfmt`**
//!
Expand Down

0 comments on commit 1dbc0aa

Please sign in to comment.