Skip to content

Commit

Permalink
Merge pull request #1034 from godot-rust/qol/variant-object-id-polyfill
Browse files Browse the repository at this point in the history
Support `Variant::object_id()` for Godot <4.4; add `object_id_unchecked()`
  • Loading branch information
Bromeon authored Feb 2, 2025
2 parents b9b3f9f + fe50efc commit a2d4861
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 6 deletions.
45 changes: 41 additions & 4 deletions godot-core/src/builtin/variant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use crate::builtin::{
GString, StringName, VariantArray, VariantDispatch, VariantOperator, VariantType,
};
use crate::meta::error::ConvertError;
use crate::meta::error::{ConvertError, ErrorKind, FromVariantError};
use crate::meta::{arg_into_ref, ArrayElement, AsArg, FromGodot, ToGodot};
use godot_ffi as sys;
use std::{fmt, ptr};
Expand Down Expand Up @@ -110,10 +110,47 @@ impl Variant {
///
/// If the variant is not an object, returns `None`.
///
/// If the object is dead, the instance ID is still returned. Use [`Variant::try_to::<Gd<T>>()`][Self::try_to]
/// to retrieve only live objects.
#[cfg(since_api = "4.4")]
/// # Panics
/// If the variant holds an object and that object is dead.
///
/// If you want to detect this case, use [`try_to::<Gd<...>>()`](Self::try_to). If you want to retrieve the previous instance ID of a
/// freed object for whatever reason, use [`object_id_unchecked()`][Self::object_id_unchecked]. This method is only available from
/// Godot 4.4 onwards.
pub fn object_id(&self) -> Option<crate::obj::InstanceId> {
#[cfg(since_api = "4.4")]
{
assert!(
self.get_type() != VariantType::OBJECT || self.is_object_alive(),
"Variant::object_id(): object has been freed"
);
self.object_id_unchecked()
}

#[cfg(before_api = "4.4")]
{
match self.try_to::<crate::obj::Gd<crate::classes::Object>>() {
Ok(obj) => Some(obj.instance_id_unchecked()),
Err(c)
if matches!(
c.kind(),
ErrorKind::FromVariant(FromVariantError::DeadObject)
) =>
{
panic!("Variant::object_id(): object has been freed")
}
_ => None, // other conversion errors
}
}
}

/// For variants holding an object, returns the object's instance ID.
///
/// If the variant is not an object, returns `None`.
///
/// If the object is dead, the instance ID is still returned, similar to [`Gd::instance_id_unchecked()`][crate::obj::Gd::instance_id_unchecked].
/// Unless you have a very good reason to use this, we recommend using [`object_id()`][Self::object_id] instead.
#[cfg(since_api = "4.4")]
pub fn object_id_unchecked(&self) -> Option<crate::obj::InstanceId> {
// SAFETY: safe to call for non-object variants (returns 0).
let raw_id: u64 = unsafe { interface_fn!(variant_get_object_instance_id)(self.var_sys()) };

Expand Down
4 changes: 4 additions & 0 deletions godot-core/src/meta/error/convert_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ impl ConvertError {
pub fn into_erased(self) -> impl Error + Send + Sync {
ErasedConvertError::from(self)
}

pub(crate) fn kind(&self) -> &ErrorKind {
&self.kind
}
}

impl fmt::Display for ConvertError {
Expand Down
26 changes: 24 additions & 2 deletions itest/rust/src/builtin_tests/containers/variant_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ fn variant_get_type() {
assert_eq!(variant.get_type(), VariantType::BASIS)
}

#[cfg(since_api = "4.4")]
#[itest]
fn variant_object_id() {
let variant = Variant::nil();
Expand All @@ -257,7 +256,30 @@ fn variant_object_id() {
node.free();

// When freed, variant still returns the object ID.
assert_eq!(variant.object_id(), Some(id));
expect_panic("Variant::object_id() with freed object", || {
let _ = variant.object_id();
});
}

#[itest]
#[cfg(since_api = "4.4")]
fn variant_object_id_unchecked() {
let variant = Variant::nil();
assert_eq!(variant.object_id_unchecked(), None);

let variant = Variant::from(77);
assert_eq!(variant.object_id_unchecked(), None);

let node = Node::new_alloc();
let id = node.instance_id();

let variant = node.to_variant();
assert_eq!(variant.object_id_unchecked(), Some(id));

node.free();

// When freed, unchecked function will still return old ID.
assert_eq!(variant.object_id_unchecked(), Some(id));
}

#[itest]
Expand Down

0 comments on commit a2d4861

Please sign in to comment.