Skip to content

Commit 6e23bb9

Browse files
authored
Merge pull request #958 from godot-rust/feature/dyn-gd-integration
`DynGd` trait support + `FromGodot` "re-enrichment" conversions
2 parents a60290c + fb19420 commit 6e23bb9

File tree

17 files changed

+746
-53
lines changed

17 files changed

+746
-53
lines changed

godot-core/src/builtin/collections/array.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ use sys::{ffi_methods, interface_fn, GodotFfi};
109109
/// compiler will enforce this as long as you use only Rust threads, but it cannot protect against
110110
/// concurrent modification on other threads (e.g. created through GDScript).
111111
///
112+
/// # Element type safety
113+
///
114+
/// We provide a richer set of element types than Godot, for convenience and stronger invariants in your _Rust_ code.
115+
/// This, however, means that the Godot representation of such arrays is not capable of incorporating the additional "Rust-side" information.
116+
/// This can lead to situations where GDScript code or the editor UI can insert values that do not fulfill the Rust-side invariants.
117+
/// The library offers some best-effort protection in Debug mode, but certain errors may only occur on element access, in the form of panics.
118+
///
119+
/// Concretely, the following types lose type information when passed to Godot. If you want 100% bullet-proof arrays, avoid those.
120+
/// - Non-`i64` integers: `i8`, `i16`, `i32`, `u8`, `u16`, `u32`. (`u64` is unsupported).
121+
/// - Non-`f64` floats: `f32`.
122+
/// - Non-null objects: [`Gd<T>`][crate::obj::Gd].
123+
/// Godot generally allows `null` in arrays due to default-constructability, e.g. when using `resize()`.
124+
/// The Godot-faithful (but less convenient) alternative is to use `Option<Gd<T>>` element types.
125+
/// - Objects with dyn-trait association: [`DynGd<T, D>`][crate::obj::DynGd].
126+
/// Godot doesn't know Rust traits and will only see the `T` part.
127+
///
112128
/// # Godot docs
113129
///
114130
/// [`Array[T]` (stable)](https://docs.godotengine.org/en/stable/classes/class_array.html)

godot-core/src/classes/class_runtime.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
//! Runtime checks and inspection of Godot classes.
99
10-
use crate::builtin::GString;
10+
use crate::builtin::{GString, StringName};
1111
use crate::classes::{ClassDb, Object};
1212
use crate::meta::{CallContext, ClassName};
1313
use crate::obj::{bounds, Bounds, Gd, GodotClass, InstanceId};
@@ -19,13 +19,27 @@ pub(crate) fn debug_string<T: GodotClass>(
1919
ty: &str,
2020
) -> std::fmt::Result {
2121
if let Some(id) = obj.instance_id_or_none() {
22-
let class: GString = obj.raw.as_object().get_class();
22+
let class: StringName = obj.dynamic_class_string();
2323
write!(f, "{ty} {{ id: {id}, class: {class} }}")
2424
} else {
2525
write!(f, "{ty} {{ freed obj }}")
2626
}
2727
}
2828

29+
pub(crate) fn debug_string_with_trait<T: GodotClass>(
30+
obj: &Gd<T>,
31+
f: &mut std::fmt::Formatter<'_>,
32+
ty: &str,
33+
trt: &str,
34+
) -> std::fmt::Result {
35+
if let Some(id) = obj.instance_id_or_none() {
36+
let class: StringName = obj.dynamic_class_string();
37+
write!(f, "{ty} {{ id: {id}, class: {class}, trait: {trt} }}")
38+
} else {
39+
write!(f, "{ty} {{ freed obj }}")
40+
}
41+
}
42+
2943
pub(crate) fn display_string<T: GodotClass>(
3044
obj: &Gd<T>,
3145
f: &mut std::fmt::Formatter<'_>,

godot-core/src/meta/error/call_error.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,8 @@ impl CallError {
188188
expected: VariantType,
189189
) -> Self {
190190
// Note: reason is same wording as in FromVariantError::description().
191-
let reason = format!(
192-
"parameter #{param_index} conversion -- expected type {expected:?}, got {actual:?}"
193-
);
191+
let reason =
192+
format!("parameter #{param_index} -- cannot convert from {actual:?} to {expected:?}");
194193

195194
Self::new(call_ctx, reason, None)
196195
}

godot-core/src/meta/error/convert_error.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ pub(crate) enum FromGodotError {
181181
/// InvalidEnum is also used by bitfields.
182182
InvalidEnum,
183183

184+
/// Cannot map object to `dyn Trait` because none of the known concrete classes implements it.
185+
UnimplementedDynTrait {
186+
trait_name: String,
187+
class_name: String,
188+
},
189+
190+
/// Cannot map object to `dyn Trait` because none of the known concrete classes implements it.
191+
UnregisteredDynTrait { trait_name: String },
192+
184193
/// `InstanceId` cannot be 0.
185194
ZeroInstanceId,
186195
}
@@ -235,6 +244,21 @@ impl fmt::Display for FromGodotError {
235244
}
236245
Self::InvalidEnum => write!(f, "invalid engine enum value"),
237246
Self::ZeroInstanceId => write!(f, "`InstanceId` cannot be 0"),
247+
Self::UnimplementedDynTrait {
248+
trait_name,
249+
class_name,
250+
} => {
251+
write!(
252+
f,
253+
"none of the classes derived from `{class_name}` have been linked to trait `{trait_name}` with #[godot_dyn]"
254+
)
255+
}
256+
FromGodotError::UnregisteredDynTrait { trait_name } => {
257+
write!(
258+
f,
259+
"trait `{trait_name}` has not been registered with #[godot_dyn]"
260+
)
261+
}
238262
}
239263
}
240264
}
@@ -313,11 +337,11 @@ impl fmt::Display for FromVariantError {
313337
match self {
314338
Self::BadType { expected, actual } => {
315339
// Note: wording is the same as in CallError::failed_param_conversion_engine()
316-
write!(f, "expected type {expected:?}, got {actual:?}")
340+
write!(f, "cannot convert from {actual:?} to {expected:?}")
317341
}
318342
Self::BadValue => write!(f, "value cannot be represented in target type's domain"),
319343
Self::WrongClass { expected } => {
320-
write!(f, "expected class {expected}")
344+
write!(f, "cannot convert to class {expected}")
321345
}
322346
}
323347
}

godot-core/src/meta/sealed.rs

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

1616
pub trait Sealed {}
1717
impl Sealed for Aabb {}
@@ -64,6 +64,7 @@ impl Sealed for Variant {}
6464
impl<T: ArrayElement> Sealed for Array<T> {}
6565
impl<T: GodotClass> Sealed for Gd<T> {}
6666
impl<T: GodotClass> Sealed for RawGd<T> {}
67+
impl<T: GodotClass, D: ?Sized> Sealed for DynGd<T, D> {}
6768
impl<T: GodotClass> Sealed for meta::ObjectArg<T> {}
6869
impl<T> Sealed for Option<T>
6970
where

0 commit comments

Comments
 (0)