Skip to content

Commit a2d4861

Browse files
authored
Merge pull request #1034 from godot-rust/qol/variant-object-id-polyfill
Support `Variant::object_id()` for Godot <4.4; add `object_id_unchecked()`
2 parents b9b3f9f + fe50efc commit a2d4861

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

godot-core/src/builtin/variant/mod.rs

+41-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::builtin::{
99
GString, StringName, VariantArray, VariantDispatch, VariantOperator, VariantType,
1010
};
11-
use crate::meta::error::ConvertError;
11+
use crate::meta::error::{ConvertError, ErrorKind, FromVariantError};
1212
use crate::meta::{arg_into_ref, ArrayElement, AsArg, FromGodot, ToGodot};
1313
use godot_ffi as sys;
1414
use std::{fmt, ptr};
@@ -110,10 +110,47 @@ impl Variant {
110110
///
111111
/// If the variant is not an object, returns `None`.
112112
///
113-
/// If the object is dead, the instance ID is still returned. Use [`Variant::try_to::<Gd<T>>()`][Self::try_to]
114-
/// to retrieve only live objects.
115-
#[cfg(since_api = "4.4")]
113+
/// # Panics
114+
/// If the variant holds an object and that object is dead.
115+
///
116+
/// 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
117+
/// freed object for whatever reason, use [`object_id_unchecked()`][Self::object_id_unchecked]. This method is only available from
118+
/// Godot 4.4 onwards.
116119
pub fn object_id(&self) -> Option<crate::obj::InstanceId> {
120+
#[cfg(since_api = "4.4")]
121+
{
122+
assert!(
123+
self.get_type() != VariantType::OBJECT || self.is_object_alive(),
124+
"Variant::object_id(): object has been freed"
125+
);
126+
self.object_id_unchecked()
127+
}
128+
129+
#[cfg(before_api = "4.4")]
130+
{
131+
match self.try_to::<crate::obj::Gd<crate::classes::Object>>() {
132+
Ok(obj) => Some(obj.instance_id_unchecked()),
133+
Err(c)
134+
if matches!(
135+
c.kind(),
136+
ErrorKind::FromVariant(FromVariantError::DeadObject)
137+
) =>
138+
{
139+
panic!("Variant::object_id(): object has been freed")
140+
}
141+
_ => None, // other conversion errors
142+
}
143+
}
144+
}
145+
146+
/// For variants holding an object, returns the object's instance ID.
147+
///
148+
/// If the variant is not an object, returns `None`.
149+
///
150+
/// If the object is dead, the instance ID is still returned, similar to [`Gd::instance_id_unchecked()`][crate::obj::Gd::instance_id_unchecked].
151+
/// Unless you have a very good reason to use this, we recommend using [`object_id()`][Self::object_id] instead.
152+
#[cfg(since_api = "4.4")]
153+
pub fn object_id_unchecked(&self) -> Option<crate::obj::InstanceId> {
117154
// SAFETY: safe to call for non-object variants (returns 0).
118155
let raw_id: u64 = unsafe { interface_fn!(variant_get_object_instance_id)(self.var_sys()) };
119156

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

+4
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ impl ConvertError {
9191
pub fn into_erased(self) -> impl Error + Send + Sync {
9292
ErasedConvertError::from(self)
9393
}
94+
95+
pub(crate) fn kind(&self) -> &ErrorKind {
96+
&self.kind
97+
}
9498
}
9599

96100
impl fmt::Display for ConvertError {

itest/rust/src/builtin_tests/containers/variant_test.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ fn variant_get_type() {
239239
assert_eq!(variant.get_type(), VariantType::BASIS)
240240
}
241241

242-
#[cfg(since_api = "4.4")]
243242
#[itest]
244243
fn variant_object_id() {
245244
let variant = Variant::nil();
@@ -257,7 +256,30 @@ fn variant_object_id() {
257256
node.free();
258257

259258
// When freed, variant still returns the object ID.
260-
assert_eq!(variant.object_id(), Some(id));
259+
expect_panic("Variant::object_id() with freed object", || {
260+
let _ = variant.object_id();
261+
});
262+
}
263+
264+
#[itest]
265+
#[cfg(since_api = "4.4")]
266+
fn variant_object_id_unchecked() {
267+
let variant = Variant::nil();
268+
assert_eq!(variant.object_id_unchecked(), None);
269+
270+
let variant = Variant::from(77);
271+
assert_eq!(variant.object_id_unchecked(), None);
272+
273+
let node = Node::new_alloc();
274+
let id = node.instance_id();
275+
276+
let variant = node.to_variant();
277+
assert_eq!(variant.object_id_unchecked(), Some(id));
278+
279+
node.free();
280+
281+
// When freed, unchecked function will still return old ID.
282+
assert_eq!(variant.object_id_unchecked(), Some(id));
261283
}
262284

263285
#[itest]

0 commit comments

Comments
 (0)