From 6bd920f9578f8058c2a91ac00cbeb634b485fa69 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Oct 2021 13:51:54 +0200 Subject: [PATCH 01/11] Begin rewrite of egui::any --- egui/src/any/element.rs | 2 +- egui/src/any/id_any_map.rs | 418 +++++++++++++++++++++++++++ egui/src/any/mod.rs | 3 +- egui/src/any/serializable/any_map.rs | 24 ++ egui/src/any/serializable/element.rs | 5 +- egui/src/any/serializable/type_id.rs | 5 + egui/src/id.rs | 4 +- epaint/src/util.rs | 5 +- 8 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 egui/src/any/id_any_map.rs diff --git a/egui/src/any/element.rs b/egui/src/any/element.rs index 93b9146a5118..cafc70dfeb52 100644 --- a/egui/src/any/element.rs +++ b/egui/src/any/element.rs @@ -11,7 +11,7 @@ impl fmt::Debug for AnyMapElement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AnyMapElement") .field("value_type_id", &self.type_id()) - .finish() + .finish_non_exhaustive() } } diff --git a/egui/src/any/id_any_map.rs b/egui/src/any/id_any_map.rs new file mode 100644 index 000000000000..50a4fc9f73d6 --- /dev/null +++ b/egui/src/any/id_any_map.rs @@ -0,0 +1,418 @@ +use std::any::Any; + +// ----------------------------------------------------------------------------------------------- + +/// We need this because `TypeId` can't be deserialized or serialized directly, but this can be done using hashing. However, there is a small possibility that different types will have intersection by hashes of their type ids. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct TypeId(u64); + +impl TypeId { + #[inline] + pub fn of() -> Self { + std::any::TypeId::of::().into() + } + + #[inline(always)] + pub(crate) fn value(&self) -> u64 { + self.0 + } +} + +impl From for TypeId { + #[inline] + fn from(id: std::any::TypeId) -> Self { + Self(epaint::util::hash(id)) + } +} + +// ----------------------------------------------------------------------------------------------- + +#[cfg(feature = "serde")] +pub trait SerializableTrait: + 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync +{ +} + +#[cfg(feature = "serde")] +impl SerializableTrait for T where + T: 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync +{ +} + +#[cfg(not(feature = "serde"))] +pub trait SerializableTrait: 'static + Any + Clone + for<'a> Send + Sync {} + +#[cfg(not(feature = "serde"))] +impl SerializableTrait for T where T: 'static + Any + Clone + for<'a> Send + Sync {} + +// ----------------------------------------------------------------------------------------------- + +#[cfg(feature = "persistence")] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +struct SerializedElement { + type_id: TypeId, + ron: String, +} + +pub(crate) enum Element { + /// Serializable data + Value { + value: Box, + clone_fn: fn(&Box) -> Box, + + // None if non-serializable type. + #[cfg(feature = "persistence")] + serialize_fn: + Option) -> Result>, + }, + Serialized { + type_id: TypeId, + ron: String, + }, +} + +impl Clone for Element { + fn clone(&self) -> Self { + match &self { + Self::Value { + value, + clone_fn, + #[cfg(feature = "persistence")] + serialize_fn, + } => Self::Value { + value: clone_fn(value), + clone_fn: *clone_fn, + #[cfg(feature = "persistence")] + serialize_fn: *serialize_fn, + }, + + Self::Serialized { type_id, ron } => Self::Serialized { + type_id: *type_id, + ron: ron.clone(), + }, + } + } +} + +impl std::fmt::Debug for Element { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Self::Value { value, .. } => f + .debug_struct("MaybeSerializable::Temporary") + .field("value_type_id", &value.type_id()) + .finish_non_exhaustive(), + Self::Serialized { type_id, ron } => f + .debug_struct("MaybeSerializable::Temporary") + .field("type_id", &type_id) + .field("ron", &ron) + .finish(), + } + } +} + +impl Element { + #[inline] + pub(crate) fn new_temp(t: T) -> Self { + Self::Value { + value: Box::new(t), + clone_fn: |x| { + let x = x.downcast_ref::().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change. + Box::new(x.clone()) + }, + #[cfg(feature = "persistence")] + serialize_fn: None, + } + } + + #[inline] + pub(crate) fn new_persisted(t: T) -> Self { + Self::Value { + value: Box::new(t), + clone_fn: |x| { + let x = x.downcast_ref::().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change. + Box::new(x.clone()) + }, + #[cfg(feature = "persistence")] + serialize_fn: Some(|x| { + let x = x.downcast_ref::().unwrap(); // This will never panic too, for same reason. + ron::to_string(x) + }), + } + } + + #[inline] + pub(crate) fn type_id(&self) -> TypeId { + match self { + Self::Value { value, .. } => (**value).type_id().into(), + Self::Serialized { type_id, .. } => *type_id, + } + } + + #[inline] + pub(crate) fn get_mut_temp(&mut self) -> Option<&mut T> { + match self { + Self::Value { value, .. } => value.downcast_mut(), + Self::Serialized { .. } => None, + } + } + + pub(crate) fn get_mut_persisted(&mut self) -> Option<&mut T> { + match self { + Self::Value { value, .. } => value.downcast_mut(), + + #[cfg(feature = "persistence")] + Self::Serialized { ron, .. } => { + *self = Self::new_persisted(from_ron_str::(ron)?); + + match self { + Self::Value { value, .. } => value.downcast_mut(), + Self::Serialized { .. } => unreachable!(), + } + } + + #[cfg(not(feature = "persistence"))] + Self::Serialized { .. } => None, + } + } + + #[cfg(feature = "persistence")] + fn to_serialize(&self) -> Option { + match self { + Self::Value { + value, + serialize_fn, + .. + } => { + if let Some(serialize_fn) = serialize_fn { + let ron = serialize_fn(value).ok()?; + Some(SerializedElement { + type_id: (**value).type_id().into(), + ron, + }) + } else { + None + } + } + Self::Serialized { type_id, ron } => Some(SerializedElement { + type_id: *type_id, + ron: ron.clone(), + }), + } + } +} + +#[cfg(feature = "persistence")] +fn from_ron_str(ron: &str) -> Option { + match ron::from_str::(ron) { + Ok(value) => Some(value), + Err(err) => { + eprintln!( + "egui: Failed to deserialize {} from memory: {}, ron: {:?}", + std::any::type_name::(), + err, + ron + ); + None + } + } +} + +// ----------------------------------------------------------------------------------------------- + +use crate::Id; + +// TODO: make generic over the key, instead of using hard-coded `Id`. +/// Stores any value grouped by [`Id`] and [`TypeId`]. +/// Values can optionally be serializable. +#[derive(Clone, Debug, Default)] +pub struct IdAnyMap(nohash_hasher::IntMap); + +impl IdAnyMap { + #[inline] + pub fn get_temp(&mut self, id: Id) -> Option { + let hash = hash(TypeId::of::(), id); + self.0 + .get_mut(&hash) + .and_then(|x| x.get_mut_temp()) + .cloned() + } + + #[inline] + pub fn get_persisted(&mut self, id: Id) -> Option { + let hash = hash(TypeId::of::(), id); + self.0 + .get_mut(&hash) + .and_then(|x| x.get_mut_persisted()) + .cloned() + } + + #[inline] + pub fn insert_temp(&mut self, id: Id, value: T) { + let hash = hash(TypeId::of::(), id); + self.0.insert(hash, Element::new_temp(value)); + } + + #[inline] + pub fn insert_persisted(&mut self, id: Id, value: T) { + let hash = hash(TypeId::of::(), id); + self.0.insert(hash, Element::new_persisted(value)); + } + + #[inline] + pub fn remove(&mut self, id: Id) { + let hash = hash(TypeId::of::(), id); + self.0.remove(&hash); + } +} + +#[inline(always)] +fn hash(type_id: TypeId, id: Id) -> u64 { + type_id.value() ^ id.value() +} + +#[cfg(feature = "serde")] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct PersistedMap(Vec<(u64, SerializedElement)>); + +#[cfg(feature = "serde")] +impl PersistedMap { + fn from_map(map: &IdAnyMap) -> Self { + Self( + map.0 + .iter() + .filter_map(|(&hash, element)| Some((hash, element.to_serialize()?))) + .collect(), + ) + } + fn into_map(self) -> IdAnyMap { + IdAnyMap( + self.0 + .into_iter() + .map(|(hash, SerializedElement { type_id, ron })| { + (hash, Element::Serialized { type_id, ron }) + }) + .collect(), + ) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for IdAnyMap { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + PersistedMap::from_map(self).serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for IdAnyMap { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + ::deserialize(deserializer).map(PersistedMap::into_map) + } +} + +// ---------------------------------------------------------------------------- + +#[test] +fn test_mix() { + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + #[derive(Clone, Debug, PartialEq)] + struct Foo(i32); + + #[derive(Clone, Debug, PartialEq)] + struct Bar(f32); + + let id = Id::new("a"); + + let mut map: IdAnyMap = Default::default(); + map.insert_persisted(id, Foo(555)); + map.insert_temp(id, Bar(1.0)); + + assert_eq!(map.get_temp::(id), Some(Foo(555))); + assert_eq!(map.get_persisted::(id), Some(Foo(555))); + assert_eq!(map.get_temp::(id), Some(Bar(1.0))); + + // ------------ + // Test removal: + + // We can remove: + map.remove::(id); + assert_eq!(map.get_temp::(id), None); + + // Other type is still there, even though it is the same if: + assert_eq!(map.get_temp::(id), Some(Foo(555))); + assert_eq!(map.get_persisted::(id), Some(Foo(555))); + + // But we can still remove the last: + map.remove::(id); + assert_eq!(map.get_temp::(id), None); + assert_eq!(map.get_persisted::(id), None); +} + +#[cfg(feature = "persistence")] +#[test] +fn test_mix_serialize() { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + struct Serializable(i32); + + #[derive(Clone, Debug, PartialEq)] + struct NonSerializable(f32); + + let id = Id::new("a"); + + let mut map: IdAnyMap = Default::default(); + map.insert_persisted(id, Serializable(555)); + map.insert_temp(id, NonSerializable(1.0)); + + assert_eq!(map.get_temp::(id), Some(Serializable(555))); + assert_eq!( + map.get_persisted::(id), + Some(Serializable(555)) + ); + assert_eq!( + map.get_temp::(id), + Some(NonSerializable(1.0)) + ); + + // ----------- + + let serialized = serde_json::to_string(&map).unwrap(); + + // ------------ + // Test removal: + + // We can remove: + map.remove::(id); + assert_eq!(map.get_temp::(id), None); + + // Other type is still there, even though it is the same if: + assert_eq!(map.get_temp::(id), Some(Serializable(555))); + assert_eq!( + map.get_persisted::(id), + Some(Serializable(555)) + ); + + // But we can still remove the last: + map.remove::(id); + assert_eq!(map.get_temp::(id), None); + assert_eq!(map.get_persisted::(id), None); + + // -------------------- + // Test deserialization: + + let mut map: IdAnyMap = serde_json::from_str(&serialized).unwrap(); + assert_eq!(map.get_temp::(id), None); + assert_eq!( + map.get_persisted::(id), + Some(Serializable(555)) + ); + assert_eq!(map.get_temp::(id), Some(Serializable(555))); +} diff --git a/egui/src/any/mod.rs b/egui/src/any/mod.rs index 15ea95f74249..5b3dfcc1699a 100644 --- a/egui/src/any/mod.rs +++ b/egui/src/any/mod.rs @@ -52,10 +52,11 @@ mod any_map; mod element; +mod id_any_map; mod type_map; /// Same structs and traits, but also can be de/serialized under `persistence` feature. #[cfg(feature = "persistence")] pub mod serializable; -pub use self::{any_map::AnyMap, element::AnyMapTrait, type_map::TypeMap}; +pub use self::{any_map::AnyMap, element::AnyMapTrait, id_any_map::IdAnyMap, type_map::TypeMap}; diff --git a/egui/src/any/serializable/any_map.rs b/egui/src/any/serializable/any_map.rs index b7bad87d1cc4..45cebd933ffe 100644 --- a/egui/src/any/serializable/any_map.rs +++ b/egui/src/any/serializable/any_map.rs @@ -94,6 +94,30 @@ impl IdAnyMap { // ---------------------------------------------------------------------------- +#[test] +fn id_any_map_mix_serialize_and_not() { + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + struct Serializable(Option); + + #[derive(Clone, Debug)] + struct NonSerializable(Option); + + let mut map: IdAnyMap = Default::default(); + map.insert(Id::new("yes"), Serializable(Some("Serializable".into()))); + // map.insert_not_persisted(Id::new("not"), NonSerializable(Some("Not".into()))); + + let serialized = serde_json::to_string(&map).unwrap(); + + let mut map: IdAnyMap = serde_json::from_str(&serialized).unwrap(); + assert_eq!( + map.get::(&Id::new("yes")), + Some(&Serializable(Some("Serializable".into()))) + ); + // assert_eq!(map.get::(&1), Some(&State1 { a: 42 })); +} + // #[test] // fn discard_different_struct() { // use serde::{Deserialize, Serialize}; diff --git a/egui/src/any/serializable/element.rs b/egui/src/any/serializable/element.rs index 0b41d0ee4b85..09b510eae1bc 100644 --- a/egui/src/any/serializable/element.rs +++ b/egui/src/any/serializable/element.rs @@ -2,7 +2,6 @@ use crate::any::serializable::type_id::TypeId; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::any::Any; use std::fmt; -use AnyMapElementInner::{Deserialized, Serialized}; pub(crate) struct AnyMapElement(AnyMapElementInner); @@ -16,6 +15,8 @@ enum AnyMapElementInner { Serialized(String, TypeId), } +use AnyMapElementInner::{Deserialized, Serialized}; + #[derive(Deserialize, Serialize)] struct AnyMapElementInnerSer(String, TypeId); @@ -31,7 +32,7 @@ impl Serialize for AnyMapElement { .. } => { let s = serialize_fn(value).map_err(serde::ser::Error::custom)?; - AnyMapElementInnerSer(s, self.type_id()) + AnyMapElementInnerSer(s, (**value).type_id().into()) } Serialized(s, id) => AnyMapElementInnerSer(s.clone(), *id), }; diff --git a/egui/src/any/serializable/type_id.rs b/egui/src/any/serializable/type_id.rs index 0c99ad796f8d..f2b9b650d35b 100644 --- a/egui/src/any/serializable/type_id.rs +++ b/egui/src/any/serializable/type_id.rs @@ -10,6 +10,11 @@ impl TypeId { pub fn of() -> Self { std::any::TypeId::of::().into() } + + #[inline(always)] + pub(crate) fn value(&self) -> u64 { + self.0 + } } impl From for TypeId { diff --git a/egui/src/id.rs b/egui/src/id.rs index 73ab904291cc..1dd8be32cb0d 100644 --- a/egui/src/id.rs +++ b/egui/src/id.rs @@ -31,7 +31,7 @@ pub struct Id(u64); impl Id { pub(crate) fn background() -> Self { - Self(0) + Self(1) } /// Generate a new `Id` by hashing some source (e.g. a string or integer). @@ -64,7 +64,7 @@ impl Id { impl std::fmt::Debug for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:X}", self.0) + write!(f, "{:016X}", self.0) } } diff --git a/epaint/src/util.rs b/epaint/src/util.rs index 0f47c1f93af0..a321113d1d28 100644 --- a/epaint/src/util.rs +++ b/epaint/src/util.rs @@ -1,7 +1,10 @@ /// Hash the given value with a predictable hasher. #[inline] pub fn hash(value: impl std::hash::Hash) -> u64 { - hash_with(value, ahash::AHasher::new_with_keys(123, 456)) + use std::hash::Hasher as _; + let mut hasher = ahash::AHasher::new_with_keys(123, 456); + value.hash(&mut hasher); + hasher.finish() } /// Hash the given value with the given hasher. From 1d57c6f812dff26754a2c5814513cc8548f2a4d3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Oct 2021 17:00:36 +0200 Subject: [PATCH 02/11] unify id_data and id_data_temp --- egui/src/any/id_any_map.rs | 30 ++++++++++++++++++++ egui/src/any/serializable/type_id.rs | 5 ---- egui/src/containers/area.rs | 3 +- egui/src/containers/collapsing_header.rs | 17 +++++++---- egui/src/containers/panel.rs | 19 +++++++++---- egui/src/containers/resize.rs | 14 +++++++-- egui/src/containers/scroll_area.rs | 14 +++++++-- egui/src/containers/window.rs | 14 ++++----- egui/src/context.rs | 6 ++-- egui/src/grid.rs | 15 ++++++---- egui/src/memory.rs | 13 +-------- egui/src/menu.rs | 17 +++++------ egui/src/widgets/plot/mod.rs | 36 ++++++++++++++---------- egui/src/widgets/text_edit.rs | 19 +++++++++---- egui_demo_lib/src/apps/demo/password.rs | 8 ++++-- egui_demo_lib/src/syntax_highlighting.rs | 18 ++++++++---- 16 files changed, 160 insertions(+), 88 deletions(-) diff --git a/egui/src/any/id_any_map.rs b/egui/src/any/id_any_map.rs index 50a4fc9f73d6..49746228d11d 100644 --- a/egui/src/any/id_any_map.rs +++ b/egui/src/any/id_any_map.rs @@ -264,6 +264,36 @@ impl IdAnyMap { let hash = hash(TypeId::of::(), id); self.0.remove(&hash); } + + /// Note that this function could not remove all needed types between runs because if you upgraded the Rust version or for other reasons. + pub fn remove_by_type(&mut self) { + let key = TypeId::of::(); + self.0.retain(|_, e| { + let e: &Element = e; + e.type_id() != key + }); + } + + #[inline] + pub fn clear(&mut self) { + self.0.clear(); + } + + /// You could use this function to find is there some leak or misusage. Note, that result of this function could break between runs, if you upgraded the Rust version or for other reasons. + pub fn count(&mut self) -> usize { + let key = TypeId::of::(); + self.0 + .iter() + .filter(|(_, e)| { + let e: &Element = e; + e.type_id() == key + }) + .count() + } + + pub fn count_all(&mut self) -> usize { + self.0.len() + } } #[inline(always)] diff --git a/egui/src/any/serializable/type_id.rs b/egui/src/any/serializable/type_id.rs index f2b9b650d35b..0c99ad796f8d 100644 --- a/egui/src/any/serializable/type_id.rs +++ b/egui/src/any/serializable/type_id.rs @@ -10,11 +10,6 @@ impl TypeId { pub fn of() -> Self { std::any::TypeId::of::().into() } - - #[inline(always)] - pub(crate) fn value(&self) -> u64 { - self.0 - } } impl From for TypeId { diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index e4b9b0cc76c3..711c83834ec6 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -6,7 +6,8 @@ use std::{fmt::Debug, hash::Hash}; use crate::*; -/// State that is persisted between frames +/// State that is persisted between frames. +// TODO: this is not currently stored in `memory().id_data`, but maybe it should be? #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct State { diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 18922fbdb6e7..8600cfedc911 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -23,8 +23,16 @@ impl Default for State { } impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.memory().id_data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().id_data.insert_persisted(id, self) + } + pub fn from_memory_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self { - *ctx.memory().id_data.get_or_insert_with(id, || State { + Self::load(ctx, id).unwrap_or_else(|| State { open: default_open, ..Default::default() }) @@ -35,10 +43,7 @@ impl State { if ctx.memory().everything_is_visible() { Some(true) } else { - ctx.memory() - .id_data - .get::(&id) - .map(|state| state.open) + State::load(ctx, id).map(|state| state.open) } } @@ -380,7 +385,7 @@ impl CollapsingHeader { }) .inner }); - ui.memory().id_data.insert(id, state); + state.store(ui.ctx(), id); if let Some(ret_response) = ret_response { CollapsingResponse { diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs index ddad03e5cad4..76f0d8f5652f 100644 --- a/egui/src/containers/panel.rs +++ b/egui/src/containers/panel.rs @@ -23,6 +23,16 @@ struct PanelState { rect: Rect, } +impl PanelState { + fn load(ctx: &Context, bar_id: Id) -> Option { + ctx.memory().id_data.get_persisted(bar_id) + } + + fn store(self, ctx: &Context, bar_id: Id) { + ctx.memory().id_data.insert_persisted(bar_id, self); + } +} + // ---------------------------------------------------------------------------- /// `Left` or `Right` @@ -171,7 +181,7 @@ impl SidePanel { let mut panel_rect = available_rect; { let mut width = default_width; - if let Some(state) = ui.memory().id_data.get::(&id) { + if let Some(state) = PanelState::load(ui.ctx(), id) { width = state.rect.width(); } width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); @@ -243,7 +253,7 @@ impl SidePanel { } ui.expand_to_include_rect(rect); - ui.memory().id_data.insert(id, PanelState { rect }); + PanelState { rect }.store(ui.ctx(), id); if resize_hover || is_resizing { let stroke = if is_resizing { @@ -448,8 +458,7 @@ impl TopBottomPanel { let available_rect = ui.available_rect_before_wrap(); let mut panel_rect = available_rect; { - let state = ui.memory().id_data.get::(&id).copied(); - let mut height = if let Some(state) = state { + let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { state.rect.height() } else { default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) @@ -523,7 +532,7 @@ impl TopBottomPanel { } ui.expand_to_include_rect(rect); - ui.memory().id_data.insert(id, PanelState { rect }); + PanelState { rect }.store(ui.ctx(), id); if resize_hover || is_resizing { let stroke = if is_resizing { diff --git a/egui/src/containers/resize.rs b/egui/src/containers/resize.rs index 03069eb00d7c..d7a8529ba871 100644 --- a/egui/src/containers/resize.rs +++ b/egui/src/containers/resize.rs @@ -16,6 +16,16 @@ pub(crate) struct State { pub(crate) requested_size: Option, } +impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.memory().id_data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().id_data.insert_persisted(id, self) + } +} + /// A region that can be resized by dragging the bottom right corner. #[derive(Clone, Copy, Debug)] #[must_use = "You should call .show()"] @@ -160,7 +170,7 @@ impl Resize { ui.make_persistent_id(id_source) }); - let mut state = *ui.memory().id_data.get_or_insert_with(id, || { + let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| { ui.ctx().request_repaint(); // counter frame delay let default_size = self @@ -297,7 +307,7 @@ impl Resize { } } - ui.memory().id_data.insert(id, state); + state.store(ui.ctx(), id); if ui.ctx().style().debug.show_resize { ui.ctx().debug_painter().debug_rect( diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index aa63876af9cc..48d4db6f38f8 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -41,6 +41,16 @@ impl Default for State { } } +impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.memory().id_data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().id_data.insert_persisted(id, self) + } +} + /// Add vertical and/or horizontal scrolling to a contained [`Ui`]. /// /// ``` @@ -262,7 +272,7 @@ impl ScrollArea { let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area")); let id = ui.make_persistent_id(id_source); - let mut state = *ctx.memory().id_data.get_or_default::(id); + let mut state = State::load(&ctx, id).unwrap_or_default(); if let Some(offset) = offset { state.offset = offset; @@ -718,7 +728,7 @@ impl Prepared { state.show_scroll = show_scroll_this_frame; - ui.memory().id_data.insert(id, state); + state.store(ui.ctx(), id); } } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 8c213c1778a1..13203e1296c9 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -385,10 +385,7 @@ impl<'open> Window<'open> { ); } - area_content_ui - .memory() - .id_data - .insert(collapsing_id, collapsing); + collapsing.store(&ctx, collapsing_id); if let Some(interaction) = interaction { paint_frame_interaction( @@ -525,11 +522,10 @@ fn interact( area.state_mut().pos = new_rect.min; if window_interaction.is_resize() { - ctx.memory() - .id_data - .get_mut::(&resize_id) - .unwrap() - .requested_size = Some(new_rect.size() - margins); + if let Some(mut state) = resize::State::load(ctx, resize_id) { + state.requested_size = Some(new_rect.size() - margins); + state.store(ctx, resize_id); + } } ctx.memory().areas.move_to_top(area_layer_id); diff --git a/egui/src/context.rs b/egui/src/context.rs index 4de2edbf8a73..c29a02e1e7b5 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -937,12 +937,10 @@ impl Context { ui.horizontal(|ui| { ui.label(format!( "{} menu bars", - self.memory().id_data_temp.count::() + self.memory().id_data.count::() )); if ui.button("Reset").clicked() { - self.memory() - .id_data_temp - .remove_by_type::(); + self.memory().id_data.remove_by_type::(); } }); diff --git a/egui/src/grid.rs b/egui/src/grid.rs index 7ad6943e88e2..b5968603438b 100644 --- a/egui/src/grid.rs +++ b/egui/src/grid.rs @@ -8,6 +8,14 @@ pub(crate) struct State { } impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.memory().id_data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().id_data.insert_persisted(id, self) + } + fn set_min_col_width(&mut self, col: usize, width: f32) { self.col_widths .resize(self.col_widths.len().max(col + 1), 0.0); @@ -62,7 +70,7 @@ pub(crate) struct GridLayout { impl GridLayout { pub(crate) fn new(ui: &Ui, id: Id) -> Self { - let prev_state = ui.memory().id_data.get_or_default::(id).clone(); + let prev_state = State::load(ui.ctx(), id).unwrap_or_default(); // TODO: respect current layout @@ -213,10 +221,7 @@ impl GridLayout { pub(crate) fn save(&self) { if self.curr_state != self.prev_state { - self.ctx - .memory() - .id_data - .insert(self.id, self.curr_state.clone()); + self.curr_state.clone().store(&self.ctx, self.id); self.ctx.request_repaint(); } } diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 76c7385faada..5bf35f9c4ba3 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -37,18 +37,7 @@ pub struct Memory { /// This map stores current states for all widgets with custom `Id`s. /// This will be saved between different program runs if you use the `persistence` feature. - #[cfg(feature = "persistence")] - pub id_data: any::serializable::IdAnyMap, - - /// This map stores current states for all widgets with custom `Id`s. - /// This will be saved between different program runs if you use the `persistence` feature. - #[cfg(not(feature = "persistence"))] - #[cfg_attr(feature = "serde", serde(skip))] - pub id_data: any::AnyMap, - - /// Same as `id_data`, but this data will not be saved between runs. - #[cfg_attr(feature = "serde", serde(skip))] - pub id_data_temp: any::AnyMap, + pub id_data: any::IdAnyMap, // ------------------------------------------ /// Can be used to cache computations from one frame to another. diff --git a/egui/src/menu.rs b/egui/src/menu.rs index ae31ca5fc0bd..d9472a7769aa 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -31,16 +31,17 @@ pub(crate) struct BarState { } impl BarState { - fn load(ctx: &Context, bar_id: &Id) -> Self { + fn load(ctx: &Context, bar_id: Id) -> Self { ctx.memory() - .id_data_temp - .get_or_default::(*bar_id) - .clone() + .id_data + .get_temp::(bar_id) + .unwrap_or_default() } - fn save(self, ctx: &Context, bar_id: Id) { - ctx.memory().id_data_temp.insert(bar_id, self); + fn store(self, ctx: &Context, bar_id: Id) { + ctx.memory().id_data.insert_temp(bar_id, self); } + /// Show a menu at pointer if right-clicked response. /// Should be called from [`Context`] on a [`Response`] pub fn bar_menu( @@ -161,7 +162,7 @@ fn stationary_menu_impl<'c, R>( let bar_id = ui.id(); let menu_id = bar_id.with(&title); - let mut bar_state = BarState::load(ui.ctx(), &bar_id); + let mut bar_state = BarState::load(ui.ctx(), bar_id); let mut button = Button::new(title); @@ -173,7 +174,7 @@ fn stationary_menu_impl<'c, R>( let button_response = ui.add(button); let inner = bar_state.bar_menu(&button_response, add_contents); - bar_state.save(ui.ctx(), bar_id); + bar_state.store(ui.ctx(), bar_id); InnerResponse::new(inner.map(|r| r.inner), button_response) } diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 82fc08561186..74b16acd7cea 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -30,6 +30,16 @@ struct PlotMemory { min_auto_bounds: Bounds, } +impl PlotMemory { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.memory().id_data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().id_data.insert_persisted(id, self) + } +} + // ---------------------------------------------------------------------------- /// A 2D plot, e.g. a graph of a function. @@ -342,10 +352,8 @@ impl Widget for Plot { } = self; let plot_id = ui.make_persistent_id(id_source); - let mut memory = ui - .memory() - .id_data - .get_mut_or_insert_with(plot_id, || PlotMemory { + let mut memory = PlotMemory::load(ui.ctx(), plot_id) + .unwrap_or_else(|| PlotMemory { bounds: min_auto_bounds, auto_bounds: !min_auto_bounds.is_valid(), hovered_entry: None, @@ -363,7 +371,7 @@ impl Widget for Plot { min_auto_bounds, ..memory }; - ui.memory().id_data.insert(plot_id, memory.clone()); + memory.clone().store(ui.ctx(), plot_id); } let PlotMemory { @@ -511,16 +519,14 @@ impl Widget for Plot { hovered_entry = legend.get_hovered_entry_name(); } - ui.memory().id_data.insert( - plot_id, - PlotMemory { - bounds, - auto_bounds, - hovered_entry, - hidden_items, - min_auto_bounds, - }, - ); + let memory = PlotMemory { + bounds, + auto_bounds, + hovered_entry, + hidden_items, + min_auto_bounds, + }; + memory.store(ui.ctx(), plot_id); if show_x || show_y { response.on_hover_cursor(CursorIcon::Crosshair) diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index 28779cf7727e..e08147cd5035 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -21,6 +21,16 @@ pub(crate) struct State { singleline_offset: f32, } +impl State { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.memory().id_data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().id_data.insert_persisted(id, self) + } +} + #[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct CursorPair { @@ -433,10 +443,7 @@ impl<'t> TextEdit<'t> { impl<'t> TextEdit<'t> { pub fn cursor(ui: &Ui, id: Id) -> Option { - ui.memory() - .id_data - .get::(&id) - .and_then(|state| state.cursorp) + State::load(ui.ctx(), id).and_then(|state| state.cursorp) } } @@ -589,7 +596,7 @@ impl<'t> TextEdit<'t> { auto_id // Since we are only storing the cursor a persistent Id is not super important } }); - let mut state = ui.memory().id_data.get_or_default::(id).clone(); + let mut state = State::load(ui.ctx(), id).unwrap_or_default().clone(); // On touch screens (e.g. mobile in egui_web), should // dragging select text, or scroll the enclosing `ScrollArea` (if any)? @@ -927,7 +934,7 @@ impl<'t> TextEdit<'t> { } } - ui.memory().id_data.insert(id, state); + state.store(ui.ctx(), id); let selection_changed = if let (Some(text_cursor), Some(prev_text_cursor)) = (text_cursor, prev_text_cursor) diff --git a/egui_demo_lib/src/apps/demo/password.rs b/egui_demo_lib/src/apps/demo/password.rs index 4b040d5a744c..11caff614c89 100644 --- a/egui_demo_lib/src/apps/demo/password.rs +++ b/egui_demo_lib/src/apps/demo/password.rs @@ -27,7 +27,11 @@ pub fn password_ui(ui: &mut egui::Ui, text: &mut String) -> egui::Response { // You can read more about available `Memory` functions in the documentation of `egui::Memory` // struct and `egui::any` module. // You should get state by value, not by reference to avoid borrowing of `Memory`. - let mut plaintext = *ui.memory().id_data_temp.get_or_default::(id); + let mut plaintext = ui + .memory() + .id_data + .get_temp::(id) + .unwrap_or_default(); // 4. Process ui, change a local copy of the state // We want TextEdit to fill entire space, and have button after that, so in that case we can @@ -51,7 +55,7 @@ pub fn password_ui(ui: &mut egui::Ui, text: &mut String) -> egui::Response { }); // 5. Insert changed state back - ui.memory().id_data_temp.insert(id, plaintext); + ui.memory().id_data.insert_temp(id, plaintext); // All done! Return the interaction response so the user can check what happened // (hovered, clicked, …) and maybe show a tooltip: diff --git a/egui_demo_lib/src/syntax_highlighting.rs b/egui_demo_lib/src/syntax_highlighting.rs index f6a2fa5b7bd3..fb1c400914bf 100644 --- a/egui_demo_lib/src/syntax_highlighting.rs +++ b/egui_demo_lib/src/syntax_highlighting.rs @@ -146,21 +146,27 @@ impl CodeTheme { pub fn from_memory(ctx: &egui::Context) -> Self { if ctx.style().visuals.dark_mode { - *ctx.memory() + ctx.memory() .id_data - .get_or_insert_with(egui::Id::new("dark"), CodeTheme::dark) + .get_persisted(egui::Id::new("dark")) + .unwrap_or_else(CodeTheme::dark) } else { - *ctx.memory() + ctx.memory() .id_data - .get_or_insert_with(egui::Id::new("light"), CodeTheme::light) + .get_persisted(egui::Id::new("light")) + .unwrap_or_else(CodeTheme::light) } } pub fn store_in_memory(&self, ctx: &egui::Context) { if self.dark_mode { - ctx.memory().id_data.insert(egui::Id::new("dark"), *self); + ctx.memory() + .id_data + .insert_persisted(egui::Id::new("dark"), *self); } else { - ctx.memory().id_data.insert(egui::Id::new("light"), *self); + ctx.memory() + .id_data + .insert_persisted(egui::Id::new("light"), *self); } } } From f701a54e923bcb2e292af5fc16b9ac9bab7cd3db Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Oct 2021 23:26:25 +0200 Subject: [PATCH 03/11] unify data and id_data --- egui/src/any/id_any_map.rs | 137 +++++++++++++++++++++-- egui/src/any/serializable/element.rs | 2 +- egui/src/containers/area.rs | 2 +- egui/src/containers/collapsing_header.rs | 4 +- egui/src/containers/panel.rs | 4 +- egui/src/containers/popup.rs | 22 ++-- egui/src/containers/resize.rs | 4 +- egui/src/containers/scroll_area.rs | 4 +- egui/src/containers/window.rs | 2 +- egui/src/context.rs | 16 +-- egui/src/grid.rs | 4 +- egui/src/id.rs | 9 ++ egui/src/memory.rs | 25 +---- egui/src/menu.rs | 4 +- egui/src/widgets/color_picker.rs | 32 ++---- egui/src/widgets/plot/mod.rs | 20 ++-- egui/src/widgets/text_edit.rs | 6 +- egui_demo_lib/src/apps/demo/password.rs | 8 +- egui_demo_lib/src/syntax_highlighting.rs | 16 ++- 19 files changed, 211 insertions(+), 110 deletions(-) diff --git a/egui/src/any/id_any_map.rs b/egui/src/any/id_any_map.rs index 49746228d11d..84f10748935d 100644 --- a/egui/src/any/id_any_map.rs +++ b/egui/src/any/id_any_map.rs @@ -1,3 +1,8 @@ +// TODO: it is possible we can simplify `Element` further by +// assuming everything is "faalible" serializable, and by supplying serialize/deserialize functions for them. +// For non-serializable types, these simply return `None`. +// This will also allow users to pick their own serialization format per type. + use std::any::Any; // ----------------------------------------------------------------------------------------------- @@ -29,22 +34,22 @@ impl From for TypeId { // ----------------------------------------------------------------------------------------------- #[cfg(feature = "serde")] -pub trait SerializableTrait: +pub trait SerializableAny: 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync { } #[cfg(feature = "serde")] -impl SerializableTrait for T where +impl SerializableAny for T where T: 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync { } #[cfg(not(feature = "serde"))] -pub trait SerializableTrait: 'static + Any + Clone + for<'a> Send + Sync {} +pub trait SerializableAny: 'static + Any + Clone + for<'a> Send + Sync {} #[cfg(not(feature = "serde"))] -impl SerializableTrait for T where T: 'static + Any + Clone + for<'a> Send + Sync {} +impl SerializableAny for T where T: 'static + Any + Clone + for<'a> Send + Sync {} // ----------------------------------------------------------------------------------------------- @@ -55,7 +60,7 @@ struct SerializedElement { ron: String, } -pub(crate) enum Element { +enum Element { /// Serializable data Value { value: Box, @@ -126,7 +131,7 @@ impl Element { } #[inline] - pub(crate) fn new_persisted(t: T) -> Self { + pub(crate) fn new_persisted(t: T) -> Self { Self::Value { value: Box::new(t), clone_fn: |x| { @@ -157,7 +162,58 @@ impl Element { } } - pub(crate) fn get_mut_persisted(&mut self) -> Option<&mut T> { + #[inline] + pub(crate) fn get_temp_mut_or_insert_with( + &mut self, + insert_with: impl FnOnce() -> T, + ) -> &mut T { + match self { + Self::Value { value, .. } => { + if !value.is::() { + *self = Self::new_temp(insert_with()); + } + } + Self::Serialized { .. } => { + *self = Self::new_temp(insert_with()); + } + } + + match self { + Self::Value { value, .. } => value.downcast_mut().unwrap(), // This unwrap will never panic because we already converted object to required type + Self::Serialized { .. } => unreachable!(), + } + } + + #[inline] + pub(crate) fn get_persisted_mut_or_insert_with( + &mut self, + insert_with: impl FnOnce() -> T, + ) -> &mut T { + match self { + Self::Value { value, .. } => { + if !value.is::() { + *self = Self::new_persisted(insert_with()); + } + } + + #[cfg(feature = "persistence")] + Self::Serialized { ron, .. } => { + *self = Self::new_persisted(from_ron_str::(ron).unwrap_or_else(insert_with)); + } + + #[cfg(not(feature = "persistence"))] + Self::Serialized { .. } => { + *self = Self::new_persisted(insert_with()); + } + } + + match self { + Self::Value { value, .. } => value.downcast_mut().unwrap(), // This unwrap will never panic because we already converted object to required type + Self::Serialized { .. } => unreachable!(), + } + } + + pub(crate) fn get_mut_persisted(&mut self) -> Option<&mut T> { match self { Self::Value { value, .. } => value.downcast_mut(), @@ -239,7 +295,7 @@ impl IdAnyMap { } #[inline] - pub fn get_persisted(&mut self, id: Id) -> Option { + pub fn get_persisted(&mut self, id: Id) -> Option { let hash = hash(TypeId::of::(), id); self.0 .get_mut(&hash) @@ -247,6 +303,69 @@ impl IdAnyMap { .cloned() } + #[inline] + pub fn get_temp_mut_or( + &mut self, + id: Id, + or_insert: T, + ) -> &mut T { + self.get_temp_mut_or_insert_with(id, || or_insert) + } + + #[inline] + pub fn get_persisted_mut_or(&mut self, id: Id, or_insert: T) -> &mut T { + self.get_persisted_mut_or_insert_with(id, || or_insert) + } + + #[inline] + pub fn get_temp_mut_or_default( + &mut self, + id: Id, + ) -> &mut T { + self.get_temp_mut_or_insert_with(id, Default::default) + } + + #[inline] + pub fn get_persisted_mut_or_default(&mut self, id: Id) -> &mut T { + self.get_persisted_mut_or_insert_with(id, Default::default) + } + + pub fn get_temp_mut_or_insert_with( + &mut self, + id: Id, + insert_with: impl FnOnce() -> T, + ) -> &mut T { + let hash = hash(TypeId::of::(), id); + use std::collections::hash_map::Entry; + match self.0.entry(hash) { + Entry::Vacant(vacant) => vacant + .insert(Element::new_temp(insert_with())) + .get_mut_temp() + .unwrap(), // this unwrap will never panic, because we insert correct type right now + Entry::Occupied(occupied) => { + occupied.into_mut().get_temp_mut_or_insert_with(insert_with) + } + } + } + + pub fn get_persisted_mut_or_insert_with( + &mut self, + id: Id, + insert_with: impl FnOnce() -> T, + ) -> &mut T { + let hash = hash(TypeId::of::(), id); + use std::collections::hash_map::Entry; + match self.0.entry(hash) { + Entry::Vacant(vacant) => vacant + .insert(Element::new_persisted(insert_with())) + .get_mut_persisted() + .unwrap(), // this unwrap will never panic, because we insert correct type right now + Entry::Occupied(occupied) => occupied + .into_mut() + .get_persisted_mut_or_insert_with(insert_with), + } + } + #[inline] pub fn insert_temp(&mut self, id: Id, value: T) { let hash = hash(TypeId::of::(), id); @@ -254,7 +373,7 @@ impl IdAnyMap { } #[inline] - pub fn insert_persisted(&mut self, id: Id, value: T) { + pub fn insert_persisted(&mut self, id: Id, value: T) { let hash = hash(TypeId::of::(), id); self.0.insert(hash, Element::new_persisted(value)); } diff --git a/egui/src/any/serializable/element.rs b/egui/src/any/serializable/element.rs index 09b510eae1bc..f03232e56976 100644 --- a/egui/src/any/serializable/element.rs +++ b/egui/src/any/serializable/element.rs @@ -146,7 +146,7 @@ impl AnyMapElement { } } Serialized(s, _) => { - *self = Self::new(from_ron_str::(s).unwrap_or_else(|| set_with())); + *self = Self::new(from_ron_str::(s).unwrap_or_else(set_with)); } } diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index 711c83834ec6..9ba2359971d5 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -7,7 +7,7 @@ use std::{fmt::Debug, hash::Hash}; use crate::*; /// State that is persisted between frames. -// TODO: this is not currently stored in `memory().id_data`, but maybe it should be? +// TODO: this is not currently stored in `memory().data`, but maybe it should be? #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct State { diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 8600cfedc911..7cb337e71338 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -24,11 +24,11 @@ impl Default for State { impl State { pub fn load(ctx: &Context, id: Id) -> Option { - ctx.memory().id_data.get_persisted(id) + ctx.memory().data.get_persisted(id) } pub fn store(self, ctx: &Context, id: Id) { - ctx.memory().id_data.insert_persisted(id, self) + ctx.memory().data.insert_persisted(id, self); } pub fn from_memory_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self { diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs index 76f0d8f5652f..5488667eb5be 100644 --- a/egui/src/containers/panel.rs +++ b/egui/src/containers/panel.rs @@ -25,11 +25,11 @@ struct PanelState { impl PanelState { fn load(ctx: &Context, bar_id: Id) -> Option { - ctx.memory().id_data.get_persisted(bar_id) + ctx.memory().data.get_persisted(bar_id) } fn store(self, ctx: &Context, bar_id: Id) { - ctx.memory().id_data.insert_persisted(bar_id, self); + ctx.memory().data.insert_persisted(bar_id, self); } } diff --git a/egui/src/containers/popup.rs b/egui/src/containers/popup.rs index b17c5a08f7da..ac3368909e0f 100644 --- a/egui/src/containers/popup.rs +++ b/egui/src/containers/popup.rs @@ -12,6 +12,14 @@ pub(crate) struct MonoState { } impl MonoState { + fn load(ctx: &Context) -> Option { + ctx.memory().data.get_temp(Id::null()) + } + + fn store(self, ctx: &Context) { + ctx.memory().data.insert_temp(Id::null(), self); + } + fn tooltip_size(&self, id: Id, index: usize) -> Option { if self.last_id == Some(id) { self.last_size.get(index).cloned() @@ -167,11 +175,8 @@ fn show_tooltip_at_avoid_dyn<'c, R>( return None; // No good place for a tooltip :( }; - let expected_size = ctx - .memory() - .data_temp - .get_or_default::() - .tooltip_size(id, count); + let mut state = MonoState::load(ctx).unwrap_or_default(); + let expected_size = state.tooltip_size(id, count); let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0)); if above { @@ -199,10 +204,9 @@ fn show_tooltip_at_avoid_dyn<'c, R>( let position = position.at_least(ctx.input().screen_rect().min); let InnerResponse { inner, response } = show_tooltip_area_dyn(ctx, id, position, add_contents); - ctx.memory() - .data_temp - .get_mut_or_default::() - .set_tooltip_size(id, count, response.rect.size()); + + state.set_tooltip_size(id, count, response.rect.size()); + state.store(ctx); ctx.frame_state().tooltip_rect = Some((id, tooltip_rect.union(response.rect), count + 1)); Some(inner) diff --git a/egui/src/containers/resize.rs b/egui/src/containers/resize.rs index d7a8529ba871..903d669a9c15 100644 --- a/egui/src/containers/resize.rs +++ b/egui/src/containers/resize.rs @@ -18,11 +18,11 @@ pub(crate) struct State { impl State { pub fn load(ctx: &Context, id: Id) -> Option { - ctx.memory().id_data.get_persisted(id) + ctx.memory().data.get_persisted(id) } pub fn store(self, ctx: &Context, id: Id) { - ctx.memory().id_data.insert_persisted(id, self) + ctx.memory().data.insert_persisted(id, self); } } diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index 48d4db6f38f8..040bf507cfdf 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -43,11 +43,11 @@ impl Default for State { impl State { pub fn load(ctx: &Context, id: Id) -> Option { - ctx.memory().id_data.get_persisted(id) + ctx.memory().data.get_persisted(id) } pub fn store(self, ctx: &Context, id: Id) { - ctx.memory().id_data.insert_persisted(id, self) + ctx.memory().data.insert_persisted(id, self); } } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 13203e1296c9..d4abd7fe41c6 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -385,7 +385,7 @@ impl<'open> Window<'open> { ); } - collapsing.store(&ctx, collapsing_id); + collapsing.store(ctx, collapsing_id); if let Some(interaction) = interaction { paint_frame_interaction( diff --git a/egui/src/context.rs b/egui/src/context.rs index c29a02e1e7b5..5d72ab86d37a 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -924,12 +924,12 @@ impl Context { ui.label(format!( "{} collapsing headers", self.memory() - .id_data + .data .count::() )); if ui.button("Reset").clicked() { self.memory() - .id_data + .data .remove_by_type::(); } }); @@ -937,30 +937,30 @@ impl Context { ui.horizontal(|ui| { ui.label(format!( "{} menu bars", - self.memory().id_data.count::() + self.memory().data.count::() )); if ui.button("Reset").clicked() { - self.memory().id_data.remove_by_type::(); + self.memory().data.remove_by_type::(); } }); ui.horizontal(|ui| { ui.label(format!( "{} scroll areas", - self.memory().id_data.count::() + self.memory().data.count::() )); if ui.button("Reset").clicked() { - self.memory().id_data.remove_by_type::(); + self.memory().data.remove_by_type::(); } }); ui.horizontal(|ui| { ui.label(format!( "{} resize areas", - self.memory().id_data.count::() + self.memory().data.count::() )); if ui.button("Reset").clicked() { - self.memory().id_data.remove_by_type::(); + self.memory().data.remove_by_type::(); } }); diff --git a/egui/src/grid.rs b/egui/src/grid.rs index b5968603438b..f29d61414fa1 100644 --- a/egui/src/grid.rs +++ b/egui/src/grid.rs @@ -9,11 +9,11 @@ pub(crate) struct State { impl State { pub fn load(ctx: &Context, id: Id) -> Option { - ctx.memory().id_data.get_persisted(id) + ctx.memory().data.get_persisted(id) } pub fn store(self, ctx: &Context, id: Id) { - ctx.memory().id_data.insert_persisted(id, self) + ctx.memory().data.insert_persisted(id, self); } fn set_min_col_width(&mut self, col: usize, width: f32) { diff --git a/egui/src/id.rs b/egui/src/id.rs index 1dd8be32cb0d..9bb563b79ae5 100644 --- a/egui/src/id.rs +++ b/egui/src/id.rs @@ -30,6 +30,15 @@ pub struct Id(u64); impl Id { + /// A special `Id`, in particular as a key to [`crate::Memory::id_map`] + /// 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 fn null() -> Self { + Self(0) + } + pub(crate) fn background() -> Self { Self(1) } diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 5bf35f9c4ba3..977c7dc78aca 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -11,33 +11,20 @@ use crate::{any, area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style /// /// If you want this to persist when closing your app you should serialize `Memory` and store it. /// -/// If you want to store data for your widgets, you should look at `data`/`data_temp` and -/// `id_data`/`id_data_temp` fields, and read the documentation of [`any`] module. +/// If you want to store data for your widgets, you should look at [`Memory::data`] #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct Memory { pub options: Options, - // ------------------------------------------ - /// This map stores current states for widgets that don't require `Id`. - /// This will be saved between different program runs if you use the `persistence` feature. - #[cfg(feature = "persistence")] - pub data: any::serializable::TypeMap, - - /// This map stores current states for widgets that don't require `Id`. - /// This will be saved between different program runs if you use the `persistence` feature. - #[cfg(not(feature = "persistence"))] - #[cfg_attr(feature = "serde", serde(skip))] - pub data: any::TypeMap, - - /// Same as `data`, but this data will not be saved between runs. - #[cfg_attr(feature = "serde", serde(skip))] - pub data_temp: any::TypeMap, - /// This map stores current states for all widgets with custom `Id`s. + /// /// This will be saved between different program runs if you use the `persistence` feature. - pub id_data: any::IdAnyMap, + /// + /// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key. + #[cfg(feature = "persistence")] + pub data: any::IdAnyMap, // ------------------------------------------ /// Can be used to cache computations from one frame to another. diff --git a/egui/src/menu.rs b/egui/src/menu.rs index d9472a7769aa..820ebc4a78d8 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -33,13 +33,13 @@ pub(crate) struct BarState { impl BarState { fn load(ctx: &Context, bar_id: Id) -> Self { ctx.memory() - .id_data + .data .get_temp::(bar_id) .unwrap_or_default() } fn store(self, ctx: &Context, bar_id: Id) { - ctx.memory().id_data.insert_temp(bar_id, self); + ctx.memory().data.insert_temp(bar_id, self); } /// Show a menu at pointer if right-clicked response. diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 794713507d59..c709d1ae1cfd 100644 --- a/egui/src/widgets/color_picker.rs +++ b/egui/src/widgets/color_picker.rs @@ -318,24 +318,14 @@ pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> b // To ensure we keep hue slider when `srgba` is gray we store the // full `Hsva` in a cache: - let mut hsva = ui - .ctx() - .memory() - .data_temp - .get_or_default::>() - .get(srgba) - .cloned() + let mut hsva = use_color_cache(ui.ctx(), |cc| cc.get(srgba).cloned()) .unwrap_or_else(|| Hsva::from(*srgba)); let response = color_picker_hsva_2d(ui, &mut hsva, alpha); *srgba = Color32::from(hsva); - ui.ctx() - .memory() - .data_temp - .get_mut_or_default::>() - .set(*srgba, hsva); + use_color_cache(ui.ctx(), |cc| cc.set(*srgba, hsva)); response } @@ -382,24 +372,18 @@ pub fn color_edit_button_srgba(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) - // To ensure we keep hue slider when `srgba` is gray we store the // full `Hsva` in a cache: - let mut hsva = ui - .ctx() - .memory() - .data_temp - .get_or_default::>() - .get(srgba) - .cloned() + let mut hsva = use_color_cache(ui.ctx(), |cc| cc.get(srgba).cloned()) .unwrap_or_else(|| Hsva::from(*srgba)); let response = color_edit_button_hsva(ui, &mut hsva, alpha); *srgba = Color32::from(hsva); - ui.ctx() - .memory() - .data_temp - .get_mut_or_default::>() - .set(*srgba, hsva); + use_color_cache(ui.ctx(), |cc| cc.set(*srgba, hsva)); response } + +fn use_color_cache(ctx: &Context, f: impl FnOnce(&mut FixedCache) -> R) -> R { + f(ctx.memory().data.get_temp_mut_or_default(Id::null())) +} diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index 74b16acd7cea..98ad69a23c23 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -32,11 +32,11 @@ struct PlotMemory { impl PlotMemory { pub fn load(ctx: &Context, id: Id) -> Option { - ctx.memory().id_data.get_persisted(id) + ctx.memory().data.get_persisted(id) } pub fn store(self, ctx: &Context, id: Id) { - ctx.memory().id_data.insert_persisted(id, self) + ctx.memory().data.insert_persisted(id, self); } } @@ -352,15 +352,13 @@ impl Widget for Plot { } = self; let plot_id = ui.make_persistent_id(id_source); - let mut memory = PlotMemory::load(ui.ctx(), plot_id) - .unwrap_or_else(|| PlotMemory { - bounds: min_auto_bounds, - auto_bounds: !min_auto_bounds.is_valid(), - hovered_entry: None, - hidden_items: Default::default(), - min_auto_bounds, - }) - .clone(); + let mut memory = PlotMemory::load(ui.ctx(), plot_id).unwrap_or_else(|| PlotMemory { + bounds: min_auto_bounds, + auto_bounds: !min_auto_bounds.is_valid(), + hovered_entry: None, + hidden_items: Default::default(), + min_auto_bounds, + }); // If the min bounds changed, recalculate everything. if min_auto_bounds != memory.min_auto_bounds { diff --git a/egui/src/widgets/text_edit.rs b/egui/src/widgets/text_edit.rs index e08147cd5035..a7dd5f8ee13f 100644 --- a/egui/src/widgets/text_edit.rs +++ b/egui/src/widgets/text_edit.rs @@ -23,11 +23,11 @@ pub(crate) struct State { impl State { pub fn load(ctx: &Context, id: Id) -> Option { - ctx.memory().id_data.get_persisted(id) + ctx.memory().data.get_persisted(id) } pub fn store(self, ctx: &Context, id: Id) { - ctx.memory().id_data.insert_persisted(id, self) + ctx.memory().data.insert_persisted(id, self); } } @@ -596,7 +596,7 @@ impl<'t> TextEdit<'t> { auto_id // Since we are only storing the cursor a persistent Id is not super important } }); - let mut state = State::load(ui.ctx(), id).unwrap_or_default().clone(); + let mut state = State::load(ui.ctx(), id).unwrap_or_default(); // On touch screens (e.g. mobile in egui_web), should // dragging select text, or scroll the enclosing `ScrollArea` (if any)? diff --git a/egui_demo_lib/src/apps/demo/password.rs b/egui_demo_lib/src/apps/demo/password.rs index 11caff614c89..2328f11eaef5 100644 --- a/egui_demo_lib/src/apps/demo/password.rs +++ b/egui_demo_lib/src/apps/demo/password.rs @@ -27,11 +27,7 @@ pub fn password_ui(ui: &mut egui::Ui, text: &mut String) -> egui::Response { // You can read more about available `Memory` functions in the documentation of `egui::Memory` // struct and `egui::any` module. // You should get state by value, not by reference to avoid borrowing of `Memory`. - let mut plaintext = ui - .memory() - .id_data - .get_temp::(id) - .unwrap_or_default(); + let mut plaintext = ui.memory().data.get_temp::(id).unwrap_or_default(); // 4. Process ui, change a local copy of the state // We want TextEdit to fill entire space, and have button after that, so in that case we can @@ -55,7 +51,7 @@ pub fn password_ui(ui: &mut egui::Ui, text: &mut String) -> egui::Response { }); // 5. Insert changed state back - ui.memory().id_data.insert_temp(id, plaintext); + ui.memory().data.insert_temp(id, plaintext); // All done! Return the interaction response so the user can check what happened // (hovered, clicked, …) and maybe show a tooltip: diff --git a/egui_demo_lib/src/syntax_highlighting.rs b/egui_demo_lib/src/syntax_highlighting.rs index fb1c400914bf..9603f5ae5084 100644 --- a/egui_demo_lib/src/syntax_highlighting.rs +++ b/egui_demo_lib/src/syntax_highlighting.rs @@ -147,12 +147,12 @@ impl CodeTheme { pub fn from_memory(ctx: &egui::Context) -> Self { if ctx.style().visuals.dark_mode { ctx.memory() - .id_data + .data .get_persisted(egui::Id::new("dark")) .unwrap_or_else(CodeTheme::dark) } else { ctx.memory() - .id_data + .data .get_persisted(egui::Id::new("light")) .unwrap_or_else(CodeTheme::light) } @@ -161,11 +161,11 @@ impl CodeTheme { pub fn store_in_memory(&self, ctx: &egui::Context) { if self.dark_mode { ctx.memory() - .id_data + .data .insert_persisted(egui::Id::new("dark"), *self); } else { ctx.memory() - .id_data + .data .insert_persisted(egui::Id::new("light"), *self); } } @@ -235,7 +235,11 @@ impl CodeTheme { pub fn ui(&mut self, ui: &mut egui::Ui) { ui.horizontal_top(|ui| { - let mut selected_tt: TokenType = *ui.memory().data.get_or(TokenType::Comment); + let selected_id = egui::Id::null(); + let mut selected_tt: TokenType = *ui + .memory() + .data + .get_persisted_mut_or(selected_id, TokenType::Comment); ui.vertical(|ui| { ui.set_width(150.0); @@ -277,7 +281,7 @@ impl CodeTheme { ui.add_space(16.0); - ui.memory().data.insert(selected_tt); + ui.memory().data.insert_persisted(selected_id, selected_tt); egui::Frame::group(ui.style()) .margin(egui::Vec2::splat(2.0)) From b9772b91340d5d569cc4fdbfa2788f11db2581d5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Oct 2021 23:27:29 +0200 Subject: [PATCH 04/11] Simplify the code a bit --- egui/src/any/id_any_map.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/egui/src/any/id_any_map.rs b/egui/src/any/id_any_map.rs index 84f10748935d..d17e0b93d309 100644 --- a/egui/src/any/id_any_map.rs +++ b/egui/src/any/id_any_map.rs @@ -60,6 +60,8 @@ struct SerializedElement { ron: String, } +type Serializer = fn(&Box) -> Option; + enum Element { /// Serializable data Value { @@ -68,8 +70,7 @@ enum Element { // None if non-serializable type. #[cfg(feature = "persistence")] - serialize_fn: - Option) -> Result>, + serialize_fn: Option, }, Serialized { type_id: TypeId, @@ -141,7 +142,7 @@ impl Element { #[cfg(feature = "persistence")] serialize_fn: Some(|x| { let x = x.downcast_ref::().unwrap(); // This will never panic too, for same reason. - ron::to_string(x) + ron::to_string(x).ok() }), } } @@ -241,7 +242,7 @@ impl Element { .. } => { if let Some(serialize_fn) = serialize_fn { - let ron = serialize_fn(value).ok()?; + let ron = serialize_fn(value)?; Some(SerializedElement { type_id: (**value).type_id().into(), ron, From fbb14a7dc7de498c0c227147ff615a3082d20bbd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Oct 2021 23:35:01 +0200 Subject: [PATCH 05/11] Remove old any code, and clean up the new code slightly --- egui/src/any/any_map.rs | 224 -------------------- egui/src/any/element.rs | 63 ------ egui/src/any/mod.rs | 62 ------ egui/src/any/serializable/any_map.rs | 288 -------------------------- egui/src/any/serializable/element.rs | 173 ---------------- egui/src/any/serializable/mod.rs | 6 - egui/src/any/serializable/type_id.rs | 20 -- egui/src/any/serializable/type_map.rs | 211 ------------------- egui/src/any/type_map.rs | 162 --------------- egui/src/lib.rs | 1 - egui/src/memory.rs | 4 +- egui/src/{any => util}/id_any_map.rs | 51 +++-- egui/src/util/mod.rs | 2 + 13 files changed, 36 insertions(+), 1231 deletions(-) delete mode 100644 egui/src/any/any_map.rs delete mode 100644 egui/src/any/element.rs delete mode 100644 egui/src/any/mod.rs delete mode 100644 egui/src/any/serializable/any_map.rs delete mode 100644 egui/src/any/serializable/element.rs delete mode 100644 egui/src/any/serializable/mod.rs delete mode 100644 egui/src/any/serializable/type_id.rs delete mode 100644 egui/src/any/serializable/type_map.rs delete mode 100644 egui/src/any/type_map.rs rename egui/src/{any => util}/id_any_map.rs (94%) diff --git a/egui/src/any/any_map.rs b/egui/src/any/any_map.rs deleted file mode 100644 index 16eb40471672..000000000000 --- a/egui/src/any/any_map.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::any::element::{AnyMapElement, AnyMapTrait}; -use std::any::TypeId; -use std::collections::hash_map::RandomState; -use std::collections::HashMap; -use std::hash::{BuildHasher, Hash}; - -/// Stores any object by `K`. -#[derive(Clone, Debug)] -pub struct AnyMap( - HashMap, -); - -impl Default for AnyMap -where - K: Hash + Eq, - S: BuildHasher + Default, -{ - fn default() -> Self { - AnyMap(HashMap::default()) - } -} - -// ---------------------------------------------------------------------------- - -impl AnyMap -where - K: Hash + Eq, - S: BuildHasher + Default, -{ - #[inline] - pub fn get(&mut self, key: &K) -> Option<&T> { - self.get_mut(key).map(|x| &*x) - } - - #[inline] - pub fn get_mut(&mut self, key: &K) -> Option<&mut T> { - self.0.get_mut(key)?.get_mut() - } - - #[inline] - pub fn get_or_insert_with( - &mut self, - key: K, - or_insert_with: impl FnOnce() -> T, - ) -> &T { - &*self.get_mut_or_insert_with(key, or_insert_with) - } - - #[inline] - pub fn get_or_default(&mut self, key: K) -> &T { - self.get_or_insert_with(key, Default::default) - } - - #[inline] - pub fn get_or(&mut self, key: K, value: T) -> &T { - &*self.get_mut_or_insert_with(key, || value) - } - - pub fn get_mut_or_insert_with( - &mut self, - key: K, - or_insert_with: impl FnOnce() -> T, - ) -> &mut T { - use std::collections::hash_map::Entry; - match self.0.entry(key) { - Entry::Vacant(vacant) => vacant - .insert(AnyMapElement::new(or_insert_with())) - .get_mut() - .unwrap(), // this unwrap will never panic, because we insert correct type right now - Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with), - } - } - - #[inline] - pub fn get_mut_or_default(&mut self, key: K) -> &mut T { - self.get_mut_or_insert_with(key, Default::default) - } - - #[inline] - pub fn insert(&mut self, key: K, element: T) { - self.0.insert(key, AnyMapElement::new(element)); - } - - #[inline] - pub fn remove(&mut self, key: &K) { - self.0.remove(key); - } - - #[inline] - pub fn remove_by_type(&mut self) { - let key = TypeId::of::(); - self.0.retain(|_, v| v.type_id() != key); - } - - #[inline] - pub fn clear(&mut self) { - self.0.clear(); - } - - /// You could use this function to find is there some leak or misusage. - pub fn count(&mut self) -> usize { - let key = TypeId::of::(); - self.0.iter().filter(|(_, v)| v.type_id() == key).count() - } - - pub fn count_all(&mut self) -> usize { - self.0.len() - } -} - -// ---------------------------------------------------------------------------- - -#[cfg(test)] -#[test] -fn basic_usage() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map: AnyMap = Default::default(); - - assert!(map.get::(&0).is_none()); - map.insert(0, State { a: 42 }); - - assert_eq!(*map.get::(&0).unwrap(), State { a: 42 }); - assert!(map.get::(&1).is_none()); - map.get_mut::(&0).unwrap().a = 43; - assert_eq!(*map.get::(&0).unwrap(), State { a: 43 }); - - map.remove(&0); - assert!(map.get::(&0).is_none()); - - assert_eq!( - *map.get_or_insert_with(0, || State { a: 55 }), - State { a: 55 } - ); - map.remove(&0); - assert_eq!( - *map.get_mut_or_insert_with(0, || State { a: 56 }), - State { a: 56 } - ); - map.remove(&0); - assert_eq!(*map.get_or_default::(0), State { a: 0 }); - map.remove(&0); - assert_eq!(*map.get_mut_or_default::(0), State { a: 0 }); -} - -#[cfg(test)] -#[test] -fn different_type_same_id() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map: AnyMap = Default::default(); - - map.insert(0, State { a: 42 }); - - assert_eq!(*map.get::(&0).unwrap(), State { a: 42 }); - assert!(map.get::(&0).is_none()); - - map.insert(0, 255i32); - - assert_eq!(*map.get::(&0).unwrap(), 255); - assert!(map.get::(&0).is_none()); -} - -#[cfg(test)] -#[test] -fn cloning() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map: AnyMap = Default::default(); - - map.insert(0, State::default()); - map.insert(10, 10i32); - map.insert(11, 11i32); - - let mut cloned_map = map.clone(); - - map.insert(12, 12i32); - map.insert(1, State { a: 10 }); - - assert_eq!(*cloned_map.get::(&0).unwrap(), State { a: 0 }); - assert!(cloned_map.get::(&1).is_none()); - assert_eq!(*cloned_map.get::(&10).unwrap(), 10i32); - assert_eq!(*cloned_map.get::(&11).unwrap(), 11i32); - assert!(cloned_map.get::(&12).is_none()); -} - -#[cfg(test)] -#[test] -fn counting() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map: AnyMap = Default::default(); - - map.insert(0, State::default()); - map.insert(1, State { a: 10 }); - map.insert(10, 10i32); - map.insert(11, 11i32); - map.insert(12, 12i32); - - assert_eq!(map.count::(), 2); - assert_eq!(map.count::(), 3); - - map.remove_by_type::(); - - assert_eq!(map.count::(), 0); - assert_eq!(map.count::(), 3); - - map.clear(); - - assert_eq!(map.count::(), 0); - assert_eq!(map.count::(), 0); -} diff --git a/egui/src/any/element.rs b/egui/src/any/element.rs deleted file mode 100644 index cafc70dfeb52..000000000000 --- a/egui/src/any/element.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::any::{Any, TypeId}; -use std::fmt; - -/// Like [`std::any::Any`], but also implements `Clone`. -pub(crate) struct AnyMapElement { - value: Box, - clone_fn: fn(&Box) -> Box, -} - -impl fmt::Debug for AnyMapElement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("AnyMapElement") - .field("value_type_id", &self.type_id()) - .finish_non_exhaustive() - } -} - -impl Clone for AnyMapElement { - fn clone(&self) -> Self { - AnyMapElement { - value: (self.clone_fn)(&self.value), - clone_fn: self.clone_fn, - } - } -} - -pub trait AnyMapTrait: 'static + Any + Clone + Send + Sync {} - -impl AnyMapTrait for T {} - -impl AnyMapElement { - pub(crate) fn new(t: T) -> Self { - AnyMapElement { - value: Box::new(t), - clone_fn: |x| { - let x = x.downcast_ref::().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with type `T`, so type cannot change. - Box::new(x.clone()) - }, - } - } - - #[inline] - pub(crate) fn type_id(&self) -> TypeId { - (*self.value).type_id() - } - - #[inline] - pub(crate) fn get_mut(&mut self) -> Option<&mut T> { - self.value.downcast_mut() - } - - pub(crate) fn get_mut_or_set_with( - &mut self, - set_with: impl FnOnce() -> T, - ) -> &mut T { - if !self.value.is::() { - *self = Self::new(set_with()); - // TODO: log this error, because it can occurs when user used same Id or same type for different widgets - } - - self.value.downcast_mut().unwrap() // This unwrap will never panic because we already converted object to required type - } -} diff --git a/egui/src/any/mod.rs b/egui/src/any/mod.rs deleted file mode 100644 index 5b3dfcc1699a..000000000000 --- a/egui/src/any/mod.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Any-type storages for [`Memory`]. -//! -//! This module contains structs to store arbitrary types using [`Any`] trait. Also, they can be cloned, and structs in [`serializable`] can be de/serialized. -//! -//! All this is just `HashMap>` and `HashMap>`, but with helper functions and hacks for cloning and de/serialization. -//! -//! # Trait requirements -//! -//! If you want to store your type here, it must implement `Clone` and `Any` and be `'static`, which means it must not contain references. If you want to store your data in serializable storage, it must implement `serde::Serialize` and `serde::Deserialize` under the `persistent` feature. -//! -//! # [`TypeMap`] -//! -//! It stores everything by just type. You should use this map for your widget when all instances of your widgets can have only one state. E.g. for popup windows, for color picker. -//! -//! To not have intersections, you should create newtype for anything you try to store here, like: -//! ```rust -//! struct MyEditBool(pub bool); -//! ``` -//! -//! # [`AnyMap`] -//! -//! In [`Memory`] `Key` = [`Id`]. -//! -//! [`TypeMap`] and [`AnyMap`] has a quite similar interface, except for [`AnyMap`] you should pass `Key` to get and insert things. -//! -//! It stores everything by `Key`, this should be used when your widget can have different data for different instances of the widget. -//! -//! # `serializable` -//! -//! [`TypeMap`] and [`serializable::TypeMap`] has exactly the same interface, but [`serializable::TypeMap`] only requires serde traits for stored object under `persistent` feature. Same thing for [`AnyMap`] and [`serializable::IdAnyMap`]. -//! -//! # What could break -//! -//! Things here could break only when you trying to load this from file. -//! -//! First, serialized `TypeId` in [`serializable::TypeMap`] could broke if you updated the version of the Rust compiler between runs. -//! -//! Second, count and reset all instances of a type in [`serializable::IdAnyMap`] could return an incorrect value for the same reason. -//! -//! Deserialization errors of loaded elements of these storages can be determined only when you call `get_…` functions, they not logged and not provided to a user, on this errors value is just replaced with `or_insert()`/default value. -//! -//! # When not to use this -//! -//! This is not for important widget data. Some errors are just ignored and the correct value of type is inserted when you call. This is done to more simple interface. -//! -//! You shouldn't use any map here when you need very reliable state storage with rich error-handling. For this purpose you should create your own `Memory` struct and pass it everywhere you need it. Then, you should de/serialize it by yourself, handling all serialization or other errors as you wish. -//! -//! [`Id`]: crate::Id -//! [`Memory`]: crate::Memory -//! [`Any`]: std::any::Any -//! [`AnyMap`]: crate::any::AnyMap - -mod any_map; -mod element; -mod id_any_map; -mod type_map; - -/// Same structs and traits, but also can be de/serialized under `persistence` feature. -#[cfg(feature = "persistence")] -pub mod serializable; - -pub use self::{any_map::AnyMap, element::AnyMapTrait, id_any_map::IdAnyMap, type_map::TypeMap}; diff --git a/egui/src/any/serializable/any_map.rs b/egui/src/any/serializable/any_map.rs deleted file mode 100644 index 45cebd933ffe..000000000000 --- a/egui/src/any/serializable/any_map.rs +++ /dev/null @@ -1,288 +0,0 @@ -use crate::any::serializable::element::{AnyMapElement, AnyMapTrait}; -use crate::any::serializable::type_id::TypeId; -use crate::{Id, IdMap}; -use serde::{Deserialize, Serialize}; - -// I gave up making this general over any key and hash builder, like for `AnyMap`, -// hence the disabled test later on. -/// Stores any object by [`Id`], and can be de/serialized. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct IdAnyMap(IdMap); - -// ---------------------------------------------------------------------------- - -impl IdAnyMap { - #[inline] - pub fn get(&mut self, key: &Id) -> Option<&T> { - self.get_mut(key).map(|x| &*x) - } - - #[inline] - pub fn get_mut(&mut self, key: &Id) -> Option<&mut T> { - self.0.get_mut(key)?.get_mut() - } - - #[inline] - pub fn get_or_insert_with( - &mut self, - key: Id, - or_insert_with: impl FnOnce() -> T, - ) -> &T { - &*self.get_mut_or_insert_with(key, or_insert_with) - } - - #[inline] - pub fn get_or_default(&mut self, key: Id) -> &T { - self.get_or_insert_with(key, Default::default) - } - - #[inline] - pub fn get_or(&mut self, key: Id, value: T) -> &T { - &*self.get_mut_or_insert_with(key, || value) - } - - pub fn get_mut_or_insert_with( - &mut self, - key: Id, - or_insert_with: impl FnOnce() -> T, - ) -> &mut T { - use std::collections::hash_map::Entry; - match self.0.entry(key) { - Entry::Vacant(vacant) => vacant - .insert(AnyMapElement::new(or_insert_with())) - .get_mut() - .unwrap(), // this unwrap will never panic, because we insert correct type right now - Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with), - } - } - - pub fn get_mut_or_default(&mut self, key: Id) -> &mut T { - self.get_mut_or_insert_with(key, Default::default) - } - - #[inline] - pub fn insert(&mut self, key: Id, element: T) { - self.0.insert(key, AnyMapElement::new(element)); - } - - #[inline] - pub fn remove(&mut self, key: &Id) { - self.0.remove(key); - } - - /// Note that this function could not remove all needed types between runs because if you upgraded the Rust version or for other reasons. - pub fn remove_by_type(&mut self) { - let key = TypeId::of::(); - self.0.retain(|_, v| v.type_id() != key); - } - - #[inline] - pub fn clear(&mut self) { - self.0.clear(); - } - - /// You could use this function to find is there some leak or misusage. Note, that result of this function could break between runs, if you upgraded the Rust version or for other reasons. - pub fn count(&mut self) -> usize { - let key = TypeId::of::(); - self.0.iter().filter(|(_, v)| v.type_id() == key).count() - } - - pub fn count_all(&mut self) -> usize { - self.0.len() - } -} - -// ---------------------------------------------------------------------------- - -#[test] -fn id_any_map_mix_serialize_and_not() { - use serde::{Deserialize, Serialize}; - - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] - struct Serializable(Option); - - #[derive(Clone, Debug)] - struct NonSerializable(Option); - - let mut map: IdAnyMap = Default::default(); - map.insert(Id::new("yes"), Serializable(Some("Serializable".into()))); - // map.insert_not_persisted(Id::new("not"), NonSerializable(Some("Not".into()))); - - let serialized = serde_json::to_string(&map).unwrap(); - - let mut map: IdAnyMap = serde_json::from_str(&serialized).unwrap(); - assert_eq!( - map.get::(&Id::new("yes")), - Some(&Serializable(Some("Serializable".into()))) - ); - // assert_eq!(map.get::(&1), Some(&State1 { a: 42 })); -} - -// #[test] -// fn discard_different_struct() { -// use serde::{Deserialize, Serialize}; - -// #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -// struct State1 { -// a: i32, -// } - -// #[derive(Clone, Debug, Serialize, Deserialize)] -// struct State2 { -// b: String, -// } - -// let file_string = { -// let mut map: AnyMap = Default::default(); -// map.insert(1, State1 { a: 42 }); -// ron::to_string(&map).unwrap() -// }; - -// let mut map: AnyMap = ron::from_str(&file_string).unwrap(); -// assert!(map.get::(&1).is_none()); -// assert_eq!(map.get::(&1), Some(&State1 { a: 42 })); -// } - -// #[test] -// fn new_field_between_runs() { -// use serde::{Deserialize, Serialize}; - -// #[derive(Clone, Debug, Serialize, Deserialize)] -// struct State { -// a: i32, -// } - -// #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -// struct StateNew { -// a: i32, - -// #[serde(default)] -// b: String, -// } - -// let file_string = { -// let mut map: AnyMap = Default::default(); -// map.insert(1, State { a: 42 }); -// ron::to_string(&map).unwrap() -// }; - -// let mut map: AnyMap = ron::from_str(&file_string).unwrap(); -// assert_eq!( -// map.get::(&1), -// Some(&StateNew { -// a: 42, -// b: String::default() -// }) -// ); -// } - -// ---------------------------------------------------------------------------- - -// #[test] -// fn basic_usage() { -// #[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)] -// struct State { -// a: i32, -// } - -// let mut map: AnyMap = Default::default(); - -// assert!(map.get::(&0).is_none()); -// map.insert(0, State { a: 42 }); - -// assert_eq!(*map.get::(&0).unwrap(), State { a: 42 }); -// assert!(map.get::(&1).is_none()); -// map.get_mut::(&0).unwrap().a = 43; -// assert_eq!(*map.get::(&0).unwrap(), State { a: 43 }); - -// map.remove(&0); -// assert!(map.get::(&0).is_none()); - -// assert_eq!( -// *map.get_or_insert_with(0, || State { a: 55 }), -// State { a: 55 } -// ); -// map.remove(&0); -// assert_eq!( -// *map.get_mut_or_insert_with(0, || State { a: 56 }), -// State { a: 56 } -// ); -// map.remove(&0); -// assert_eq!(*map.get_or_default::(0), State { a: 0 }); -// map.remove(&0); -// assert_eq!(*map.get_mut_or_default::(0), State { a: 0 }); -// } - -// #[test] -// fn different_type_same_id() { -// #[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)] -// struct State { -// a: i32, -// } - -// let mut map: AnyMap = Default::default(); - -// map.insert(0, State { a: 42 }); - -// assert_eq!(*map.get::(&0).unwrap(), State { a: 42 }); -// assert!(map.get::(&0).is_none()); - -// map.insert(0, 255i32); - -// assert_eq!(*map.get::(&0).unwrap(), 255); -// assert!(map.get::(&0).is_none()); -// } - -// #[test] -// fn cloning() { -// #[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)] -// struct State { -// a: i32, -// } - -// let mut map: AnyMap = Default::default(); - -// map.insert(0, State::default()); -// map.insert(10, 10i32); -// map.insert(11, 11i32); - -// let mut cloned_map = map.clone(); - -// map.insert(12, 12i32); -// map.insert(1, State { a: 10 }); - -// assert_eq!(*cloned_map.get::(&0).unwrap(), State { a: 0 }); -// assert!(cloned_map.get::(&1).is_none()); -// assert_eq!(*cloned_map.get::(&10).unwrap(), 10i32); -// assert_eq!(*cloned_map.get::(&11).unwrap(), 11i32); -// assert!(cloned_map.get::(&12).is_none()); -// } - -// #[test] -// fn counting() { -// #[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)] -// struct State { -// a: i32, -// } - -// let mut map: AnyMap = Default::default(); - -// map.insert(0, State::default()); -// map.insert(1, State { a: 10 }); -// map.insert(10, 10i32); -// map.insert(11, 11i32); -// map.insert(12, 12i32); - -// assert_eq!(map.count::(), 2); -// assert_eq!(map.count::(), 3); - -// map.remove_by_type::(); - -// assert_eq!(map.count::(), 0); -// assert_eq!(map.count::(), 3); - -// map.clear(); - -// assert_eq!(map.count::(), 0); -// assert_eq!(map.count::(), 0); -// } diff --git a/egui/src/any/serializable/element.rs b/egui/src/any/serializable/element.rs deleted file mode 100644 index f03232e56976..000000000000 --- a/egui/src/any/serializable/element.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::any::serializable::type_id::TypeId; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::any::Any; -use std::fmt; - -pub(crate) struct AnyMapElement(AnyMapElementInner); - -enum AnyMapElementInner { - Deserialized { - value: Box, - clone_fn: fn(&Box) -> Box, - - serialize_fn: fn(&Box) -> Result, - }, - Serialized(String, TypeId), -} - -use AnyMapElementInner::{Deserialized, Serialized}; - -#[derive(Deserialize, Serialize)] -struct AnyMapElementInnerSer(String, TypeId); - -impl Serialize for AnyMapElement { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let inner = match &self.0 { - Deserialized { - value, - serialize_fn, - .. - } => { - let s = serialize_fn(value).map_err(serde::ser::Error::custom)?; - AnyMapElementInnerSer(s, (**value).type_id().into()) - } - Serialized(s, id) => AnyMapElementInnerSer(s.clone(), *id), - }; - - inner.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for AnyMapElement { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let AnyMapElementInnerSer(s, id) = AnyMapElementInnerSer::deserialize(deserializer)?; - - Ok(AnyMapElement(Serialized(s, id))) - } -} - -impl fmt::Debug for AnyMapElement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - Deserialized { value, .. } => f - .debug_struct("AnyMapElement_Deserialized") - .field("value_type_id", &value.type_id()) - .finish(), - Serialized(s, id) => f - .debug_tuple("AnyMapElement_Serialized") - .field(&s) - .field(&id) - .finish(), - } - } -} - -impl Clone for AnyMapElement { - fn clone(&self) -> Self { - match &self.0 { - Deserialized { - value, - clone_fn, - serialize_fn, - } => AnyMapElement(Deserialized { - value: clone_fn(value), - clone_fn: *clone_fn, - serialize_fn: *serialize_fn, - }), - Serialized(s, id) => AnyMapElement(Serialized(s.clone(), *id)), - } - } -} - -pub trait AnyMapTrait: - 'static + Any + Clone + Serialize + for<'a> Deserialize<'a> + Send + Sync -{ -} -impl Deserialize<'a> + Send + Sync> AnyMapTrait - for T -{ -} - -impl AnyMapElement { - pub(crate) fn new(t: T) -> Self { - AnyMapElement(Deserialized { - value: Box::new(t), - clone_fn: |x| { - let x = x.downcast_ref::().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change. - Box::new(x.clone()) - }, - - serialize_fn: |x| { - let x = x.downcast_ref::().unwrap(); // This will never panic too, for same reason. - ron::to_string(x) - }, - }) - } - - pub(crate) fn type_id(&self) -> TypeId { - match self { - AnyMapElement(Deserialized { value, .. }) => (**value).type_id().into(), - AnyMapElement(Serialized(_, id)) => *id, - } - } - - pub(crate) fn get_mut(&mut self) -> Option<&mut T> { - match self { - AnyMapElement(Deserialized { value, .. }) => value.downcast_mut(), - AnyMapElement(Serialized(s, _)) => { - *self = Self::new(from_ron_str::(s)?); - - match self { - AnyMapElement(Deserialized { value, .. }) => value.downcast_mut(), - AnyMapElement(Serialized(_, _)) => unreachable!(), - } - } - } - } - - pub(crate) fn get_mut_or_set_with( - &mut self, - set_with: impl FnOnce() -> T, - ) -> &mut T { - match &mut self.0 { - Deserialized { value, .. } => { - if !value.is::() { - *self = Self::new(set_with()); - eprintln!( - "egui: Value stored in serialized memory was not of type {}", - std::any::type_name::() - ); - } - } - Serialized(s, _) => { - *self = Self::new(from_ron_str::(s).unwrap_or_else(set_with)); - } - } - - match &mut self.0 { - Deserialized { value, .. } => value.downcast_mut().unwrap(), // This unwrap will never panic because we already converted object to required type - Serialized(_, _) => unreachable!(), - } - } -} - -fn from_ron_str(ron: &str) -> Option { - match ron::from_str::(ron) { - Ok(value) => Some(value), - Err(err) => { - eprintln!( - "egui: Failed to deserialize {} from memory: {}, ron: {:?}", - std::any::type_name::(), - err, - ron - ); - None - } - } -} diff --git a/egui/src/any/serializable/mod.rs b/egui/src/any/serializable/mod.rs deleted file mode 100644 index d46b7d7e5ada..000000000000 --- a/egui/src/any/serializable/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod any_map; -mod element; -mod type_id; -mod type_map; - -pub use self::{any_map::IdAnyMap, element::AnyMapTrait, type_map::TypeMap}; diff --git a/egui/src/any/serializable/type_id.rs b/egui/src/any/serializable/type_id.rs deleted file mode 100644 index 0c99ad796f8d..000000000000 --- a/egui/src/any/serializable/type_id.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::any::Any; - -/// We need this because `TypeId` can't be deserialized or serialized directly, but this can be done using hashing. However, there is a small possibility that different types will have intersection by hashes of their type ids. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub struct TypeId(u64); - -impl TypeId { - #[inline] - pub fn of() -> Self { - std::any::TypeId::of::().into() - } -} - -impl From for TypeId { - #[inline] - fn from(id: std::any::TypeId) -> Self { - Self(epaint::util::hash(id)) - } -} diff --git a/egui/src/any/serializable/type_map.rs b/egui/src/any/serializable/type_map.rs deleted file mode 100644 index b3223849a5c3..000000000000 --- a/egui/src/any/serializable/type_map.rs +++ /dev/null @@ -1,211 +0,0 @@ -use crate::any::serializable::element::{AnyMapElement, AnyMapTrait}; -use crate::any::serializable::type_id::TypeId; -use crate::epaint::ahash::AHashMap; -use serde::{Deserialize, Serialize}; - -/// Maps types to a single instance of that type. -/// -/// Used to store state per widget type. In effect a sort of singleton storage. -/// Similar to [the `typemap` crate](https://docs.rs/typemap/0.3.3/typemap/) but allows serialization. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct TypeMap(AHashMap); - -// ---------------------------------------------------------------------------- - -impl TypeMap { - #[inline] - pub fn get(&mut self) -> Option<&T> { - self.get_mut().map(|x| &*x) - } - - #[inline] - pub fn get_mut(&mut self) -> Option<&mut T> { - self.0.get_mut(&TypeId::of::())?.get_mut() - } - - #[inline] - pub fn get_or_insert_with(&mut self, or_insert_with: impl FnOnce() -> T) -> &T { - &*self.get_mut_or_insert_with(or_insert_with) - } - - #[inline] - pub fn get_or_default(&mut self) -> &T { - self.get_or_insert_with(Default::default) - } - - #[inline] - pub fn get_or(&mut self, value: T) -> &T { - &*self.get_mut_or_insert_with(|| value) - } - - pub fn get_mut_or_insert_with( - &mut self, - or_insert_with: impl FnOnce() -> T, - ) -> &mut T { - use std::collections::hash_map::Entry; - match self.0.entry(TypeId::of::()) { - Entry::Vacant(vacant) => vacant - .insert(AnyMapElement::new(or_insert_with())) - .get_mut() - .unwrap(), // this unwrap will never panic, because we insert correct type right now - Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with), - } - } - - pub fn get_mut_or_default(&mut self) -> &mut T { - self.get_mut_or_insert_with(Default::default) - } - - #[inline] - pub fn insert(&mut self, element: T) { - self.0 - .insert(TypeId::of::(), AnyMapElement::new(element)); - } - - #[inline] - pub fn remove(&mut self) { - self.0.remove(&TypeId::of::()); - } - - #[inline] - pub fn clear(&mut self) { - self.0.clear(); - } -} - -// ---------------------------------------------------------------------------- - -#[test] -fn discard_different_struct() { - #[derive(Clone, Debug, Serialize, Deserialize)] - struct State1 { - a: i32, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - struct State2 { - a: String, - } - - let file_string = { - let mut map: TypeMap = Default::default(); - map.insert(State1 { a: 42 }); - ron::to_string(&map).unwrap() - }; - - let mut map: TypeMap = ron::from_str(&file_string).unwrap(); - assert!(map.get::().is_none()); -} - -#[test] -fn new_field_between_runs() { - #[derive(Clone, Debug, Serialize, Deserialize)] - struct State { - a: i32, - } - - #[derive(Clone, Debug, Serialize, Deserialize)] - struct StateNew { - a: i32, - - #[serde(default)] - b: i32, - } - - let file_string = { - let mut map: TypeMap = Default::default(); - map.insert(State { a: 42 }); - ron::to_string(&map).unwrap() - }; - - let mut map: TypeMap = ron::from_str(&file_string).unwrap(); - assert!(map.get::().is_none()); -} - -// ---------------------------------------------------------------------------- - -#[cfg(test)] -#[test] -fn basic_usage() { - #[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] - struct State { - a: i32, - } - - let mut map = TypeMap::default(); - - assert!(map.get::().is_none()); - map.insert(State { a: 42 }); - map.insert(5i32); - map.insert((6.0f32, -1i16)); - - assert_eq!(*map.get::().unwrap(), State { a: 42 }); - map.get_mut::().unwrap().a = 43; - assert_eq!(*map.get::().unwrap(), State { a: 43 }); - - map.remove::(); - assert!(map.get::().is_none()); - - assert_eq!(*map.get_or_insert_with(|| State { a: 55 }), State { a: 55 }); - map.remove::(); - assert_eq!( - *map.get_mut_or_insert_with(|| State { a: 56 }), - State { a: 56 } - ); - map.remove::(); - assert_eq!(*map.get_or_default::(), State { a: 0 }); - map.remove::(); - assert_eq!(*map.get_mut_or_default::(), State { a: 0 }); -} - -#[cfg(test)] -#[test] -fn cloning() { - #[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] - struct State { - a: i32, - } - - let mut map: TypeMap = Default::default(); - - map.insert(State::default()); - map.insert(10i32); - - let mut cloned_map = map.clone(); - - map.insert(11.5f32); - map.insert("aoeu".to_string()); - - assert_eq!(*cloned_map.get::().unwrap(), State { a: 0 }); - assert_eq!(*cloned_map.get::().unwrap(), 10i32); - assert!(cloned_map.get::().is_none()); - assert!(cloned_map.get::().is_none()); -} - -#[cfg(test)] -#[test] -fn removing() { - #[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)] - struct State { - a: i32, - } - - let mut map: TypeMap = Default::default(); - - map.insert(State::default()); - map.insert(10i32); - map.insert(11.5f32); - map.insert("aoeu".to_string()); - - map.remove::(); - assert!(map.get::().is_none()); - assert!(map.get::().is_some()); - assert!(map.get::().is_some()); - assert!(map.get::().is_some()); - - map.clear(); - assert!(map.get::().is_none()); - assert!(map.get::().is_none()); - assert!(map.get::().is_none()); - assert!(map.get::().is_none()); -} diff --git a/egui/src/any/type_map.rs b/egui/src/any/type_map.rs deleted file mode 100644 index e4b89fa6e03d..000000000000 --- a/egui/src/any/type_map.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::any::element::{AnyMapElement, AnyMapTrait}; -use crate::epaint::ahash::AHashMap; -use std::any::TypeId; - -/// Maps types to a single instance of that type. -/// -/// Used to store state per widget type. In effect a sort of singleton storage. -/// Similar to [the `typemap` crate](https://docs.rs/typemap/0.3.3/typemap/). -#[derive(Clone, Debug, Default)] -pub struct TypeMap(AHashMap); - -// ---------------------------------------------------------------------------- - -impl TypeMap { - #[inline] - pub fn get(&mut self) -> Option<&T> { - self.get_mut().map(|x| &*x) - } - - #[inline] - pub fn get_mut(&mut self) -> Option<&mut T> { - self.0.get_mut(&TypeId::of::())?.get_mut() - } - - #[inline] - pub fn get_or_insert_with(&mut self, or_insert_with: impl FnOnce() -> T) -> &T { - &*self.get_mut_or_insert_with(or_insert_with) - } - - #[inline] - pub fn get_or_default(&mut self) -> &T { - self.get_or_insert_with(Default::default) - } - - #[inline] - pub fn get_or(&mut self, value: T) -> &T { - &*self.get_mut_or_insert_with(|| value) - } - - pub fn get_mut_or_insert_with( - &mut self, - or_insert_with: impl FnOnce() -> T, - ) -> &mut T { - use std::collections::hash_map::Entry; - match self.0.entry(TypeId::of::()) { - Entry::Vacant(vacant) => vacant - .insert(AnyMapElement::new(or_insert_with())) - .get_mut() - .unwrap(), // this unwrap will never panic, because we insert correct type right now - Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with), - } - } - - #[inline] - pub fn get_mut_or_default(&mut self) -> &mut T { - self.get_mut_or_insert_with(Default::default) - } - - #[inline] - pub fn insert(&mut self, element: T) { - self.0 - .insert(TypeId::of::(), AnyMapElement::new(element)); - } - - #[inline] - pub fn remove(&mut self) { - self.0.remove(&TypeId::of::()); - } - - #[inline] - pub fn clear(&mut self) { - self.0.clear(); - } -} - -// ---------------------------------------------------------------------------- - -#[cfg(test)] -#[test] -fn basic_usage() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map = TypeMap::default(); - - assert!(map.get::().is_none()); - map.insert(State { a: 42 }); - map.insert(5i32); - map.insert((6.0f32, -1i16)); - - assert_eq!(*map.get::().unwrap(), State { a: 42 }); - map.get_mut::().unwrap().a = 43; - assert_eq!(*map.get::().unwrap(), State { a: 43 }); - - map.remove::(); - assert!(map.get::().is_none()); - - assert_eq!(*map.get_or_insert_with(|| State { a: 55 }), State { a: 55 }); - map.remove::(); - assert_eq!( - *map.get_mut_or_insert_with(|| State { a: 56 }), - State { a: 56 } - ); - map.remove::(); - assert_eq!(*map.get_or_default::(), State { a: 0 }); - map.remove::(); - assert_eq!(*map.get_mut_or_default::(), State { a: 0 }); -} - -#[cfg(test)] -#[test] -fn cloning() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map: TypeMap = Default::default(); - - map.insert(State::default()); - map.insert(10i32); - - let mut cloned_map = map.clone(); - - map.insert(11.5f32); - map.insert("aoeu"); - - assert_eq!(*cloned_map.get::().unwrap(), State { a: 0 }); - assert_eq!(*cloned_map.get::().unwrap(), 10i32); - assert!(cloned_map.get::().is_none()); - assert!(cloned_map.get::<&'static str>().is_none()); -} - -#[cfg(test)] -#[test] -fn removing() { - #[derive(Debug, Clone, Eq, PartialEq, Default)] - struct State { - a: i32, - } - - let mut map: TypeMap = Default::default(); - - map.insert(State::default()); - map.insert(10i32); - map.insert(11.5f32); - map.insert("aoeu"); - - map.remove::(); - assert!(map.get::().is_none()); - assert!(map.get::().is_some()); - assert!(map.get::().is_some()); - assert!(map.get::<&'static str>().is_some()); - - map.clear(); - assert!(map.get::().is_none()); - assert!(map.get::().is_none()); - assert!(map.get::().is_none()); - assert!(map.get::<&'static str>().is_none()); -} diff --git a/egui/src/lib.rs b/egui/src/lib.rs index f737646d590d..cbff870b7101 100644 --- a/egui/src/lib.rs +++ b/egui/src/lib.rs @@ -348,7 +348,6 @@ #![allow(clippy::manual_range_contains)] mod animation_manager; -pub mod any; pub mod containers; mod context; mod data; diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 977c7dc78aca..254e074b5c22 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -1,6 +1,6 @@ use epaint::ahash::AHashSet; -use crate::{any, area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style}; +use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style}; // ---------------------------------------------------------------------------- @@ -24,7 +24,7 @@ pub struct Memory { /// /// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key. #[cfg(feature = "persistence")] - pub data: any::IdAnyMap, + pub data: crate::util::IdAnyMap, // ------------------------------------------ /// Can be used to cache computations from one frame to another. diff --git a/egui/src/any/id_any_map.rs b/egui/src/util/id_any_map.rs similarity index 94% rename from egui/src/any/id_any_map.rs rename to egui/src/util/id_any_map.rs index d17e0b93d309..6cf57b269e8c 100644 --- a/egui/src/any/id_any_map.rs +++ b/egui/src/util/id_any_map.rs @@ -63,17 +63,23 @@ struct SerializedElement { type Serializer = fn(&Box) -> Option; enum Element { - /// Serializable data + /// A value, possibly serializable Value { + /// The actual value. value: Box, + + /// How to clone the value. clone_fn: fn(&Box) -> Box, - // None if non-serializable type. + /// How to serialize the vlaue. + /// None if non-serializable type. #[cfg(feature = "persistence")] serialize_fn: Option, }, Serialized { + /// The type stored. type_id: TypeId, + /// The ron data we can deserialize. ron: String, }, } @@ -105,11 +111,11 @@ impl std::fmt::Debug for Element { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self { Self::Value { value, .. } => f - .debug_struct("MaybeSerializable::Temporary") - .field("value_type_id", &value.type_id()) + .debug_struct("MaybeSerializable::Value") + .field("type_id", &value.type_id()) .finish_non_exhaustive(), Self::Serialized { type_id, ron } => f - .debug_struct("MaybeSerializable::Temporary") + .debug_struct("MaybeSerializable::Serialized") .field("type_id", &type_id) .field("ron", &ron) .finish(), @@ -280,12 +286,28 @@ fn from_ron_str(ron: &str) -> Option { use crate::Id; // TODO: make generic over the key, instead of using hard-coded `Id`. -/// Stores any value grouped by [`Id`] and [`TypeId`]. -/// Values can optionally be serializable. +/// Stores any value identified by their type and a given [`Id`]. +/// +/// Values can either be "persisted" (serializable) or "temporary" (cleared when egui is shut down). #[derive(Clone, Debug, Default)] pub struct IdAnyMap(nohash_hasher::IntMap); impl IdAnyMap { + /// 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.0.insert(hash, Element::new_temp(value)); + } + + /// 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.0.insert(hash, Element::new_persisted(value)); + } + + /// Read a value without trying to deserialize a persited value. #[inline] pub fn get_temp(&mut self, id: Id) -> Option { let hash = hash(TypeId::of::(), id); @@ -295,6 +317,7 @@ impl IdAnyMap { .cloned() } + /// Read a value, optionally deserializing it if available. #[inline] pub fn get_persisted(&mut self, id: Id) -> Option { let hash = hash(TypeId::of::(), id); @@ -367,18 +390,6 @@ impl IdAnyMap { } } - #[inline] - pub fn insert_temp(&mut self, id: Id, value: T) { - let hash = hash(TypeId::of::(), id); - self.0.insert(hash, Element::new_temp(value)); - } - - #[inline] - pub fn insert_persisted(&mut self, id: Id, value: T) { - let hash = hash(TypeId::of::(), id); - self.0.insert(hash, Element::new_persisted(value)); - } - #[inline] pub fn remove(&mut self, id: Id) { let hash = hash(TypeId::of::(), id); @@ -421,6 +432,8 @@ fn hash(type_id: TypeId, id: Id) -> u64 { type_id.value() ^ id.value() } +// ---------------------------------------------------------------------------- + #[cfg(feature = "serde")] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct PersistedMap(Vec<(u64, SerializedElement)>); diff --git a/egui/src/util/mod.rs b/egui/src/util/mod.rs index c5b96a32b1ae..5ba8a2b84d4a 100644 --- a/egui/src/util/mod.rs +++ b/egui/src/util/mod.rs @@ -3,8 +3,10 @@ pub mod cache; pub(crate) mod fixed_cache; mod history; +pub mod id_any_map; pub mod undoer; pub use history::History; +pub use id_any_map::IdAnyMap; pub use epaint::util::{hash, hash_with}; From a4bfcceff395f3708e95413603a9684019155554 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 22 Oct 2021 23:38:43 +0200 Subject: [PATCH 06/11] Fixes for when not using persistance feature --- egui/src/memory.rs | 1 - egui/src/util/id_any_map.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 254e074b5c22..116ed2c12e1d 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -23,7 +23,6 @@ pub struct Memory { /// This will be saved between different program runs if you use the `persistence` feature. /// /// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key. - #[cfg(feature = "persistence")] pub data: crate::util::IdAnyMap, // ------------------------------------------ diff --git a/egui/src/util/id_any_map.rs b/egui/src/util/id_any_map.rs index 6cf57b269e8c..6b39b169eda3 100644 --- a/egui/src/util/id_any_map.rs +++ b/egui/src/util/id_any_map.rs @@ -60,6 +60,7 @@ struct SerializedElement { ron: String, } +#[cfg(feature = "persistence")] type Serializer = fn(&Box) -> Option; enum Element { From bc87a5c8c1d83c0548d794746ff894376ab4103d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 23 Oct 2021 04:43:51 +0200 Subject: [PATCH 07/11] Remove serde_json from dev-dependencies --- egui/src/util/id_any_map.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/egui/src/util/id_any_map.rs b/egui/src/util/id_any_map.rs index 6b39b169eda3..2dc3f7fbf628 100644 --- a/egui/src/util/id_any_map.rs +++ b/egui/src/util/id_any_map.rs @@ -548,7 +548,7 @@ fn test_mix_serialize() { // ----------- - let serialized = serde_json::to_string(&map).unwrap(); + let serialized = ron::to_string(&map).unwrap(); // ------------ // Test removal: @@ -572,7 +572,7 @@ fn test_mix_serialize() { // -------------------- // Test deserialization: - let mut map: IdAnyMap = serde_json::from_str(&serialized).unwrap(); + let mut map: IdAnyMap = ron::from_str(&serialized).unwrap(); assert_eq!(map.get_temp::(id), None); assert_eq!( map.get_persisted::(id), From fa19c76a451bbd0aaf4a04ba7f7676f464eb2eb4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 24 Oct 2021 18:44:36 +0200 Subject: [PATCH 08/11] Only implement serde-traits on Memory on "persistence" feature --- egui/src/memory.rs | 19 +++++++++++-------- egui/src/util/id_any_map.rs | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 116ed2c12e1d..836e2239e247 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -10,11 +10,12 @@ use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style}; /// how far the user has scrolled in a `ScrollArea` etc. /// /// If you want this to persist when closing your app you should serialize `Memory` and store it. +/// For this you need to enable the `persistence`. /// /// If you want to store data for your widgets, you should look at [`Memory::data`] #[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "serde", serde(default))] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "persistence", serde(default))] pub struct Memory { pub options: Options, @@ -49,33 +50,35 @@ pub struct Memory { /// let cache = memory.caches.cache::>(); /// assert_eq!(cache.get("hello"), 5); /// ``` - #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(feature = "persistence", serde(skip))] pub caches: crate::util::cache::CacheStorage, // ------------------------------------------ /// new scale that will be applied at the start of the next frame + #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) new_pixels_per_point: Option, /// new fonts that will be applied at the start of the next frame + #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) new_font_definitions: Option, - #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) interaction: Interaction, - #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) window_interaction: Option, - #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) drag_value: crate::widgets::drag_value::MonoState, pub(crate) areas: Areas, /// Which popup-window is open (if any)? /// Could be a combo box, color picker, menu etc. - #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(feature = "persistence", serde(skip))] popup: Option, - #[cfg_attr(feature = "serde", serde(skip))] + #[cfg_attr(feature = "persistence", serde(skip))] everything_is_visible: bool, } diff --git a/egui/src/util/id_any_map.rs b/egui/src/util/id_any_map.rs index 2dc3f7fbf628..5fd588460022 100644 --- a/egui/src/util/id_any_map.rs +++ b/egui/src/util/id_any_map.rs @@ -9,7 +9,7 @@ use std::any::Any; /// We need this because `TypeId` can't be deserialized or serialized directly, but this can be done using hashing. However, there is a small possibility that different types will have intersection by hashes of their type ids. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct TypeId(u64); impl TypeId { @@ -33,28 +33,28 @@ impl From for TypeId { // ----------------------------------------------------------------------------------------------- -#[cfg(feature = "serde")] +#[cfg(feature = "persistence")] pub trait SerializableAny: 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync { } -#[cfg(feature = "serde")] +#[cfg(feature = "persistence")] impl SerializableAny for T where T: 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync { } -#[cfg(not(feature = "serde"))] +#[cfg(not(feature = "persistence"))] pub trait SerializableAny: 'static + Any + Clone + for<'a> Send + Sync {} -#[cfg(not(feature = "serde"))] +#[cfg(not(feature = "persistence"))] impl SerializableAny for T where T: 'static + Any + Clone + for<'a> Send + Sync {} // ----------------------------------------------------------------------------------------------- #[cfg(feature = "persistence")] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] struct SerializedElement { type_id: TypeId, ron: String, @@ -435,11 +435,11 @@ fn hash(type_id: TypeId, id: Id) -> u64 { // ---------------------------------------------------------------------------- -#[cfg(feature = "serde")] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg(feature = "persistence")] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct PersistedMap(Vec<(u64, SerializedElement)>); -#[cfg(feature = "serde")] +#[cfg(feature = "persistence")] impl PersistedMap { fn from_map(map: &IdAnyMap) -> Self { Self( @@ -461,7 +461,7 @@ impl PersistedMap { } } -#[cfg(feature = "serde")] +#[cfg(feature = "persistence")] impl serde::Serialize for IdAnyMap { fn serialize(&self, serializer: S) -> Result where @@ -471,7 +471,7 @@ impl serde::Serialize for IdAnyMap { } } -#[cfg(feature = "serde")] +#[cfg(feature = "persistence")] impl<'de> serde::Deserialize<'de> for IdAnyMap { fn deserialize(deserializer: D) -> Result where @@ -485,7 +485,7 @@ impl<'de> serde::Deserialize<'de> for IdAnyMap { #[test] fn test_mix() { - #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] + #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Debug, PartialEq)] struct Foo(i32); From 65fe204d9e252285aec220ff8892c038f17f766d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 24 Oct 2021 20:25:42 +0200 Subject: [PATCH 09/11] Clean up the IdAnyMap code --- egui/src/context.rs | 7 +++++ egui/src/util/id_any_map.rs | 55 +++++++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/egui/src/context.rs b/egui/src/context.rs index 5d72ab86d37a..85c9a68a32b2 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -885,6 +885,13 @@ impl Context { *self.memory() = Default::default(); } + let num_state = self.memory().data.len(); + let num_serialized = self.memory().data.count_serialized(); + ui.label(format!( + "{} widget states stored (of which {} are serialized).", + num_state, num_serialized + )); + ui.horizontal(|ui| { ui.label(format!( "{} areas (panels, windows, popups, …)", diff --git a/egui/src/util/id_any_map.rs b/egui/src/util/id_any_map.rs index 5fd588460022..cfc91604f9dd 100644 --- a/egui/src/util/id_any_map.rs +++ b/egui/src/util/id_any_map.rs @@ -1,5 +1,5 @@ // TODO: it is possible we can simplify `Element` further by -// assuming everything is "faalible" serializable, and by supplying serialize/deserialize functions for them. +// assuming everything is possibly serializable, and by supplying serialize/deserialize functions for them. // For non-serializable types, these simply return `None`. // This will also allow users to pick their own serialization format per type. @@ -7,8 +7,8 @@ use std::any::Any; // ----------------------------------------------------------------------------------------------- -/// We need this because `TypeId` can't be deserialized or serialized directly, but this can be done using hashing. However, there is a small possibility that different types will have intersection by hashes of their type ids. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +/// Like [`std::any::TypeId`], but can be serialized and deserialized. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct TypeId(u64); @@ -64,7 +64,7 @@ struct SerializedElement { type Serializer = fn(&Box) -> Option; enum Element { - /// A value, possibly serializable + /// A value, maybe serializable. Value { /// The actual value. value: Box, @@ -72,13 +72,14 @@ enum Element { /// How to clone the value. clone_fn: fn(&Box) -> Box, - /// How to serialize the vlaue. + /// How to serialize the value. /// None if non-serializable type. #[cfg(feature = "persistence")] serialize_fn: Option, }, + /// A serialized value Serialized { - /// The type stored. + /// The type of value we are storing. type_id: TypeId, /// The ron data we can deserialize. ron: String, @@ -125,6 +126,7 @@ impl std::fmt::Debug for Element { } impl Element { + /// Create a value that won't be persisted. #[inline] pub(crate) fn new_temp(t: T) -> Self { Self::Value { @@ -138,6 +140,7 @@ impl Element { } } + /// Create a value that will be persisted. #[inline] pub(crate) fn new_persisted(t: T) -> Self { Self::Value { @@ -154,6 +157,7 @@ impl Element { } } + /// The type of the stored value. #[inline] pub(crate) fn type_id(&self) -> TypeId { match self { @@ -272,7 +276,7 @@ fn from_ron_str(ron: &str) -> Option { Ok(value) => Some(value), Err(err) => { eprintln!( - "egui: Failed to deserialize {} from memory: {}, ron: {:?}", + "egui: Failed to deserialize {} from memory: {}, ron error: {:?}", std::any::type_name::(), err, ron @@ -290,7 +294,10 @@ use crate::Id; /// Stores any value identified by their type and a given [`Id`]. /// /// Values can either be "persisted" (serializable) or "temporary" (cleared when egui is shut down). +/// +/// You can store state using the key [`Id::null`]. The state will then only be identified by its type. #[derive(Clone, Debug, Default)] +// We store use `id XOR typeid` as a key, so we don't need to hash again! pub struct IdAnyMap(nohash_hasher::IntMap); impl IdAnyMap { @@ -308,7 +315,7 @@ impl IdAnyMap { self.0.insert(hash, Element::new_persisted(value)); } - /// Read a value without trying to deserialize a persited value. + /// Read a value without trying to deserialize a persisted value. #[inline] pub fn get_temp(&mut self, id: Id) -> Option { let hash = hash(TypeId::of::(), id); @@ -391,13 +398,14 @@ impl IdAnyMap { } } + /// Remove the state of this type an id. #[inline] pub fn remove(&mut self, id: Id) { let hash = hash(TypeId::of::(), id); self.0.remove(&hash); } - /// Note that this function could not remove all needed types between runs because if you upgraded the Rust version or for other reasons. + /// Note all state of the given type. pub fn remove_by_type(&mut self) { let key = TypeId::of::(); self.0.retain(|_, e| { @@ -411,7 +419,26 @@ impl IdAnyMap { self.0.clear(); } - /// You could use this function to find is there some leak or misusage. Note, that result of this function could break between runs, if you upgraded the Rust version or for other reasons. + #[inline] + pub fn is_empty(&mut self) -> bool { + self.0.is_empty() + } + + #[inline] + pub fn len(&mut self) -> usize { + self.0.len() + } + + /// Count how many values are stored but not yet deserialized. + #[inline] + pub fn count_serialized(&mut self) -> usize { + self.0 + .values() + .filter(|e| matches!(e, Element::Serialized { .. })) + .count() + } + + /// Count the number of values are stored with the given type. pub fn count(&mut self) -> usize { let key = TypeId::of::(); self.0 @@ -422,10 +449,6 @@ impl IdAnyMap { }) .count() } - - pub fn count_all(&mut self) -> usize { - self.0.len() - } } #[inline(always)] @@ -435,13 +458,15 @@ fn hash(type_id: TypeId, id: Id) -> u64 { // ---------------------------------------------------------------------------- +/// How [`IdAnyMap`] is persisted. #[cfg(feature = "persistence")] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] -pub struct PersistedMap(Vec<(u64, SerializedElement)>); +struct PersistedMap(Vec<(u64, SerializedElement)>); #[cfg(feature = "persistence")] impl PersistedMap { fn from_map(map: &IdAnyMap) -> Self { + // filter out the elements which cannot be serialized: Self( map.0 .iter() From 9a0a0f50f3f09b4d389a2bd8c07b7f649f46ad0d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Oct 2021 01:23:12 +0200 Subject: [PATCH 10/11] Rename `IdAnyMap` to `IdTypeMap` --- egui/src/id.rs | 2 +- egui/src/memory.rs | 2 +- .../util/{id_any_map.rs => id_type_map.rs} | 127 ++++++++++++++++-- egui/src/util/mod.rs | 4 +- 4 files changed, 118 insertions(+), 17 deletions(-) rename egui/src/util/{id_any_map.rs => id_type_map.rs} (82%) diff --git a/egui/src/id.rs b/egui/src/id.rs index 9bb563b79ae5..da79bb2a6094 100644 --- a/egui/src/id.rs +++ b/egui/src/id.rs @@ -30,7 +30,7 @@ pub struct Id(u64); impl Id { - /// A special `Id`, in particular as a key to [`crate::Memory::id_map`] + /// 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, diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 836e2239e247..b5350f5cf397 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -24,7 +24,7 @@ pub struct Memory { /// This will be saved between different program runs if you use the `persistence` feature. /// /// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key. - pub data: crate::util::IdAnyMap, + pub data: crate::util::IdTypeMap, // ------------------------------------------ /// Can be used to cache computations from one frame to another. diff --git a/egui/src/util/id_any_map.rs b/egui/src/util/id_type_map.rs similarity index 82% rename from egui/src/util/id_any_map.rs rename to egui/src/util/id_type_map.rs index cfc91604f9dd..2e04b6b38e6f 100644 --- a/egui/src/util/id_any_map.rs +++ b/egui/src/util/id_type_map.rs @@ -290,17 +290,46 @@ fn from_ron_str(ron: &str) -> Option { use crate::Id; -// TODO: make generic over the key, instead of using hard-coded `Id`. -/// Stores any value identified by their type and a given [`Id`]. +// TODO: make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap. +/// Stores values identified by an [`Id`] AND a the [`std::any::TypeId`] of the value. +/// +/// so it maps `(Id, TypeId)` to any value you want. /// /// Values can either be "persisted" (serializable) or "temporary" (cleared when egui is shut down). /// /// You can store state using the key [`Id::null`]. The state will then only be identified by its type. +/// +/// ``` +/// # use egui::{Id, util::IdTypeMap}; +/// let a = Id::new("a"); +/// let b = Id::new("b"); +/// let mut map: IdTypeMap = Default::default(); +/// +/// // `a` associated with an f64 and an i32 +/// map.insert_persisted(a, 3.14); +/// map.insert_temp(a, 42); +/// +/// // `b` associated with an f64 and a `&'static str` +/// map.insert_persisted(b, 6.28); +/// map.insert_temp(b, "Hello World".to_string()); +/// +/// // we can retrieve all four values: +/// assert_eq!(map.get_temp::(a), Some(3.14)); +/// assert_eq!(map.get_temp::(a), Some(42)); +/// assert_eq!(map.get_temp::(b), Some(6.28)); +/// assert_eq!(map.get_temp::(b), Some("Hello World".to_string())); +/// +/// // we can retrieve them like so also: +/// assert_eq!(map.get_persisted::(a), Some(3.14)); +/// assert_eq!(map.get_persisted::(a), Some(42)); +/// assert_eq!(map.get_persisted::(b), Some(6.28)); +/// assert_eq!(map.get_temp::(b), Some("Hello World".to_string())); +/// ``` #[derive(Clone, Debug, Default)] // We store use `id XOR typeid` as a key, so we don't need to hash again! -pub struct IdAnyMap(nohash_hasher::IntMap); +pub struct IdTypeMap(nohash_hasher::IntMap); -impl IdAnyMap { +impl IdTypeMap { /// Insert a value that will not be persisted. #[inline] pub fn insert_temp(&mut self, id: Id, value: T) { @@ -458,14 +487,14 @@ fn hash(type_id: TypeId, id: Id) -> u64 { // ---------------------------------------------------------------------------- -/// How [`IdAnyMap`] is persisted. +/// How [`IdTypeMap`] is persisted. #[cfg(feature = "persistence")] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] struct PersistedMap(Vec<(u64, SerializedElement)>); #[cfg(feature = "persistence")] impl PersistedMap { - fn from_map(map: &IdAnyMap) -> Self { + fn from_map(map: &IdTypeMap) -> Self { // filter out the elements which cannot be serialized: Self( map.0 @@ -474,8 +503,8 @@ impl PersistedMap { .collect(), ) } - fn into_map(self) -> IdAnyMap { - IdAnyMap( + fn into_map(self) -> IdTypeMap { + IdTypeMap( self.0 .into_iter() .map(|(hash, SerializedElement { type_id, ron })| { @@ -487,7 +516,7 @@ impl PersistedMap { } #[cfg(feature = "persistence")] -impl serde::Serialize for IdAnyMap { +impl serde::Serialize for IdTypeMap { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -497,7 +526,7 @@ impl serde::Serialize for IdAnyMap { } #[cfg(feature = "persistence")] -impl<'de> serde::Deserialize<'de> for IdAnyMap { +impl<'de> serde::Deserialize<'de> for IdTypeMap { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -508,6 +537,78 @@ impl<'de> serde::Deserialize<'de> for IdAnyMap { // ---------------------------------------------------------------------------- +#[test] +fn test_two_id_two_type() { + let a = Id::new("a"); + let b = Id::new("b"); + + let mut map: IdTypeMap = Default::default(); + map.insert_persisted(a, 6.28); + map.insert_temp(b, 42); + assert_eq!(map.get_persisted::(a), Some(6.28)); + assert_eq!(map.get_persisted::(b), Some(42)); + assert_eq!(map.get_temp::(a), Some(6.28)); + assert_eq!(map.get_temp::(b), Some(42)); +} + +#[test] +fn test_two_id_x_two_types() { + #![allow(clippy::approx_constant)] + + let a = Id::new("a"); + let b = Id::new("b"); + let mut map: IdTypeMap = Default::default(); + + // `a` associated with an f64 and an i32 + map.insert_persisted(a, 3.14); + map.insert_temp(a, 42); + + // `b` associated with an f64 and a `&'static str` + map.insert_persisted(b, 6.28); + map.insert_temp(b, "Hello World".to_string()); + + // we can retrieve all four values: + assert_eq!(map.get_temp::(a), Some(3.14)); + assert_eq!(map.get_temp::(a), Some(42)); + assert_eq!(map.get_temp::(b), Some(6.28)); + assert_eq!(map.get_temp::(b), Some("Hello World".to_string())); + + // we can retrieve them like so also: + assert_eq!(map.get_persisted::(a), Some(3.14)); + assert_eq!(map.get_persisted::(a), Some(42)); + assert_eq!(map.get_persisted::(b), Some(6.28)); + assert_eq!(map.get_temp::(b), Some("Hello World".to_string())); +} + +#[test] +fn test_one_id_two_types() { + let id = Id::new("a"); + + let mut map: IdTypeMap = Default::default(); + map.insert_persisted(id, 6.28); + map.insert_temp(id, 42); + + assert_eq!(map.get_temp::(id), Some(6.28)); + assert_eq!(map.get_persisted::(id), Some(6.28)); + assert_eq!(map.get_temp::(id), Some(42)); + + // ------------ + // Test removal: + + // We can remove: + map.remove::(id); + assert_eq!(map.get_temp::(id), None); + + // Other type is still there, even though it is the same if: + assert_eq!(map.get_temp::(id), Some(6.28)); + assert_eq!(map.get_persisted::(id), Some(6.28)); + + // But we can still remove the last: + map.remove::(id); + assert_eq!(map.get_temp::(id), None); + assert_eq!(map.get_persisted::(id), None); +} + #[test] fn test_mix() { #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] @@ -519,7 +620,7 @@ fn test_mix() { let id = Id::new("a"); - let mut map: IdAnyMap = Default::default(); + let mut map: IdTypeMap = Default::default(); map.insert_persisted(id, Foo(555)); map.insert_temp(id, Bar(1.0)); @@ -557,7 +658,7 @@ fn test_mix_serialize() { let id = Id::new("a"); - let mut map: IdAnyMap = Default::default(); + let mut map: IdTypeMap = Default::default(); map.insert_persisted(id, Serializable(555)); map.insert_temp(id, NonSerializable(1.0)); @@ -597,7 +698,7 @@ fn test_mix_serialize() { // -------------------- // Test deserialization: - let mut map: IdAnyMap = ron::from_str(&serialized).unwrap(); + let mut map: IdTypeMap = ron::from_str(&serialized).unwrap(); assert_eq!(map.get_temp::(id), None); assert_eq!( map.get_persisted::(id), diff --git a/egui/src/util/mod.rs b/egui/src/util/mod.rs index 5ba8a2b84d4a..ea83ea618f69 100644 --- a/egui/src/util/mod.rs +++ b/egui/src/util/mod.rs @@ -3,10 +3,10 @@ pub mod cache; pub(crate) mod fixed_cache; mod history; -pub mod id_any_map; +pub mod id_type_map; pub mod undoer; pub use history::History; -pub use id_any_map::IdAnyMap; +pub use id_type_map::IdTypeMap; pub use epaint::util::{hash, hash_with}; From 21f1bc37ca79ca697d19773cf60c133cc074111f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Oct 2021 08:42:52 +0200 Subject: [PATCH 11/11] Add a line to the changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c752164750c..7661af8d2bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w ### Added ⭐ * Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)). +### Changed 🔧 +* Unified the four `Memory` data buckts (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)). + ## 0.15.0 - 2021-10-24 - Syntax highlighting and hscroll