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