From 8e4fd942a9a38a6aa84580c29d0b794a4fe2e2da Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Oct 2021 08:51:34 +0200 Subject: [PATCH] Refactor memory data (#836) This refactors the widget state storage introduced by @optozorax in https://github.com/emilk/egui/pull/257 * Unify the four buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `data`. * Less complexity, and also less chance of error (storing in one bucket, reading from another). * Store data by `Id` and `TypeId`. * Users can thus reuse the same `Id` to store many types. * Uses a simple xor of id and typeid, which is fast and good since both id and typeid are already high-entropy hashes. * Use different suffixes on the functions to pick if you want the data persisted or not (`get_temp`, `insert_persisted`, etc). * Writing with one suffix and reading with the other works. * To store state not bound to a specific `Id` (i.e. only based on type), use the new `Id::null` as the key. --- CHANGELOG.md | 3 + egui/src/any/any_map.rs | 224 ------- egui/src/any/element.rs | 63 -- egui/src/any/mod.rs | 61 -- egui/src/any/serializable/any_map.rs | 264 --------- egui/src/any/serializable/element.rs | 172 ------ 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/containers/area.rs | 3 +- egui/src/containers/collapsing_header.rs | 17 +- egui/src/containers/panel.rs | 19 +- egui/src/containers/popup.rs | 22 +- egui/src/containers/resize.rs | 14 +- egui/src/containers/scroll_area.rs | 14 +- egui/src/containers/window.rs | 14 +- egui/src/context.rs | 25 +- egui/src/grid.rs | 15 +- egui/src/id.rs | 13 +- egui/src/lib.rs | 1 - egui/src/memory.rs | 56 +- egui/src/menu.rs | 17 +- egui/src/util/id_type_map.rs | 708 +++++++++++++++++++++++ egui/src/util/mod.rs | 2 + egui/src/widgets/color_picker.rs | 32 +- egui/src/widgets/plot/mod.rs | 48 +- egui/src/widgets/text_edit.rs | 19 +- egui_demo_lib/src/apps/demo/password.rs | 4 +- egui_demo_lib/src/syntax_highlighting.rs | 30 +- epaint/src/util.rs | 5 +- 31 files changed, 917 insertions(+), 1347 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 create mode 100644 egui/src/util/id_type_map.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c752164750..7661af8d2bf 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 diff --git a/egui/src/any/any_map.rs b/egui/src/any/any_map.rs deleted file mode 100644 index 16eb4047167..00000000000 --- 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 93b9146a511..00000000000 --- 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() - } -} - -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 15ea95f7424..00000000000 --- a/egui/src/any/mod.rs +++ /dev/null @@ -1,61 +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 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}; diff --git a/egui/src/any/serializable/any_map.rs b/egui/src/any/serializable/any_map.rs deleted file mode 100644 index b7bad87d1cc..00000000000 --- a/egui/src/any/serializable/any_map.rs +++ /dev/null @@ -1,264 +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 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 0b41d0ee4b8..00000000000 --- a/egui/src/any/serializable/element.rs +++ /dev/null @@ -1,172 +0,0 @@ -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); - -enum AnyMapElementInner { - Deserialized { - value: Box, - clone_fn: fn(&Box) -> Box, - - serialize_fn: fn(&Box) -> Result, - }, - Serialized(String, TypeId), -} - -#[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, self.type_id()) - } - 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 d46b7d7e5ad..00000000000 --- 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 0c99ad796f8..00000000000 --- 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 b3223849a5c..00000000000 --- 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 e4b89fa6e03..00000000000 --- 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/containers/area.rs b/egui/src/containers/area.rs index e4b9b0cc76c..9ba2359971d 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().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 18922fbdb6e..7cb337e7133 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().data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().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 ddad03e5cad..5488667eb5b 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().data.get_persisted(bar_id) + } + + fn store(self, ctx: &Context, bar_id: Id) { + ctx.memory().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/popup.rs b/egui/src/containers/popup.rs index b17c5a08f7d..ac3368909e0 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 03069eb00d7..903d669a9c1 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().data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().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 aa63876af9c..040bf507cfd 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().data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().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 8c213c1778a..d4abd7fe41c 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 4de2edbf8a7..85c9a68a32b 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, …)", @@ -924,12 +931,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,32 +944,30 @@ impl Context { ui.horizontal(|ui| { ui.label(format!( "{} menu bars", - self.memory().id_data_temp.count::() + self.memory().data.count::() )); if ui.button("Reset").clicked() { - self.memory() - .id_data_temp - .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 7ad6943e88e..f29d61414fa 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().data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().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/id.rs b/egui/src/id.rs index 73ab904291c..da79bb2a609 100644 --- a/egui/src/id.rs +++ b/egui/src/id.rs @@ -30,10 +30,19 @@ pub struct Id(u64); impl Id { - pub(crate) fn background() -> Self { + /// A special `Id`, in particular as a key to [`crate::Memory::data`] + /// for when there is no particular widget to attach the data. + /// + /// The null `Id` is still a valid id to use in all circumstances, + /// though obviously it will lead to a lot of collisions if you do use it! + pub fn null() -> Self { Self(0) } + pub(crate) fn background() -> Self { + Self(1) + } + /// Generate a new `Id` by hashing some source (e.g. a string or integer). pub fn new(source: impl std::hash::Hash) -> Id { use std::hash::Hasher; @@ -64,7 +73,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/egui/src/lib.rs b/egui/src/lib.rs index f737646d590..cbff870b710 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 76c7385faad..b5350f5cf39 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}; // ---------------------------------------------------------------------------- @@ -10,45 +10,21 @@ use crate::{any, 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 `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))] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "persistence", 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. - #[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, + /// + /// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key. + pub data: crate::util::IdTypeMap, // ------------------------------------------ /// Can be used to cache computations from one frame to another. @@ -74,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/menu.rs b/egui/src/menu.rs index ae31ca5fc0b..820ebc4a78d 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() + .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().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/util/id_type_map.rs b/egui/src/util/id_type_map.rs new file mode 100644 index 00000000000..2e04b6b38e6 --- /dev/null +++ b/egui/src/util/id_type_map.rs @@ -0,0 +1,708 @@ +// TODO: it is possible we can simplify `Element` further by +// 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. + +use std::any::Any; + +// ----------------------------------------------------------------------------------------------- + +/// 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); + +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 = "persistence")] +pub trait SerializableAny: + 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync +{ +} + +#[cfg(feature = "persistence")] +impl SerializableAny for T where + T: 'static + Any + Clone + serde::Serialize + for<'a> serde::Deserialize<'a> + Send + Sync +{ +} + +#[cfg(not(feature = "persistence"))] +pub trait SerializableAny: 'static + Any + Clone + for<'a> Send + Sync {} + +#[cfg(not(feature = "persistence"))] +impl SerializableAny for T where T: 'static + Any + Clone + for<'a> Send + Sync {} + +// ----------------------------------------------------------------------------------------------- + +#[cfg(feature = "persistence")] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] +struct SerializedElement { + type_id: TypeId, + ron: String, +} + +#[cfg(feature = "persistence")] +type Serializer = fn(&Box) -> Option; + +enum Element { + /// A value, maybe serializable. + Value { + /// The actual value. + value: Box, + + /// How to clone the value. + clone_fn: fn(&Box) -> Box, + + /// How to serialize the value. + /// None if non-serializable type. + #[cfg(feature = "persistence")] + serialize_fn: Option, + }, + /// A serialized value + Serialized { + /// The type of value we are storing. + type_id: TypeId, + /// The ron data we can deserialize. + 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::Value") + .field("type_id", &value.type_id()) + .finish_non_exhaustive(), + Self::Serialized { type_id, ron } => f + .debug_struct("MaybeSerializable::Serialized") + .field("type_id", &type_id) + .field("ron", &ron) + .finish(), + } + } +} + +impl Element { + /// Create a value that won't be persisted. + #[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, + } + } + + /// Create a value that will be persisted. + #[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).ok() + }), + } + } + + /// The type of the stored value. + #[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, + } + } + + #[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(), + + #[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)?; + 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 error: {:?}", + std::any::type_name::(), + err, + ron + ); + None + } + } +} + +// ----------------------------------------------------------------------------------------------- + +use crate::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 IdTypeMap(nohash_hasher::IntMap); + +impl IdTypeMap { + /// Insert a value that will not be persisted. + #[inline] + pub fn insert_temp(&mut self, id: Id, value: T) { + let hash = hash(TypeId::of::(), id); + self.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 persisted value. + #[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() + } + + /// Read a value, optionally deserializing it if available. + #[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 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), + } + } + + /// 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 all state of the given type. + 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(); + } + + #[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 + .iter() + .filter(|(_, e)| { + let e: &Element = e; + e.type_id() == key + }) + .count() + } +} + +#[inline(always)] +fn hash(type_id: TypeId, id: Id) -> u64 { + type_id.value() ^ id.value() +} + +// ---------------------------------------------------------------------------- + +/// 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: &IdTypeMap) -> Self { + // filter out the elements which cannot be serialized: + Self( + map.0 + .iter() + .filter_map(|(&hash, element)| Some((hash, element.to_serialize()?))) + .collect(), + ) + } + fn into_map(self) -> IdTypeMap { + IdTypeMap( + self.0 + .into_iter() + .map(|(hash, SerializedElement { type_id, ron })| { + (hash, Element::Serialized { type_id, ron }) + }) + .collect(), + ) + } +} + +#[cfg(feature = "persistence")] +impl serde::Serialize for IdTypeMap { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + PersistedMap::from_map(self).serialize(serializer) + } +} + +#[cfg(feature = "persistence")] +impl<'de> serde::Deserialize<'de> for IdTypeMap { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + ::deserialize(deserializer).map(PersistedMap::into_map) + } +} + +// ---------------------------------------------------------------------------- + +#[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))] + #[derive(Clone, Debug, PartialEq)] + struct Foo(i32); + + #[derive(Clone, Debug, PartialEq)] + struct Bar(f32); + + let id = Id::new("a"); + + let mut map: IdTypeMap = 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: IdTypeMap = 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 = ron::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: IdTypeMap = ron::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/util/mod.rs b/egui/src/util/mod.rs index c5b96a32b1a..ea83ea618f6 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_type_map; pub mod undoer; pub use history::History; +pub use id_type_map::IdTypeMap; pub use epaint::util::{hash, hash_with}; diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 794713507d5..c709d1ae1cf 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 82fc0856118..98ad69a23c2 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().data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().data.insert_persisted(id, self); + } +} + // ---------------------------------------------------------------------------- /// A 2D plot, e.g. a graph of a function. @@ -342,17 +352,13 @@ 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 { - 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 { @@ -363,7 +369,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 +517,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 28779cf7727..a7dd5f8ee13 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().data.get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.memory().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(); // 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 4b040d5a744..2328f11eaef 100644 --- a/egui_demo_lib/src/apps/demo/password.rs +++ b/egui_demo_lib/src/apps/demo/password.rs @@ -27,7 +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_temp.get_or_default::(id); + 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 @@ -51,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_temp.insert(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 f6a2fa5b7bd..9603f5ae508 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() - .id_data - .get_or_insert_with(egui::Id::new("dark"), CodeTheme::dark) + ctx.memory() + .data + .get_persisted(egui::Id::new("dark")) + .unwrap_or_else(CodeTheme::dark) } else { - *ctx.memory() - .id_data - .get_or_insert_with(egui::Id::new("light"), CodeTheme::light) + ctx.memory() + .data + .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() + .data + .insert_persisted(egui::Id::new("dark"), *self); } else { - ctx.memory().id_data.insert(egui::Id::new("light"), *self); + ctx.memory() + .data + .insert_persisted(egui::Id::new("light"), *self); } } } @@ -229,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); @@ -271,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)) diff --git a/epaint/src/util.rs b/epaint/src/util.rs index 0f47c1f93af..a321113d1d2 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.