1#[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#[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#[repr(C, align(8))]
28pub struct Identifier {
29 #[cfg(target_endian = "little")]
32 low: u32,
33 high: NonZero<u32>,
34 #[cfg(target_endian = "big")]
35 low: u32,
36}
37
38impl Identifier {
39 #[inline(always)]
42 pub const fn new(low: u32, high: u32, kind: IdKind) -> Result<Self, IdentifierError> {
43 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 packed_high == 0 {
54 Err(IdentifierError::InvalidIdentifier)
55 } else {
56 unsafe {
59 Ok(Self {
60 low,
61 high: NonZero::<u32>::new_unchecked(packed_high),
62 })
63 }
64 }
65 }
66
67 #[inline(always)]
69 pub const fn low(self) -> u32 {
70 self.low
71 }
72
73 #[inline(always)]
76 pub const fn high(self) -> NonZero<u32> {
77 self.high
78 }
79
80 #[inline(always)]
83 pub const fn masked_high(self) -> u32 {
84 IdentifierMask::extract_value_from_high(self.high.get())
85 }
86
87 #[inline(always)]
89 pub const fn kind(self) -> IdKind {
90 IdentifierMask::extract_kind_from_high(self.high.get())
91 }
92
93 #[inline(always)]
95 pub const fn to_bits(self) -> u64 {
96 IdentifierMask::pack_into_u64(self.low, self.high.get())
97 }
98
99 #[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 #[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
131impl PartialEq for Identifier {
134 #[inline]
135 fn eq(&self, other: &Self) -> bool {
136 self.to_bits() == other.to_bits()
140 }
141}
142
143impl Eq for Identifier {}
144
145impl PartialOrd for Identifier {
152 #[inline]
153 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
154 Some(self.cmp(other))
156 }
157}
158
159impl Ord for Identifier {
166 #[inline]
167 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
168 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 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)] 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 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}