Skip to content

Commit

Permalink
Merge pull request #958 from godot-rust/feature/dyn-gd-integration
Browse files Browse the repository at this point in the history
`DynGd` trait support + `FromGodot` "re-enrichment" conversions
  • Loading branch information
Bromeon authored Dec 3, 2024
2 parents a60290c + fb19420 commit 6e23bb9
Show file tree
Hide file tree
Showing 17 changed files with 746 additions and 53 deletions.
16 changes: 16 additions & 0 deletions godot-core/src/builtin/collections/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ use sys::{ffi_methods, interface_fn, GodotFfi};
/// compiler will enforce this as long as you use only Rust threads, but it cannot protect against
/// concurrent modification on other threads (e.g. created through GDScript).
///
/// # Element type safety
///
/// We provide a richer set of element types than Godot, for convenience and stronger invariants in your _Rust_ code.
/// This, however, means that the Godot representation of such arrays is not capable of incorporating the additional "Rust-side" information.
/// This can lead to situations where GDScript code or the editor UI can insert values that do not fulfill the Rust-side invariants.
/// The library offers some best-effort protection in Debug mode, but certain errors may only occur on element access, in the form of panics.
///
/// Concretely, the following types lose type information when passed to Godot. If you want 100% bullet-proof arrays, avoid those.
/// - Non-`i64` integers: `i8`, `i16`, `i32`, `u8`, `u16`, `u32`. (`u64` is unsupported).
/// - Non-`f64` floats: `f32`.
/// - Non-null objects: [`Gd<T>`][crate::obj::Gd].
/// Godot generally allows `null` in arrays due to default-constructability, e.g. when using `resize()`.
/// The Godot-faithful (but less convenient) alternative is to use `Option<Gd<T>>` element types.
/// - Objects with dyn-trait association: [`DynGd<T, D>`][crate::obj::DynGd].
/// Godot doesn't know Rust traits and will only see the `T` part.
///
/// # Godot docs
///
/// [`Array[T]` (stable)](https://docs.godotengine.org/en/stable/classes/class_array.html)
Expand Down
18 changes: 16 additions & 2 deletions godot-core/src/classes/class_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

//! Runtime checks and inspection of Godot classes.
use crate::builtin::GString;
use crate::builtin::{GString, StringName};
use crate::classes::{ClassDb, Object};
use crate::meta::{CallContext, ClassName};
use crate::obj::{bounds, Bounds, Gd, GodotClass, InstanceId};
Expand All @@ -19,13 +19,27 @@ pub(crate) fn debug_string<T: GodotClass>(
ty: &str,
) -> std::fmt::Result {
if let Some(id) = obj.instance_id_or_none() {
let class: GString = obj.raw.as_object().get_class();
let class: StringName = obj.dynamic_class_string();
write!(f, "{ty} {{ id: {id}, class: {class} }}")
} else {
write!(f, "{ty} {{ freed obj }}")
}
}

pub(crate) fn debug_string_with_trait<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
ty: &str,
trt: &str,
) -> std::fmt::Result {
if let Some(id) = obj.instance_id_or_none() {
let class: StringName = obj.dynamic_class_string();
write!(f, "{ty} {{ id: {id}, class: {class}, trait: {trt} }}")
} else {
write!(f, "{ty} {{ freed obj }}")
}
}

pub(crate) fn display_string<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
Expand Down
5 changes: 2 additions & 3 deletions godot-core/src/meta/error/call_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,8 @@ impl CallError {
expected: VariantType,
) -> Self {
// Note: reason is same wording as in FromVariantError::description().
let reason = format!(
"parameter #{param_index} conversion -- expected type {expected:?}, got {actual:?}"
);
let reason =
format!("parameter #{param_index} -- cannot convert from {actual:?} to {expected:?}");

Self::new(call_ctx, reason, None)
}
Expand Down
28 changes: 26 additions & 2 deletions godot-core/src/meta/error/convert_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ pub(crate) enum FromGodotError {
/// InvalidEnum is also used by bitfields.
InvalidEnum,

/// Cannot map object to `dyn Trait` because none of the known concrete classes implements it.
UnimplementedDynTrait {
trait_name: String,
class_name: String,
},

/// Cannot map object to `dyn Trait` because none of the known concrete classes implements it.
UnregisteredDynTrait { trait_name: String },

/// `InstanceId` cannot be 0.
ZeroInstanceId,
}
Expand Down Expand Up @@ -235,6 +244,21 @@ impl fmt::Display for FromGodotError {
}
Self::InvalidEnum => write!(f, "invalid engine enum value"),
Self::ZeroInstanceId => write!(f, "`InstanceId` cannot be 0"),
Self::UnimplementedDynTrait {
trait_name,
class_name,
} => {
write!(
f,
"none of the classes derived from `{class_name}` have been linked to trait `{trait_name}` with #[godot_dyn]"
)
}
FromGodotError::UnregisteredDynTrait { trait_name } => {
write!(
f,
"trait `{trait_name}` has not been registered with #[godot_dyn]"
)
}
}
}
}
Expand Down Expand Up @@ -313,11 +337,11 @@ impl fmt::Display for FromVariantError {
match self {
Self::BadType { expected, actual } => {
// Note: wording is the same as in CallError::failed_param_conversion_engine()
write!(f, "expected type {expected:?}, got {actual:?}")
write!(f, "cannot convert from {actual:?} to {expected:?}")
}
Self::BadValue => write!(f, "value cannot be represented in target type's domain"),
Self::WrongClass { expected } => {
write!(f, "expected class {expected}")
write!(f, "cannot convert to class {expected}")
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion godot-core/src/meta/sealed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use crate::builtin::*;
use crate::meta;
use crate::meta::traits::{ArrayElement, GodotNullableFfi, GodotType};
use crate::obj::{Gd, GodotClass, RawGd};
use crate::obj::{DynGd, Gd, GodotClass, RawGd};

pub trait Sealed {}
impl Sealed for Aabb {}
Expand Down Expand Up @@ -64,6 +64,7 @@ impl Sealed for Variant {}
impl<T: ArrayElement> Sealed for Array<T> {}
impl<T: GodotClass> Sealed for Gd<T> {}
impl<T: GodotClass> Sealed for RawGd<T> {}
impl<T: GodotClass, D: ?Sized> Sealed for DynGd<T, D> {}
impl<T: GodotClass> Sealed for meta::ObjectArg<T> {}
impl<T> Sealed for Option<T>
where
Expand Down
Loading

0 comments on commit 6e23bb9

Please sign in to comment.