Skip to content

Commit

Permalink
Merge pull request #914 from godot-rust/feature/variant-object-id
Browse files Browse the repository at this point in the history
Add `Variant::object_id()`
  • Loading branch information
Bromeon authored Nov 23, 2024
2 parents f8a0939 + 9a0d8ba commit 8a62088
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 15 deletions.
6 changes: 4 additions & 2 deletions godot-core/src/builtin/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ impl Callable {

/// Returns the object on which this callable is called.
///
/// Returns `None` when this callable doesn't have any target object to call a method on, regardless of
/// if the method exists for that target or not.
/// Returns `None` when this callable doesn't have any target object to call a method on (regardless of whether the method exists for that
/// target or not). Also returns `None` if the object is dead. You can differentiate these two cases using [`object_id()`][Self::object_id].
///
/// _Godot equivalent: `get_object`_
pub fn object(&self) -> Option<Gd<Object>> {
Expand All @@ -236,6 +236,8 @@ impl Callable {
///
/// Returns `None` when this callable doesn't have any target to call a method on.
///
/// If the pointed-to object is dead, the ID will still be returned. Use [`object()`][Self::object] to check for liveness.
///
/// _Godot equivalent: `get_object_id`_
pub fn object_id(&self) -> Option<InstanceId> {
let id = self.as_inner().get_object_id();
Expand Down
5 changes: 4 additions & 1 deletion godot-core/src/builtin/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ impl Signal {

/// Returns the object to which this signal belongs.
///
/// Returns [`None`] when this signal doesn't have any object.
/// Returns [`None`] when this signal doesn't have any object, or the object is dead. You can differentiate these two situations using
/// [`object_id()`][Self::object_id].
///
/// _Godot equivalent: `get_object`_
pub fn object(&self) -> Option<Gd<Object>> {
Expand All @@ -137,6 +138,8 @@ impl Signal {
///
/// Returns [`None`] when this signal doesn't have any object.
///
/// If the pointed-to object is dead, the ID will still be returned. Use [`object()`][Self::object] to check for liveness.
///
/// _Godot equivalent: `get_object_id`_
pub fn object_id(&self) -> Option<InstanceId> {
let id = self.as_inner().get_object_id();
Expand Down
26 changes: 20 additions & 6 deletions godot-core/src/builtin/variant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ impl Variant {

/// Create a variant holding a non-nil value.
///
/// Equivalent to `value.to_variant()`.
/// Equivalent to [`value.to_variant()`][ToGodot::to_variant], but consumes the argument.
pub fn from<T: ToGodot>(value: T) -> Self {
value.to_variant()
}

/// ⚠️ Convert to type `T`, panicking on failure.
///
/// Equivalent to `T::from_variant(&self)`.
/// Equivalent to [`T::from_variant(&self)`][FromGodot::from_variant].
///
/// # Panics
/// When this variant holds a different type.
Expand All @@ -64,14 +64,14 @@ impl Variant {

/// Convert to type `T`, returning `Err` on failure.
///
/// Equivalent to `T::try_from_variant(&self)`.
/// Equivalent to [`T::try_from_variant(&self)`][FromGodot::try_from_variant].
pub fn try_to<T: FromGodot>(&self) -> Result<T, ConvertError> {
T::try_from_variant(self)
}

/// Checks whether the variant is empty (`null` value in GDScript).
///
/// See also [`Self::get_type`].
/// See also [`get_type()`][Self::get_type].
pub fn is_nil(&self) -> bool {
// Use get_type() rather than sys_type(), to also cover nullptr OBJECT as NIL
self.get_type() == VariantType::NIL
Expand All @@ -80,8 +80,8 @@ impl Variant {
/// Returns the type that is currently held by this variant.
///
/// If this variant holds a type `Object` but no instance (represented as a null object pointer), then `Nil` will be returned for
/// consistency. This may deviate from Godot behavior -- for example, calling `Node::get_node_or_null()` with an invalid
/// path returns a variant that has type `Object` but acts like `Nil` for all practical purposes.
/// consistency. This may deviate from Godot behavior -- for example, calling [`Node::get_node_or_null()`][crate::classes::Node::get_node_or_null]
/// with an invalid path returns a variant that has type `Object` but acts like `Nil` for all practical purposes.
pub fn get_type(&self) -> VariantType {
let sys_type = self.sys_type();

Expand All @@ -107,6 +107,20 @@ impl Variant {
}
}

/// 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. Use [`Variant::try_to::<Gd<T>>()`][Self::try_to]
/// to retrieve only live objects.
#[cfg(since_api = "4.4")]
pub fn object_id(&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()) };

crate::obj::InstanceId::try_from_u64(raw_id)
}

/// ⚠️ Calls the specified `method` with the given `args`.
///
/// Supports `Object` as well as built-ins with methods (e.g. `Array`, `Vector3`, `GString`, etc.).
Expand Down
14 changes: 10 additions & 4 deletions itest/rust/src/builtin_tests/containers/callable_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,19 @@ fn callable_hash() {

#[itest]
fn callable_object_method() {
let obj = CallableTestObj::new_gd();
let callable = obj.callable("foo");
let object = CallableTestObj::new_gd();
let object_id = object.instance_id();
let callable = object.callable("foo");

assert_eq!(callable.object(), Some(obj.clone().upcast::<Object>()));
assert_eq!(callable.object_id(), Some(obj.instance_id()));
assert_eq!(callable.object(), Some(object.clone().upcast::<Object>()));
assert_eq!(callable.object_id(), Some(object_id));
assert_eq!(callable.method_name(), Some("foo".into()));

// Invalidating the object still returns the old ID, however not the object.
drop(object);
assert_eq!(callable.object_id(), Some(object_id));
assert_eq!(callable.object(), None);

assert_eq!(Callable::invalid().object(), None);
assert_eq!(Callable::invalid().object_id(), None);
assert_eq!(Callable::invalid().method_name(), None);
Expand Down
10 changes: 8 additions & 2 deletions itest/rust/src/builtin_tests/containers/signal_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,21 @@ fn signals() {
#[itest]
fn instantiate_signal() {
let mut object = RefCounted::new_gd();
let object_id = object.instance_id();

object.add_user_signal("test_signal");

let signal = Signal::from_object_signal(&object, "test_signal");

assert!(!signal.is_null());
assert_eq!(signal.name(), StringName::from("test_signal"));
assert_eq!(signal.object().unwrap(), object.clone().upcast());
assert_eq!(signal.object_id().unwrap(), object.instance_id());
assert_eq!(signal.object(), Some(object.clone().upcast()));
assert_eq!(signal.object_id(), Some(object_id));

// Invalidating the object still returns the old ID, however not the object.
drop(object);
assert_eq!(signal.object_id(), Some(object_id));
assert_eq!(signal.object(), None);
}

#[itest]
Expand Down
21 changes: 21 additions & 0 deletions itest/rust/src/builtin_tests/containers/variant_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,27 @@ 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();
assert_eq!(variant.object_id(), None);

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

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

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

node.free();

// When freed, variant still returns the object ID.
assert_eq!(variant.object_id(), Some(id));
}

#[itest]
fn variant_equal() {
assert_eq!(Variant::nil(), ().to_variant());
Expand Down

0 comments on commit 8a62088

Please sign in to comment.