Skip to content

Commit 8860930

Browse files
authored
Niche-optimize Id so that Option<Id> is the same size as Id (#3932)
This is an optimization for places that use `Option<Id>`
1 parent 3a1244f commit 8860930

File tree

2 files changed

+30
-12
lines changed

2 files changed

+30
-12
lines changed

crates/egui/src/id.rs

+29-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// TODO(emilk): have separate types `PositionId` and `UniqueId`. ?
22

3+
use std::num::NonZeroU64;
4+
35
/// egui tracks widgets frame-to-frame using [`Id`]s.
46
///
57
/// For instance, if you start dragging a slider one frame, egui stores
@@ -25,49 +27,59 @@
2527
///
2628
/// Then there are widgets that need no identifiers at all, like labels,
2729
/// because they have no state nor are interacted with.
30+
///
31+
/// This is niche-optimized to that `Option<Id>` is the same size as `Id`.
2832
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
2933
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
30-
pub struct Id(u64);
34+
pub struct Id(NonZeroU64);
3135

3236
impl Id {
3337
/// A special [`Id`], in particular as a key to [`crate::Memory::data`]
3438
/// for when there is no particular widget to attach the data.
3539
///
3640
/// The null [`Id`] is still a valid id to use in all circumstances,
3741
/// though obviously it will lead to a lot of collisions if you do use it!
38-
pub const NULL: Self = Self(0);
42+
pub const NULL: Self = Self(NonZeroU64::MAX);
3943

40-
pub(crate) const fn background() -> Self {
41-
Self(1)
44+
#[inline]
45+
const fn from_hash(hash: u64) -> Self {
46+
if let Some(nonzero) = NonZeroU64::new(hash) {
47+
Self(nonzero)
48+
} else {
49+
Self(NonZeroU64::MIN) // The hash was exactly zero (very bad luck)
50+
}
4251
}
4352

4453
/// Generate a new [`Id`] by hashing some source (e.g. a string or integer).
4554
pub fn new(source: impl std::hash::Hash) -> Self {
46-
Self(epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
55+
Self::from_hash(epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source))
4756
}
4857

4958
/// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument.
5059
pub fn with(self, child: impl std::hash::Hash) -> Self {
5160
use std::hash::{BuildHasher, Hasher};
5261
let mut hasher = epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher();
53-
hasher.write_u64(self.0);
62+
hasher.write_u64(self.0.get());
5463
child.hash(&mut hasher);
55-
Self(hasher.finish())
64+
Self::from_hash(hasher.finish())
5665
}
5766

5867
/// Short and readable summary
5968
pub fn short_debug_format(&self) -> String {
60-
format!("{:04X}", self.0 as u16)
69+
format!("{:04X}", self.value() as u16)
6170
}
6271

72+
/// The inner value of the [`Id`].
73+
///
74+
/// This is a high-entropy hash, or [`Self::NULL`].
6375
#[inline(always)]
64-
pub(crate) fn value(&self) -> u64 {
65-
self.0
76+
pub fn value(&self) -> u64 {
77+
self.0.get()
6678
}
6779

6880
#[cfg(feature = "accesskit")]
6981
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
70-
self.0.into()
82+
self.value().into()
7183
}
7284
}
7385

@@ -92,6 +104,12 @@ impl From<String> for Id {
92104
}
93105
}
94106

107+
#[test]
108+
fn id_size() {
109+
assert_eq!(std::mem::size_of::<Id>(), 8);
110+
assert_eq!(std::mem::size_of::<Option<Id>>(), 8);
111+
}
112+
95113
// ----------------------------------------------------------------------------
96114

97115
// Idea taken from the `nohash_hasher` crate.

crates/egui/src/layers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl LayerId {
9090
pub fn background() -> Self {
9191
Self {
9292
order: Order::Background,
93-
id: Id::background(),
93+
id: Id::new("background"),
9494
}
9595
}
9696

0 commit comments

Comments
 (0)