Skip to content

Commit

Permalink
Niche-optimize Id so that Option<Id> is the same size as Id
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Feb 1, 2024
1 parent 3a1244f commit 45f56bf
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 12 deletions.
40 changes: 29 additions & 11 deletions crates/egui/src/id.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// TODO(emilk): have separate types `PositionId` and `UniqueId`. ?

use std::num::NonZeroU64;

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

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

pub(crate) const fn background() -> Self {
Self(1)
#[inline]
const fn from_hash(hash: u64) -> Self {
if let Some(nonzero) = NonZeroU64::new(hash) {
Self(nonzero)
} else {
Self(NonZeroU64::MIN) // The hash was exactly zero (very bad luck)
}
}

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

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

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

/// The inner value of the [`Id`].
///
/// This is a high-entropy hash, or [`Self::NULL`].
#[inline(always)]
pub(crate) fn value(&self) -> u64 {
self.0
pub fn value(&self) -> u64 {
self.0.get()
}

#[cfg(feature = "accesskit")]
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
self.0.into()
self.value().into()
}
}

Expand All @@ -92,6 +104,12 @@ impl From<String> for Id {
}
}

#[test]
fn id_size() {
assert_eq!(std::mem::size_of::<Id>(), 8);
assert_eq!(std::mem::size_of::<Option<Id>>(), 8);
}

// ----------------------------------------------------------------------------

// Idea taken from the `nohash_hasher` crate.
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ impl LayerId {
pub fn background() -> Self {
Self {
order: Order::Background,
id: Id::background(),
id: Id::new("background"),
}
}

Expand Down

0 comments on commit 45f56bf

Please sign in to comment.