diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index b1312a1da..76f0da5d7 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -4,7 +4,6 @@ // This will also allow users to pick their own serialization format per type. use std::{any::Any, sync::Arc}; - // ----------------------------------------------------------------------------------------------- /// Like [`std::any::TypeId`], but can be serialized and deserialized. @@ -182,6 +181,18 @@ impl Element { } } + pub fn is_temp(&self) -> bool { + match self { + #[cfg(feature = "persistence")] + Self::Value { serialize_fn, .. } => serialize_fn.is_none(), + + #[cfg(not(feature = "persistence"))] + Self::Value { .. } => true, + + Self::Serialized(_) => false, + } + } + #[inline] pub(crate) fn get_temp(&self) -> Option<&T> { match self { @@ -316,6 +327,41 @@ fn from_ron_str(ron: &str) -> Option { use crate::Id; +/// The key used in [`IdTypeMap`], which is a combination of an [`Id`] and a [`TypeId`]. +/// +/// This key can be used to remove or access values in the [`IdTypeMap`] without +/// knowledge of the `TypeId` `T` that is required for other accessors. +/// +/// [`RawKey`]s make no guarantees about layout or their ability to be persisted. +/// They only produce deterministic results if they are used with the map +/// they were initially obtained from. Using them on other instances of [`IdTypeMap`] +/// may produce unexpected behavior. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct RawKey(u64); + +impl nohash_hasher::IsEnabled for RawKey {} + +impl RawKey { + /// Create a new key for the given type. + /// + /// Note that two keys with the same id but different types + /// will be different keys. + /// + /// ``` + /// use egui::{Id, util::id_type_map::RawKey}; + /// assert_ne!( + /// RawKey::new::(Id::NULL), + /// RawKey::new::(Id::NULL), + /// ); + /// ``` + #[inline(always)] + pub fn new(id: Id) -> Self { + let type_id = TypeId::of::(); + Self(type_id.value() ^ id.value()) + } +} + // TODO(emilk): make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap. /// Stores values identified by an [`Id`] AND the [`std::any::TypeId`] of the value. /// @@ -358,7 +404,7 @@ use crate::Id; #[derive(Clone, Debug)] // We use `id XOR typeid` as a key, so we don't need to hash again! pub struct IdTypeMap { - map: nohash_hasher::IntMap, + map: nohash_hasher::IntMap, max_bytes_per_type: usize, } @@ -375,16 +421,23 @@ impl Default for IdTypeMap { impl IdTypeMap { /// Insert a value that will not be persisted. #[inline] - pub fn insert_temp(&mut self, id: Id, value: T) { - let hash = hash(TypeId::of::(), id); - self.map.insert(hash, Element::new_temp(value)); + pub fn insert_temp( + &mut self, + id: Id, + value: T, + ) -> RawKey { + let key = RawKey::new::(id); + self.map.insert(key, Element::new_temp(value)); + key } /// Insert a value that will be persisted next time you start the app. #[inline] pub fn insert_persisted(&mut self, id: Id, value: T) { - let hash = hash(TypeId::of::(), id); - self.map.insert(hash, Element::new_persisted(value)); + let key = RawKey::new::(id); + self.map.insert(key, Element::new_persisted(value)); + // We don't yet return the key here, because currently all our `raw` + // methods are only for temporary values. } /// Read a value without trying to deserialize a persisted value. @@ -392,8 +445,28 @@ impl IdTypeMap { /// The call clones the value (if found), so make sure it is cheap to clone! #[inline] pub fn get_temp(&self, id: Id) -> Option { - let hash = hash(TypeId::of::(), id); - self.map.get(&hash).and_then(|x| x.get_temp()).cloned() + let key = RawKey::new::(id); + self.map.get(&key).and_then(|x| x.get_temp()).cloned() + } + + /// Gets a reference to a value for a given raw key. + /// + /// Serialized values are ignored. + pub fn get_temp_raw(&self, raw: RawKey) -> Option<&(dyn Any + Send + Sync)> { + match self.map.get(&raw)? { + Element::Value { value, .. } => Some(value.as_ref()), + Element::Serialized(_) => None, + } + } + + /// Gets a mutable reference to a value for a given raw key. + /// + /// Serialized values are ignored. + pub fn get_temp_raw_mut(&mut self, raw: RawKey) -> Option<&mut (dyn Any + Send + Sync)> { + match self.map.get_mut(&raw)? { + Element::Value { value, .. } => Some(value.as_mut()), + Element::Serialized(_) => None, + } } /// Read a value, optionally deserializing it if available. @@ -404,9 +477,9 @@ impl IdTypeMap { /// The call clones the value (if found), so make sure it is cheap to clone! #[inline] pub fn get_persisted(&mut self, id: Id) -> Option { - let hash = hash(TypeId::of::(), id); + let key = RawKey::new::(id); self.map - .get_mut(&hash) + .get_mut(&key) .and_then(|x| x.get_mut_persisted()) .cloned() } @@ -443,9 +516,9 @@ impl IdTypeMap { id: Id, insert_with: impl FnOnce() -> T, ) -> &mut T { - let hash = hash(TypeId::of::(), id); + let key = RawKey::new::(id); use std::collections::hash_map::Entry; - match self.map.entry(hash) { + match self.map.entry(key) { Entry::Vacant(vacant) => { // this unwrap will never panic, because we insert correct type right now #[expect(clippy::unwrap_used)] @@ -465,9 +538,9 @@ impl IdTypeMap { id: Id, insert_with: impl FnOnce() -> T, ) -> &mut T { - let hash = hash(TypeId::of::(), id); + let key = RawKey::new::(id); use std::collections::hash_map::Entry; - match self.map.entry(hash) { + match self.map.entry(key) { Entry::Vacant(vacant) => { // this unwrap will never panic, because we insert correct type right now #[expect(clippy::unwrap_used)] @@ -486,7 +559,7 @@ impl IdTypeMap { #[cfg(feature = "persistence")] #[allow(clippy::allow_attributes, unused)] fn get_generation(&self, id: Id) -> Option { - let element = self.map.get(&hash(TypeId::of::(), id))?; + let element = self.map.get(&RawKey::new::(id))?; match element { Element::Value { .. } => Some(0), Element::Serialized(SerializedElement { generation, .. }) => Some(*generation), @@ -496,18 +569,33 @@ impl IdTypeMap { /// Remove the state of this type and id. #[inline] pub fn remove(&mut self, id: Id) { - let hash = hash(TypeId::of::(), id); - self.map.remove(&hash); + let key = RawKey::new::(id); + self.map.remove(&key); } /// Remove and fetch the state of this type and id. #[inline] pub fn remove_temp(&mut self, id: Id) -> Option { - let hash = hash(TypeId::of::(), id); - let mut element = self.map.remove(&hash)?; + let key = RawKey::new::(id); + let mut element = self.map.remove(&key)?; Some(std::mem::take(element.get_mut_temp()?)) } + /// Remove a temporary value given a raw key. + /// + /// Serialized values are ignored. + pub fn remove_temp_raw(&mut self, raw: RawKey) -> Option> { + use std::collections::hash_map::Entry; + if let Entry::Occupied(e) = self.map.entry(raw) + && e.get().is_temp() + && let Element::Value { value, .. } = e.remove() + { + Some(value) + } else { + None + } + } + /// Note all state of the given type. pub fn remove_by_type(&mut self) { let key = TypeId::of::(); @@ -532,6 +620,18 @@ impl IdTypeMap { self.map.len() } + /// Returns all [`RawKey`]s to values in this map. + /// + /// The returned keys can only be used with this map. + /// + /// Serializable values will be ignored. + pub fn temp_keys(&self) -> impl Iterator { + self.map + .iter() + .filter(|(_, v)| v.is_temp()) + .map(|(k, _)| *k) + } + /// Count how many values are stored but not yet deserialized. #[inline] pub fn count_serialized(&self) -> usize { @@ -576,11 +676,6 @@ impl IdTypeMap { } } -#[inline(always)] -fn hash(type_id: TypeId, id: Id) -> u64 { - type_id.value() ^ id.value() -} - // ---------------------------------------------------------------------------- /// How [`IdTypeMap`] is persisted. @@ -613,13 +708,13 @@ impl PersistedMap { { profiling::scope!("gather"); - for (hash, element) in &map.map { + for (key, element) in &map.map { if let Some(element) = element.to_serialize() { let stats = types_map.entry(element.type_id).or_default(); stats.num_bytes += element.ron.len(); let generation_stats = stats.generations.entry(element.generation).or_default(); generation_stats.num_bytes += element.ron.len(); - generation_stats.elements.push((*hash, element)); + generation_stats.elements.push((key.0, element)); } else { // temporary value that shouldn't be serialized } @@ -659,7 +754,7 @@ impl PersistedMap { .into_iter() .map( |( - hash, + raw, SerializedElement { type_id, ron, @@ -667,7 +762,7 @@ impl PersistedMap { }, )| { ( - hash, + RawKey(raw), Element::Serialized(SerializedElement { type_id, ron, diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 78c6e7435..25257421a 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -656,14 +656,13 @@ impl TableState { } fn store(self, ui: &egui::Ui, state_id: egui::Id) { - #![expect(clippy::needless_return)] #[cfg(feature = "serde")] { - return ui.data_mut(|d| d.insert_persisted(state_id, self)); + ui.data_mut(|d| d.insert_persisted(state_id, self)); } #[cfg(not(feature = "serde"))] { - return ui.data_mut(|d| d.insert_temp(state_id, self)); + ui.data_mut(|d| d.insert_temp(state_id, self)); } }