From ece4f38080b086f3a0b2fc3cea4e57d2c16350f4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 15:05:47 +0200 Subject: [PATCH 1/8] Add generations to serialized state --- crates/egui/src/util/id_type_map.rs | 112 +++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 1f2960e64f0..dcc8aa0535c 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -58,7 +58,11 @@ impl SerializableAny for T where T: 'static + Any + Clone + for<'a> Send + Sy #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] struct SerializedElement { type_id: TypeId, + ron: Arc, + + /// Increased by one each time we re-serialize an element that wss never deserialized. + generation: usize, } #[cfg(feature = "persistence")] @@ -86,6 +90,10 @@ enum Element { /// The ron data we can deserialize. ron: Arc, + + /// Increased by one each time we re-serialize an element that wss never deserialized. + /// Used for garbage collection. + generation: usize, }, } @@ -104,9 +112,14 @@ impl Clone for Element { serialize_fn: *serialize_fn, }, - Self::Serialized { type_id, ron } => Self::Serialized { + Self::Serialized { + type_id, + ron, + generation, + } => Self::Serialized { type_id: *type_id, ron: ron.clone(), + generation: *generation, }, } } @@ -116,13 +129,18 @@ 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") + .debug_struct("Element::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) + Self::Serialized { + type_id, + ron, + generation, + } => f + .debug_struct("Element::Serialized") + .field("type_id", type_id) + .field("ron", ron) + .field("generation", generation) .finish(), } } @@ -268,14 +286,20 @@ impl Element { Some(SerializedElement { type_id: (**value).type_id().into(), ron: ron.into(), + generation: 1, }) } else { None } } - Self::Serialized { type_id, ron } => Some(SerializedElement { + Self::Serialized { + type_id, + ron, + generation, + } => Some(SerializedElement { type_id: *type_id, ron: ron.clone(), + generation: *generation, }), } } @@ -447,6 +471,17 @@ impl IdTypeMap { } } + /// For tests + #[cfg(feature = "persistence")] + #[allow(unused)] + fn get_generation(&self, id: Id) -> Option { + let element = self.0.get(&hash(TypeId::of::(), id))?; + match element { + Element::Value { .. } => Some(0), + Element::Serialized { generation, .. } => Some(*generation), + } + } + /// Remove the state of this type an id. #[inline] pub fn remove(&mut self, id: Id) { @@ -530,9 +565,25 @@ impl PersistedMap { IdTypeMap( self.0 .into_iter() - .map(|(hash, SerializedElement { type_id, ron })| { - (hash, Element::Serialized { type_id, ron }) - }) + .map( + |( + hash, + SerializedElement { + type_id, + ron, + generation, + }, + )| { + ( + hash, + Element::Serialized { + type_id, + ron, + generation: generation + 1, // This is where we increment the generation! + }, + ) + }, + ) .collect(), ) } @@ -731,3 +782,44 @@ fn test_mix_serialize() { ); assert_eq!(map.get_temp::(id), Some(Serializable(555))); } + +#[cfg(feature = "persistence")] +#[test] +fn test_serialize_generations() { + use serde::{Deserialize, Serialize}; + + fn serialize_and_deserialize(map: &IdTypeMap) -> IdTypeMap { + let serialized = ron::to_string(map).unwrap(); + ron::from_str(&serialized).unwrap() + } + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + struct A(i32); + + let mut map: IdTypeMap = Default::default(); + for i in 0..3 { + map.insert_persisted(Id::new(i), A(i)); + } + for i in 0..3 { + assert_eq!(map.get_generation::(Id::new(i)), Some(0)); + } + + map = serialize_and_deserialize(&map); + + // We use generation 0 for non-serilized, + // 1 for things that have been serialized but never deserialized, + // and then we increment with 1 on each deserialize. + // So we should have generation 2 now: + for i in 0..3 { + assert_eq!(map.get_generation::(Id::new(i)), Some(2)); + } + + // Reading should reset: + assert_eq!(map.get_persisted::(Id::new(0)), Some(A(0))); + assert_eq!(map.get_generation::(Id::new(0)), Some(0)); + + // Generations should increment: + map = serialize_and_deserialize(&map); + assert_eq!(map.get_generation::(Id::new(0)), Some(2)); + assert_eq!(map.get_generation::(Id::new(1)), Some(3)); +} From ee9b1577f20333f40f1799ecede6625e70b1ad45 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 15:08:18 +0200 Subject: [PATCH 2/8] Make IdTypeMap into a field struct --- crates/egui/src/util/id_type_map.rs | 74 +++++++++++++++-------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index dcc8aa0535c..00f7fa1608a 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -367,21 +367,23 @@ use crate::Id; /// ``` #[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); +pub struct IdTypeMap { + map: 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)); + self.map.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)); + self.map.insert(hash, Element::new_persisted(value)); } /// Read a value without trying to deserialize a persisted value. @@ -390,7 +392,7 @@ impl IdTypeMap { #[inline] pub fn get_temp(&self, id: Id) -> Option { let hash = hash(TypeId::of::(), id); - self.0.get(&hash).and_then(|x| x.get_temp()).cloned() + self.map.get(&hash).and_then(|x| x.get_temp()).cloned() } /// Read a value, optionally deserializing it if available. @@ -402,7 +404,7 @@ impl IdTypeMap { #[inline] pub fn get_persisted(&mut self, id: Id) -> Option { let hash = hash(TypeId::of::(), id); - self.0 + self.map .get_mut(&hash) .and_then(|x| x.get_mut_persisted()) .cloned() @@ -442,7 +444,7 @@ impl IdTypeMap { ) -> &mut T { let hash = hash(TypeId::of::(), id); use std::collections::hash_map::Entry; - match self.0.entry(hash) { + match self.map.entry(hash) { Entry::Vacant(vacant) => vacant .insert(Element::new_temp(insert_with())) .get_mut_temp() @@ -460,7 +462,7 @@ impl IdTypeMap { ) -> &mut T { let hash = hash(TypeId::of::(), id); use std::collections::hash_map::Entry; - match self.0.entry(hash) { + match self.map.entry(hash) { Entry::Vacant(vacant) => vacant .insert(Element::new_persisted(insert_with())) .get_mut_persisted() @@ -475,7 +477,7 @@ impl IdTypeMap { #[cfg(feature = "persistence")] #[allow(unused)] fn get_generation(&self, id: Id) -> Option { - let element = self.0.get(&hash(TypeId::of::(), id))?; + let element = self.map.get(&hash(TypeId::of::(), id))?; match element { Element::Value { .. } => Some(0), Element::Serialized { generation, .. } => Some(*generation), @@ -486,13 +488,13 @@ impl IdTypeMap { #[inline] pub fn remove(&mut self, id: Id) { let hash = hash(TypeId::of::(), id); - self.0.remove(&hash); + self.map.remove(&hash); } /// Note all state of the given type. pub fn remove_by_type(&mut self) { let key = TypeId::of::(); - self.0.retain(|_, e| { + self.map.retain(|_, e| { let e: &Element = e; e.type_id() != key }); @@ -500,23 +502,23 @@ impl IdTypeMap { #[inline] pub fn clear(&mut self) { - self.0.clear(); + self.map.clear(); } #[inline] pub fn is_empty(&self) -> bool { - self.0.is_empty() + self.map.is_empty() } #[inline] pub fn len(&self) -> usize { - self.0.len() + self.map.len() } /// Count how many values are stored but not yet deserialized. #[inline] pub fn count_serialized(&self) -> usize { - self.0 + self.map .values() .filter(|e| matches!(e, Element::Serialized { .. })) .count() @@ -525,7 +527,7 @@ impl IdTypeMap { /// Count the number of values are stored with the given type. pub fn count(&self) -> usize { let key = TypeId::of::(); - self.0 + self.map .iter() .filter(|(_, e)| { let e: &Element = e; @@ -553,7 +555,7 @@ impl PersistedMap { crate::profile_function!(); // filter out the elements which cannot be serialized: Self( - map.0 + map.map .iter() .filter_map(|(&hash, element)| Some((hash, element.to_serialize()?))) .collect(), @@ -562,30 +564,30 @@ impl PersistedMap { fn into_map(self) -> IdTypeMap { crate::profile_function!(); - IdTypeMap( - self.0 - .into_iter() - .map( - |( + let map = self + .0 + .into_iter() + .map( + |( + hash, + SerializedElement { + type_id, + ron, + generation, + }, + )| { + ( hash, - SerializedElement { + Element::Serialized { type_id, ron, - generation, + generation: generation + 1, // This is where we increment the generation! }, - )| { - ( - hash, - Element::Serialized { - type_id, - ron, - generation: generation + 1, // This is where we increment the generation! - }, - ) - }, - ) - .collect(), - ) + ) + }, + ) + .collect(); + IdTypeMap { map } } } From ab7b118c46ddf9969ba498d929453e1c8c03de11 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 15:28:18 +0200 Subject: [PATCH 3/8] Less code duplication --- crates/egui/src/util/id_type_map.rs | 73 ++++++++++------------------- 1 file changed, 26 insertions(+), 47 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 00f7fa1608a..99f610ac677 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -56,12 +56,17 @@ impl SerializableAny for T where T: 'static + Any + Clone + for<'a> Send + Sy #[cfg(feature = "persistence")] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone, Debug)] struct SerializedElement { + /// The type of value we are storing. type_id: TypeId, + /// The ron data we can deserialize. ron: Arc, - /// Increased by one each time we re-serialize an element that wss never deserialized. + /// Increased by one each time we re-serialize an element that was never deserialized. + /// + /// Used to garbage collect old values that hasn't been read in a while. generation: usize, } @@ -84,17 +89,7 @@ enum Element { }, /// A serialized value - Serialized { - /// The type of value we are storing. - type_id: TypeId, - - /// The ron data we can deserialize. - ron: Arc, - - /// Increased by one each time we re-serialize an element that wss never deserialized. - /// Used for garbage collection. - generation: usize, - }, + Serialized(SerializedElement), } impl Clone for Element { @@ -112,15 +107,7 @@ impl Clone for Element { serialize_fn: *serialize_fn, }, - Self::Serialized { - type_id, - ron, - generation, - } => Self::Serialized { - type_id: *type_id, - ron: ron.clone(), - generation: *generation, - }, + Self::Serialized(element) => Self::Serialized(element.clone()), } } } @@ -132,11 +119,11 @@ impl std::fmt::Debug for Element { .debug_struct("Element::Value") .field("type_id", &value.type_id()) .finish_non_exhaustive(), - Self::Serialized { + Self::Serialized(SerializedElement { type_id, ron, generation, - } => f + }) => f .debug_struct("Element::Serialized") .field("type_id", type_id) .field("ron", ron) @@ -183,7 +170,7 @@ impl Element { pub(crate) fn type_id(&self) -> TypeId { match self { Self::Value { value, .. } => (**value).type_id().into(), - Self::Serialized { type_id, .. } => *type_id, + Self::Serialized(SerializedElement { type_id, .. }) => *type_id, } } @@ -191,7 +178,7 @@ impl Element { pub(crate) fn get_temp(&self) -> Option<&T> { match self { Self::Value { value, .. } => value.downcast_ref(), - Self::Serialized { .. } => None, + Self::Serialized(_) => None, } } @@ -199,7 +186,7 @@ impl Element { pub(crate) fn get_mut_temp(&mut self) -> Option<&mut T> { match self { Self::Value { value, .. } => value.downcast_mut(), - Self::Serialized { .. } => None, + Self::Serialized(_) => None, } } @@ -214,14 +201,14 @@ impl Element { *self = Self::new_temp(insert_with()); } } - Self::Serialized { .. } => { + 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!(), + Self::Serialized(_) => unreachable!(), } } @@ -238,19 +225,19 @@ impl Element { } #[cfg(feature = "persistence")] - Self::Serialized { ron, .. } => { + Self::Serialized(SerializedElement { ron, .. }) => { *self = Self::new_persisted(from_ron_str::(ron).unwrap_or_else(insert_with)); } #[cfg(not(feature = "persistence"))] - Self::Serialized { .. } => { + 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!(), + Self::Serialized(_) => unreachable!(), } } @@ -259,17 +246,17 @@ impl Element { Self::Value { value, .. } => value.downcast_mut(), #[cfg(feature = "persistence")] - Self::Serialized { ron, .. } => { + Self::Serialized(SerializedElement { ron, .. }) => { *self = Self::new_persisted(from_ron_str::(ron)?); match self { Self::Value { value, .. } => value.downcast_mut(), - Self::Serialized { .. } => unreachable!(), + Self::Serialized(_) => unreachable!(), } } #[cfg(not(feature = "persistence"))] - Self::Serialized { .. } => None, + Self::Serialized(_) => None, } } @@ -292,15 +279,7 @@ impl Element { None } } - Self::Serialized { - type_id, - ron, - generation, - } => Some(SerializedElement { - type_id: *type_id, - ron: ron.clone(), - generation: *generation, - }), + Self::Serialized(element) => Some(element.clone()), } } } @@ -480,7 +459,7 @@ impl IdTypeMap { let element = self.map.get(&hash(TypeId::of::(), id))?; match element { Element::Value { .. } => Some(0), - Element::Serialized { generation, .. } => Some(*generation), + Element::Serialized(SerializedElement { generation, .. }) => Some(*generation), } } @@ -520,7 +499,7 @@ impl IdTypeMap { pub fn count_serialized(&self) -> usize { self.map .values() - .filter(|e| matches!(e, Element::Serialized { .. })) + .filter(|e| matches!(e, Element::Serialized(_))) .count() } @@ -578,11 +557,11 @@ impl PersistedMap { )| { ( hash, - Element::Serialized { + Element::Serialized(SerializedElement { type_id, ron, generation: generation + 1, // This is where we increment the generation! - }, + }), ) }, ) From 463152875cf87062605078cc1288a53e69d1d691 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 15:46:54 +0200 Subject: [PATCH 4/8] Implement garbage collection during serialization --- crates/egui/src/util/id_type_map.rs | 100 +++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 99f610ac677..ffc39396f57 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -3,8 +3,8 @@ // 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; use std::sync::Arc; +use std::{any::Any, collections::BTreeMap}; // ----------------------------------------------------------------------------------------------- @@ -32,6 +32,8 @@ impl From for TypeId { } } +impl nohash_hasher::IsEnabled for TypeId {} + // ----------------------------------------------------------------------------------------------- #[cfg(feature = "persistence")] @@ -66,6 +68,8 @@ struct SerializedElement { /// Increased by one each time we re-serialize an element that was never deserialized. /// + /// Large value = old value that hasn't been read in a while. + /// /// Used to garbage collect old values that hasn't been read in a while. generation: usize, } @@ -344,10 +348,21 @@ use crate::Id; /// assert_eq!(map.get_persisted::(b), Some(13.37)); /// assert_eq!(map.get_temp::(b), Some("Hello World".to_owned())); /// ``` -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] // We store use `id XOR typeid` as a key, so we don't need to hash again! pub struct IdTypeMap { map: nohash_hasher::IntMap, + + max_bytes_per_type: usize, +} + +impl Default for IdTypeMap { + fn default() -> Self { + Self { + map: Default::default(), + max_bytes_per_type: 1_000_000, + } + } } impl IdTypeMap { @@ -514,6 +529,20 @@ impl IdTypeMap { }) .count() } + + /// When serializing, try to not use up more than this many bytes for each type. + /// + /// The things that has most recently been read will be be prioritized during serialization. + /// + /// This value in itself will not be serialized. + pub fn max_bytes_per_type(&self) -> usize { + self.max_bytes_per_type + } + + /// See [`Self::max_bytes_per_type`]. + pub fn set_max_bytes_per_type(&mut self, max_bytes_per_type: usize) { + self.max_bytes_per_type = max_bytes_per_type; + } } #[inline(always)] @@ -532,13 +561,61 @@ struct PersistedMap(Vec<(u64, SerializedElement)>); impl PersistedMap { fn from_map(map: &IdTypeMap) -> Self { crate::profile_function!(); - // filter out the elements which cannot be serialized: - Self( - map.map - .iter() - .filter_map(|(&hash, element)| Some((hash, element.to_serialize()?))) - .collect(), - ) + + let mut types_map: nohash_hasher::IntMap = Default::default(); + #[derive(Default)] + struct TypeStats { + num_bytes: usize, + generations: BTreeMap, + } + #[derive(Default)] + struct GenerationStats { + num_bytes: usize, + elements: Vec<(u64, SerializedElement)>, + } + + let max_bytes_per_type = map.max_bytes_per_type; + + { + crate::profile_scope!("gather"); + for (hash, element) in &map.map { + if let Some(element) = element.to_serialize() { + let mut stats = types_map.entry(element.type_id).or_default(); + stats.num_bytes += element.ron.len(); + let mut generation_stats = + stats.generations.entry(element.generation).or_default(); + generation_stats.num_bytes += element.ron.len(); + generation_stats.elements.push((*hash, element)); + } else { + // temporary value that shouldn't be serialized + } + } + } + + let mut persisted = vec![]; + + { + crate::profile_scope!("gc"); + for stats in types_map.values() { + let mut bytes_written = 0; + + // Start with the most recently read values, and then go as far as we are allowed. + // Always include at least one generation. + for generation in stats.generations.values() { + if bytes_written == 0 + || bytes_written + generation.num_bytes <= max_bytes_per_type + { + persisted.append(&mut generation.elements.clone()); + bytes_written += generation.num_bytes; + } else { + // Omit the rest. The user hasn't read the values in a while. + break; + } + } + } + } + + Self(persisted) } fn into_map(self) -> IdTypeMap { @@ -566,7 +643,10 @@ impl PersistedMap { }, ) .collect(); - IdTypeMap { map } + IdTypeMap { + map, + ..Default::default() + } } } From 8195e7c768c3006ee06f16c5a8126c225ac80899 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 16:00:46 +0200 Subject: [PATCH 5/8] Add unit-test --- crates/egui/src/util/id_type_map.rs | 86 ++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index ffc39396f57..dd285f90ba4 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -349,7 +349,7 @@ use crate::Id; /// assert_eq!(map.get_temp::(b), Some("Hello World".to_owned())); /// ``` #[derive(Clone, Debug)] -// We store use `id XOR typeid` as a key, so we don't need to hash again! +// We use `id XOR typeid` as a key, so we don't need to hash again! pub struct IdTypeMap { map: nohash_hasher::IntMap, @@ -884,3 +884,87 @@ fn test_serialize_generations() { assert_eq!(map.get_generation::(Id::new(0)), Some(2)); assert_eq!(map.get_generation::(Id::new(1)), Some(3)); } + +#[test] +fn test_serialize_gc() { + use serde::{Deserialize, Serialize}; + + fn serialize_and_deserialize(mut map: IdTypeMap, max_bytes_per_type: usize) -> IdTypeMap { + map.set_max_bytes_per_type(max_bytes_per_type); + let serialized = ron::to_string(&map).unwrap(); + ron::from_str(&serialized).unwrap() + } + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + struct A(usize); + + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + struct B(usize); + + let mut map: IdTypeMap = Default::default(); + + let num_a = 1_000; + let num_b = 10; + + for i in 0..num_a { + map.insert_persisted(Id::new(i), A(i)); + } + for i in 0..num_b { + map.insert_persisted(Id::new(i), B(i)); + } + + map = serialize_and_deserialize(map, 100); + + // We always serialize at least one generation: + assert_eq!(map.count::(), num_a); + assert_eq!(map.count::(), num_b); + + // Create a new small generation: + map.insert_persisted(Id::new(1_000_000), A(1_000_000)); + map.insert_persisted(Id::new(1_000_000), B(1_000_000)); + + assert_eq!(map.count::(), num_a + 1); + assert_eq!(map.count::(), num_b + 1); + + // And read a value: + assert_eq!(map.get_persisted::(Id::new(0)), Some(A(0))); + assert_eq!(map.get_persisted::(Id::new(0)), Some(B(0))); + + map = serialize_and_deserialize(map, 100); + + assert_eq!( + map.count::(), + 2, + "We should have dropped the oldest generation, but kept the new value and the read value" + ); + assert_eq!( + map.count::(), + num_b + 1, + "B should fit under the byte limit" + ); + + // Create another small generation: + map.insert_persisted(Id::new(2_000_000), A(2_000_000)); + map.insert_persisted(Id::new(2_000_000), B(2_000_000)); + + map = serialize_and_deserialize(map, 100); + + assert_eq!(map.count::(), 3); // The read value, plus the two new ones + assert_eq!(map.count::(), num_b + 2); // all the old ones, plus two new ones + + // Lower the limit, and we should only have the latest generation: + + map = serialize_and_deserialize(map, 1); + + assert_eq!(map.count::(), 1); + assert_eq!(map.count::(), 1); + + assert_eq!( + map.get_persisted::(Id::new(2_000_000)), + Some(A(2_000_000)) + ); + assert_eq!( + map.get_persisted::(Id::new(2_000_000)), + Some(B(2_000_000)) + ); +} From 12d61875b2142b235b220ddaae54dc12a66aa8e7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 17:14:00 +0200 Subject: [PATCH 6/8] Add docstring --- crates/egui/src/util/id_type_map.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index dd285f90ba4..279a054940a 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -360,7 +360,7 @@ impl Default for IdTypeMap { fn default() -> Self { Self { map: Default::default(), - max_bytes_per_type: 1_000_000, + max_bytes_per_type: 256 * 1024, } } } @@ -530,9 +530,17 @@ impl IdTypeMap { .count() } - /// When serializing, try to not use up more than this many bytes for each type. + /// The maximum number of bytes that will be used to + /// store the persisted state of a single widget type. /// - /// The things that has most recently been read will be be prioritized during serialization. + /// Some egui widgets store persisted state that is + /// serialized to disk by some backends (e.g. `eframe`). + /// + /// Example of such widgets is `CollapsingHeader` and `Window`. + /// If you keep creating widgets with unique ids (e.g. `Windows` with many different names), + /// egui will use up more and more space for these widgets, until this limit is reached. + /// + /// Once this limit is reached, the state that was read the longest time ago will be dropped first. /// /// This value in itself will not be serialized. pub fn max_bytes_per_type(&self) -> usize { From fdda8003170eb49c975480a3da5b005bc901e177 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 17:19:00 +0200 Subject: [PATCH 7/8] Build fix --- crates/egui/src/util/id_type_map.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 279a054940a..0321b8f1f82 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -3,8 +3,7 @@ // For non-serializable types, these simply return `None`. // This will also allow users to pick their own serialization format per type. -use std::sync::Arc; -use std::{any::Any, collections::BTreeMap}; +use std::{any::Any, sync::Arc}; // ----------------------------------------------------------------------------------------------- @@ -56,7 +55,6 @@ impl SerializableAny for T where T: 'static + Any + Clone + for<'a> Send + Sy // ----------------------------------------------------------------------------------------------- -#[cfg(feature = "persistence")] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Debug)] struct SerializedElement { @@ -570,6 +568,8 @@ impl PersistedMap { fn from_map(map: &IdTypeMap) -> Self { crate::profile_function!(); + use std::collections::BTreeMap; + let mut types_map: nohash_hasher::IntMap = Default::default(); #[derive(Default)] struct TypeStats { From 4fbd3a9d57befd652182f9a676cdf7cce7b299a8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 4 Sep 2023 17:27:00 +0200 Subject: [PATCH 8/8] another fix --- crates/egui/src/util/id_type_map.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 0321b8f1f82..a9867eb1b1d 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -893,6 +893,7 @@ fn test_serialize_generations() { assert_eq!(map.get_generation::(Id::new(1)), Some(3)); } +#[cfg(feature = "persistence")] #[test] fn test_serialize_gc() { use serde::{Deserialize, Serialize};