Skip to content

Commit 44855bf

Browse files
committed
Fallible from_bits, Identifier repr optimisations
1 parent fb029f3 commit 44855bf

File tree

3 files changed

+149
-36
lines changed

3 files changed

+149
-36
lines changed

crates/bevy_ecs/src/entity/mod.rs

+33-25
Original file line numberDiff line numberDiff line change
@@ -267,20 +267,37 @@ impl Entity {
267267
/// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`].
268268
///
269269
/// Only useful when applied to results from `to_bits` in the same instance of an application.
270-
#[inline(always)]
270+
///
271+
/// # Panics
272+
///
273+
/// This method will likely panic if given `u64` values that did not come from [`Entity::to_bits`].
274+
#[inline]
271275
pub const fn from_bits(bits: u64) -> Self {
272-
let high = IdentifierMask::get_high(bits);
273-
let kind = IdentifierMask::extract_kind_from_high(high) as u8;
276+
// Construct an Identifier initially to extract the kind from.
277+
let id = Self::try_from_bits(bits);
274278

275-
// Needed to convert the kind to an integer so that the comparison can be const
276-
assert!(
277-
kind == IdKind::Entity as u8,
278-
"Attempted to initialise invalid bits as an entity"
279-
);
279+
match id {
280+
Ok(entity) => entity,
281+
Err(_) => panic!("Attempted to initialise invalid bits as an entity"),
282+
}
283+
}
280284

281-
Self {
282-
index: IdentifierMask::get_low(bits),
283-
generation: high,
285+
/// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`].
286+
///
287+
/// Only useful when applied to results from `to_bits` in the same instance of an application.
288+
///
289+
/// This method is the fallible counterpart to [`Entity::from_bits`].
290+
#[inline(always)]
291+
pub const fn try_from_bits(bits: u64) -> Result<Self, IdentifierError> {
292+
let id = Identifier::from_bits(bits);
293+
let kind = id.kind();
294+
295+
match kind {
296+
IdKind::Entity => Ok(Self {
297+
index: id.low(),
298+
generation: id.high(),
299+
}),
300+
_ => Err(IdentifierError::InvalidEntityId(bits)),
284301
}
285302
}
286303

@@ -307,23 +324,16 @@ impl Entity {
307324
impl TryFrom<Identifier> for Entity {
308325
type Error = IdentifierError;
309326

327+
#[inline]
310328
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
311-
let kind = IdentifierMask::extract_kind_from_high(value.high());
312-
313-
if kind == IdKind::Entity {
314-
Ok(Entity {
315-
index: value.low(),
316-
generation: value.high(),
317-
})
318-
} else {
319-
Err(IdentifierError::InvalidEntityId(value.to_bits()))
320-
}
329+
Self::try_from_bits(value.to_bits())
321330
}
322331
}
323332

324333
impl From<Entity> for Identifier {
334+
#[inline]
325335
fn from(value: Entity) -> Self {
326-
Identifier::new(value.index(), value.generation(), IdKind::Entity)
336+
Identifier::from_bits(value.to_bits())
327337
}
328338
}
329339

@@ -343,9 +353,7 @@ impl<'de> Deserialize<'de> for Entity {
343353
{
344354
let id: u64 = serde::de::Deserialize::deserialize(deserializer)?;
345355

346-
// Try converting from an `Identifier` as this will allow us to determine
347-
// if it is a valid entity or not
348-
Entity::try_from(Identifier::from_bits(id)).map_err(serde::de::Error::custom)
356+
Entity::try_from_bits(id).map_err(serde::de::Error::custom)
349357
}
350358
}
351359

crates/bevy_ecs/src/identifier/kinds.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/// of the ID.
44
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55
#[repr(u8)]
6-
pub(crate) enum IdKind {
6+
pub enum IdKind {
77
/// An ID variant that is compatible with [`crate::entity::Entity`].
88
Entity = 0,
99
/// A future ID variant.

crates/bevy_ecs/src/identifier/mod.rs

+115-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! [`Identifier`]s cannot be created directly, only able to be converted from other
55
//! compatible IDs.
66
use self::{kinds::IdKind, masks::IdentifierMask};
7+
use std::hash::Hash;
78

89
pub mod error;
910
pub(crate) mod kinds;
@@ -13,16 +14,24 @@ pub(crate) mod masks;
1314
/// Has the same size as a `u64` integer, but the layout is split between a 32-bit low
1415
/// segment, a 31-bit high segment, and the significant bit reserved as type flags to denote
1516
/// entity kinds.
16-
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17+
#[derive(Debug, Clone, Copy)]
18+
// Alignment repr necessary to allow LLVM to better output
19+
// optimised codegen for `to_bits`, `PartialEq` and `Ord`.
20+
#[repr(C, align(8))]
1721
pub struct Identifier {
22+
// Do not reorder the fields here. The ordering is explicitly used by repr(C)
23+
// to make this struct equivalent to a u64.
24+
#[cfg(target_endian = "little")]
1825
low: u32,
1926
high: u32,
27+
#[cfg(target_endian = "big")]
28+
low: u32,
2029
}
2130

2231
impl Identifier {
2332
/// Construct a new [`Identifier`]. The `high` parameter is masked with the
2433
/// `kind` so to pack the high value and bit flags into the same field.
25-
#[inline]
34+
#[inline(always)]
2635
#[must_use]
2736
pub(crate) const fn new(low: u32, high: u32, kind: IdKind) -> Self {
2837
// the high bits are masked to cut off the most significant bit
@@ -38,34 +47,98 @@ impl Identifier {
3847
}
3948

4049
/// Returns the value of the low segment of the [`Identifier`].
41-
#[inline]
42-
pub(crate) const fn low(self) -> u32 {
50+
#[inline(always)]
51+
pub const fn low(self) -> u32 {
4352
self.low
4453
}
4554

4655
/// Returns the value of the high segment of the [`Identifier`]. This
4756
/// does not apply any masking.
48-
#[inline]
49-
pub(crate) const fn high(self) -> u32 {
57+
#[inline(always)]
58+
pub const fn high(self) -> u32 {
5059
self.high
5160
}
5261

62+
/// Returns the masked value of the high segment of the [`Identifier`].
63+
/// Does not include the flag bits.
64+
#[inline(always)]
65+
pub const fn masked_high(self) -> u32 {
66+
IdentifierMask::extract_value_from_high(self.high())
67+
}
68+
69+
/// Returns the kind of [`Identifier`] from the high segment.
70+
#[inline(always)]
71+
pub const fn kind(self) -> IdKind {
72+
IdentifierMask::extract_kind_from_high(self.high())
73+
}
74+
5375
/// Convert the [`Identifier`] into a `u64`.
54-
#[inline]
55-
pub(crate) const fn to_bits(self) -> u64 {
76+
#[inline(always)]
77+
pub const fn to_bits(self) -> u64 {
5678
IdentifierMask::pack_into_u64(self.low, self.high)
5779
}
5880

5981
/// Convert a `u64` into an [`Identifier`].
60-
#[inline]
61-
pub(crate) const fn from_bits(value: u64) -> Self {
82+
#[inline(always)]
83+
pub const fn from_bits(value: u64) -> Self {
6284
Self {
6385
low: IdentifierMask::get_low(value),
6486
high: IdentifierMask::get_high(value),
6587
}
6688
}
6789
}
6890

91+
// By not short-circuiting in comparisons, we get better codegen.
92+
// See <https://github.com/rust-lang/rust/issues/117800>
93+
impl PartialEq for Identifier {
94+
#[inline]
95+
fn eq(&self, other: &Self) -> bool {
96+
// By using `to_bits`, the codegen can be optimised out even
97+
// further potentially. Relies on the correct alignment/field
98+
// order of `Entity`.
99+
self.to_bits() == other.to_bits()
100+
}
101+
}
102+
103+
impl Eq for Identifier {}
104+
105+
// The derive macro codegen output is not optimal and can't be optimised as well
106+
// by the compiler. This impl resolves the issue of non-optimal codegen by relying
107+
// on comparing against the bit representation of `Entity` instead of comparing
108+
// the fields. The result is then LLVM is able to optimise the codegen for Entity
109+
// far beyond what the derive macro can.
110+
// See <https://github.com/rust-lang/rust/issues/106107>
111+
impl PartialOrd for Identifier {
112+
#[inline]
113+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
114+
// Make use of our `Ord` impl to ensure optimal codegen output
115+
Some(self.cmp(other))
116+
}
117+
}
118+
119+
// The derive macro codegen output is not optimal and can't be optimised as well
120+
// by the compiler. This impl resolves the issue of non-optimal codegen by relying
121+
// on comparing against the bit representation of `Entity` instead of comparing
122+
// the fields. The result is then LLVM is able to optimise the codegen for Entity
123+
// far beyond what the derive macro can.
124+
// See <https://github.com/rust-lang/rust/issues/106107>
125+
impl Ord for Identifier {
126+
#[inline]
127+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
128+
// This will result in better codegen for ordering comparisons, plus
129+
// avoids pitfalls with regards to macro codegen relying on property
130+
// position when we want to compare against the bit representation.
131+
self.to_bits().cmp(&other.to_bits())
132+
}
133+
}
134+
135+
impl Hash for Identifier {
136+
#[inline]
137+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
138+
self.to_bits().hash(state);
139+
}
140+
}
141+
69142
#[cfg(test)]
70143
mod tests {
71144
use super::*;
@@ -100,4 +173,36 @@ mod tests {
100173
IdKind::Entity
101174
);
102175
}
176+
177+
#[rustfmt::skip]
178+
#[test]
179+
fn id_comparison() {
180+
// This is intentionally testing `lt` and `ge` as separate functions.
181+
#![allow(clippy::nonminimal_bool)]
182+
183+
assert!(Identifier::new(123, 456, IdKind::Entity) == Identifier::new(123, 456, IdKind::Entity));
184+
assert!(Identifier::new(123, 456, IdKind::Placeholder) == Identifier::new(123, 456, IdKind::Placeholder));
185+
assert!(Identifier::new(123, 789, IdKind::Entity) != Identifier::new(123, 456, IdKind::Entity));
186+
assert!(Identifier::new(123, 456, IdKind::Entity) != Identifier::new(123, 789, IdKind::Entity));
187+
assert!(Identifier::new(123, 456, IdKind::Entity) != Identifier::new(456, 123, IdKind::Entity));
188+
assert!(Identifier::new(123, 456, IdKind::Entity) != Identifier::new(123, 456, IdKind::Placeholder));
189+
190+
// ordering is by flag then high then by low
191+
192+
assert!(Identifier::new(123, 456, IdKind::Entity) >= Identifier::new(123, 456, IdKind::Entity));
193+
assert!(Identifier::new(123, 456, IdKind::Entity) <= Identifier::new(123, 456, IdKind::Entity));
194+
assert!(!(Identifier::new(123, 456, IdKind::Entity) < Identifier::new(123, 456, IdKind::Entity)));
195+
assert!(!(Identifier::new(123, 456, IdKind::Entity) > Identifier::new(123, 456, IdKind::Entity)));
196+
197+
assert!(Identifier::new(9, 1, IdKind::Entity) < Identifier::new(1, 9, IdKind::Entity));
198+
assert!(Identifier::new(1, 9, IdKind::Entity) > Identifier::new(9, 1, IdKind::Entity));
199+
200+
assert!(Identifier::new(9, 1, IdKind::Entity) < Identifier::new(9, 1, IdKind::Placeholder));
201+
assert!(Identifier::new(1, 9, IdKind::Placeholder) > Identifier::new(1, 9, IdKind::Entity));
202+
203+
assert!(Identifier::new(1, 1, IdKind::Entity) < Identifier::new(2, 1, IdKind::Entity));
204+
assert!(Identifier::new(1, 1, IdKind::Entity) <= Identifier::new(2, 1, IdKind::Entity));
205+
assert!(Identifier::new(2, 2, IdKind::Entity) > Identifier::new(1, 2, IdKind::Entity));
206+
assert!(Identifier::new(2, 2, IdKind::Entity) >= Identifier::new(1, 2, IdKind::Entity));
207+
}
103208
}

0 commit comments

Comments
 (0)