bevy_ecs/identifier/
mod.rs

1//! A module for the unified [`Identifier`] ID struct, for use as a representation
2//! of multiple types of IDs in a single, packed type. Allows for describing an [`crate::entity::Entity`],
3//! or other IDs that can be packed and expressed within a `u64` sized type.
4//! [`Identifier`]s cannot be created directly, only able to be converted from other
5//! compatible IDs.
6#[cfg(feature = "bevy_reflect")]
7use bevy_reflect::Reflect;
8
9use self::{error::IdentifierError, kinds::IdKind, masks::IdentifierMask};
10use core::{hash::Hash, num::NonZero};
11
12pub mod error;
13pub(crate) mod kinds;
14pub(crate) mod masks;
15
16/// A unified identifier for all entity and similar IDs.
17///
18/// Has the same size as a `u64` integer, but the layout is split between a 32-bit low
19/// segment, a 31-bit high segment, and the significant bit reserved as type flags to denote
20/// entity kinds.
21#[derive(Debug, Clone, Copy)]
22#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
23#[cfg_attr(feature = "bevy_reflect", reflect(opaque))]
24#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Hash, PartialEq))]
25// Alignment repr necessary to allow LLVM to better output
26// optimized codegen for `to_bits`, `PartialEq` and `Ord`.
27#[repr(C, align(8))]
28pub struct Identifier {
29    // Do not reorder the fields here. The ordering is explicitly used by repr(C)
30    // to make this struct equivalent to a u64.
31    #[cfg(target_endian = "little")]
32    low: u32,
33    high: NonZero<u32>,
34    #[cfg(target_endian = "big")]
35    low: u32,
36}
37
38impl Identifier {
39    /// Construct a new [`Identifier`]. The `high` parameter is masked with the
40    /// `kind` so to pack the high value and bit flags into the same field.
41    #[inline(always)]
42    pub const fn new(low: u32, high: u32, kind: IdKind) -> Result<Self, IdentifierError> {
43        // the high bits are masked to cut off the most significant bit
44        // as these are used for the type flags. This means that the high
45        // portion is only 31 bits, but this still provides 2^31
46        // values/kinds/ids that can be stored in this segment.
47        let masked_value = IdentifierMask::extract_value_from_high(high);
48
49        let packed_high = IdentifierMask::pack_kind_into_high(masked_value, kind);
50
51        // If the packed high component ends up being zero, that means that we tried
52        // to initialize an Identifier into an invalid state.
53        if packed_high == 0 {
54            Err(IdentifierError::InvalidIdentifier)
55        } else {
56            // SAFETY: The high value has been checked to ensure it is never
57            // zero.
58            unsafe {
59                Ok(Self {
60                    low,
61                    high: NonZero::<u32>::new_unchecked(packed_high),
62                })
63            }
64        }
65    }
66
67    /// Returns the value of the low segment of the [`Identifier`].
68    #[inline(always)]
69    pub const fn low(self) -> u32 {
70        self.low
71    }
72
73    /// Returns the value of the high segment of the [`Identifier`]. This
74    /// does not apply any masking.
75    #[inline(always)]
76    pub const fn high(self) -> NonZero<u32> {
77        self.high
78    }
79
80    /// Returns the masked value of the high segment of the [`Identifier`].
81    /// Does not include the flag bits.
82    #[inline(always)]
83    pub const fn masked_high(self) -> u32 {
84        IdentifierMask::extract_value_from_high(self.high.get())
85    }
86
87    /// Returns the kind of [`Identifier`] from the high segment.
88    #[inline(always)]
89    pub const fn kind(self) -> IdKind {
90        IdentifierMask::extract_kind_from_high(self.high.get())
91    }
92
93    /// Convert the [`Identifier`] into a `u64`.
94    #[inline(always)]
95    pub const fn to_bits(self) -> u64 {
96        IdentifierMask::pack_into_u64(self.low, self.high.get())
97    }
98
99    /// Convert a `u64` into an [`Identifier`].
100    ///
101    /// # Panics
102    ///
103    /// This method will likely panic if given `u64` values that did not come from [`Identifier::to_bits`].
104    #[inline(always)]
105    pub const fn from_bits(value: u64) -> Self {
106        let id = Self::try_from_bits(value);
107
108        match id {
109            Ok(id) => id,
110            Err(_) => panic!("Attempted to initialize invalid bits as an id"),
111        }
112    }
113
114    /// Convert a `u64` into an [`Identifier`].
115    ///
116    /// This method is the fallible counterpart to [`Identifier::from_bits`].
117    #[inline(always)]
118    pub const fn try_from_bits(value: u64) -> Result<Self, IdentifierError> {
119        let high = NonZero::<u32>::new(IdentifierMask::get_high(value));
120
121        match high {
122            Some(high) => Ok(Self {
123                low: IdentifierMask::get_low(value),
124                high,
125            }),
126            None => Err(IdentifierError::InvalidIdentifier),
127        }
128    }
129}
130
131// By not short-circuiting in comparisons, we get better codegen.
132// See <https://github.com/rust-lang/rust/issues/117800>
133impl PartialEq for Identifier {
134    #[inline]
135    fn eq(&self, other: &Self) -> bool {
136        // By using `to_bits`, the codegen can be optimized out even
137        // further potentially. Relies on the correct alignment/field
138        // order of `Entity`.
139        self.to_bits() == other.to_bits()
140    }
141}
142
143impl Eq for Identifier {}
144
145// The derive macro codegen output is not optimal and can't be optimized as well
146// by the compiler. This impl resolves the issue of non-optimal codegen by relying
147// on comparing against the bit representation of `Entity` instead of comparing
148// the fields. The result is then LLVM is able to optimize the codegen for Entity
149// far beyond what the derive macro can.
150// See <https://github.com/rust-lang/rust/issues/106107>
151impl PartialOrd for Identifier {
152    #[inline]
153    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
154        // Make use of our `Ord` impl to ensure optimal codegen output
155        Some(self.cmp(other))
156    }
157}
158
159// The derive macro codegen output is not optimal and can't be optimized as well
160// by the compiler. This impl resolves the issue of non-optimal codegen by relying
161// on comparing against the bit representation of `Entity` instead of comparing
162// the fields. The result is then LLVM is able to optimize the codegen for Entity
163// far beyond what the derive macro can.
164// See <https://github.com/rust-lang/rust/issues/106107>
165impl Ord for Identifier {
166    #[inline]
167    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
168        // This will result in better codegen for ordering comparisons, plus
169        // avoids pitfalls with regards to macro codegen relying on property
170        // position when we want to compare against the bit representation.
171        self.to_bits().cmp(&other.to_bits())
172    }
173}
174
175impl Hash for Identifier {
176    #[inline]
177    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
178        self.to_bits().hash(state);
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn id_construction() {
188        let id = Identifier::new(12, 55, IdKind::Entity).unwrap();
189
190        assert_eq!(id.low(), 12);
191        assert_eq!(id.high().get(), 55);
192        assert_eq!(
193            IdentifierMask::extract_kind_from_high(id.high().get()),
194            IdKind::Entity
195        );
196    }
197
198    #[test]
199    fn from_bits() {
200        // This high value should correspond to the max high() value
201        // and also Entity flag.
202        let high = 0x7FFFFFFF;
203        let low = 0xC;
204        let bits: u64 = high << u32::BITS | low;
205
206        let id = Identifier::try_from_bits(bits).unwrap();
207
208        assert_eq!(id.to_bits(), 0x7FFFFFFF0000000C);
209        assert_eq!(id.low(), low as u32);
210        assert_eq!(id.high().get(), 0x7FFFFFFF);
211        assert_eq!(
212            IdentifierMask::extract_kind_from_high(id.high().get()),
213            IdKind::Entity
214        );
215    }
216
217    #[rustfmt::skip]
218    #[test]
219    #[allow(clippy::nonminimal_bool)] // This is intentionally testing `lt` and `ge` as separate functions.
220    fn id_comparison() {
221        assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() == Identifier::new(123, 456, IdKind::Entity).unwrap());
222        assert!(Identifier::new(123, 456, IdKind::Placeholder).unwrap() == Identifier::new(123, 456, IdKind::Placeholder).unwrap());
223        assert!(Identifier::new(123, 789, IdKind::Entity).unwrap() != Identifier::new(123, 456, IdKind::Entity).unwrap());
224        assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(123, 789, IdKind::Entity).unwrap());
225        assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(456, 123, IdKind::Entity).unwrap());
226        assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(123, 456, IdKind::Placeholder).unwrap());
227
228        // ordering is by flag then high then by low
229
230        assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() >= Identifier::new(123, 456, IdKind::Entity).unwrap());
231        assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() <= Identifier::new(123, 456, IdKind::Entity).unwrap());
232        assert!(!(Identifier::new(123, 456, IdKind::Entity).unwrap() < Identifier::new(123, 456, IdKind::Entity).unwrap()));
233        assert!(!(Identifier::new(123, 456, IdKind::Entity).unwrap() > Identifier::new(123, 456, IdKind::Entity).unwrap()));
234
235        assert!(Identifier::new(9, 1, IdKind::Entity).unwrap() < Identifier::new(1, 9, IdKind::Entity).unwrap());
236        assert!(Identifier::new(1, 9, IdKind::Entity).unwrap() > Identifier::new(9, 1, IdKind::Entity).unwrap());
237
238        assert!(Identifier::new(9, 1, IdKind::Entity).unwrap() < Identifier::new(9, 1, IdKind::Placeholder).unwrap());
239        assert!(Identifier::new(1, 9, IdKind::Placeholder).unwrap() > Identifier::new(1, 9, IdKind::Entity).unwrap());
240
241        assert!(Identifier::new(1, 1, IdKind::Entity).unwrap() < Identifier::new(2, 1, IdKind::Entity).unwrap());
242        assert!(Identifier::new(1, 1, IdKind::Entity).unwrap() <= Identifier::new(2, 1, IdKind::Entity).unwrap());
243        assert!(Identifier::new(2, 2, IdKind::Entity).unwrap() > Identifier::new(1, 2, IdKind::Entity).unwrap());
244        assert!(Identifier::new(2, 2, IdKind::Entity).unwrap() >= Identifier::new(1, 2, IdKind::Entity).unwrap());
245    }
246}