Skip to main content

godot_core/obj/
instance_id.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
9use std::num::NonZeroU64;
10
11use crate::meta::error::{ConvertError, FromGodotError};
12use crate::meta::shape::GodotShape;
13use crate::meta::{FromGodot, GodotConvert, ToGodot};
14use crate::registry::property::SimpleVar;
15
16/// Represents a non-zero instance ID.
17///
18/// This is its own type for type safety and to deal with the inconsistent representation in Godot as both `u64` (C++) and `i64` (GDScript).
19/// You can usually treat this as an opaque value and pass it to and from GDScript; there are conversion methods however.
20#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
21#[repr(transparent)]
22pub struct InstanceId {
23    // Note: in the public API, signed i64 is the canonical representation.
24    //
25    // Methods converting to/from u64 exist only because GDExtension tends to work with u64. However, user-facing APIs
26    // interact with GDScript, which uses i64. Not having two representations avoids confusion about negative values.
27    value: NonZeroU64,
28}
29
30impl InstanceId {
31    /// Constructs an instance ID from an integer, or `None` if the integer is zero.
32    ///
33    /// This does *not* check if the instance is valid.
34    pub fn try_from_i64(id: i64) -> Option<Self> {
35        Self::try_from_u64(id as u64)
36    }
37
38    /// ⚠️ Constructs an instance ID from a non-zero integer, or panics.
39    ///
40    /// This does *not* check if the instance is valid.
41    ///
42    /// # Panics
43    /// If `id` is zero. Use [`try_from_i64`][Self::try_from_i64] if you are unsure.
44    pub fn from_i64(id: i64) -> Self {
45        Self::try_from_i64(id).expect("expected non-zero instance ID")
46    }
47
48    // Private: see rationale above
49    pub(crate) fn try_from_u64(id: u64) -> Option<Self> {
50        NonZeroU64::new(id).map(|value| Self { value })
51    }
52
53    pub fn to_i64(self) -> i64 {
54        self.to_u64() as i64
55    }
56
57    /// Returns if the obj being referred-to is inheriting `RefCounted`.
58    ///
59    /// This is a very fast operation and involves no engine round-trip, as the information is encoded in the ID itself.
60    pub fn is_ref_counted(self) -> bool {
61        self.to_u64() & (1u64 << 63) != 0
62    }
63
64    /// Dynamically checks if the instance behind the ID exists.
65    ///
66    /// Rather slow, involves engine round-trip plus object DB lookup. If you need the object, use
67    /// [`Gd::from_instance_id()`][crate::obj::Gd::from_instance_id] instead.
68    ///
69    /// This corresponds to Godot's global function `is_instance_id_valid()`.
70    #[doc(alias = "is_instance_id_valid")]
71    pub fn lookup_validity(self) -> bool {
72        crate::global::is_instance_id_valid(self.to_i64())
73    }
74
75    // Private: see rationale above
76    pub(crate) fn to_u64(self) -> u64 {
77        self.value.get()
78    }
79}
80
81impl Display for InstanceId {
82    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
83        write!(f, "{}", self.to_i64())
84    }
85}
86
87impl Debug for InstanceId {
88    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
89        write!(f, "InstanceId({})", self.to_i64())
90    }
91}
92
93impl GodotConvert for InstanceId {
94    // Use i64 and not u64 because the former can be represented in Variant, and is also the number format GDScript uses.
95    // The engine's C++ code can still use u64.
96    type Via = i64;
97
98    fn godot_shape() -> GodotShape {
99        GodotShape::of_builtin::<Self::Via>()
100    }
101}
102
103impl ToGodot for InstanceId {
104    type Pass = crate::meta::ByValue;
105
106    fn to_godot(&self) -> Self::Via {
107        self.to_i64()
108    }
109}
110
111impl FromGodot for InstanceId {
112    fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
113        Self::try_from_i64(via).ok_or_else(|| FromGodotError::ZeroInstanceId.into_error(via))
114    }
115}
116
117impl SimpleVar for InstanceId {}