From 9daba42f0015c17c747249f9aa04adbc6abcf85f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 24 May 2020 19:33:49 +0200 Subject: [PATCH] WIP: readable ids --- emigui/src/context.rs | 5 +- emigui/src/id2.rs | 124 ++++++++++++++++++++++++++++++++++++++ emigui/src/id3.rs | 92 ++++++++++++++++++++++++++++ emigui/src/id4.rs | 135 ++++++++++++++++++++++++++++++++++++++++++ emigui/src/lib.rs | 5 +- 5 files changed, 358 insertions(+), 3 deletions(-) create mode 100644 emigui/src/id2.rs create mode 100644 emigui/src/id3.rs create mode 100644 emigui/src/id4.rs diff --git a/emigui/src/context.rs b/emigui/src/context.rs index 599c4178..ab01d347 100644 --- a/emigui/src/context.rs +++ b/emigui/src/context.rs @@ -32,10 +32,11 @@ pub struct Context { output: Mutex, /// Used to debug name clashes of e.g. windows used_ids: Mutex>, - + id_interner: Arc>, paint_stats: Mutex, } +// We need to Clone a Context between each frame impl Clone for Context { fn clone(&self) -> Self { Context { @@ -48,6 +49,7 @@ impl Clone for Context { graphics: Mutex::new(self.graphics.lock().clone()), output: Mutex::new(self.output.lock().clone()), used_ids: Mutex::new(self.used_ids.lock().clone()), + id_interner: Arc::new(Mutex::new(self.id_interner.lock().clone())) paint_stats: Mutex::new(*self.paint_stats.lock()), } } @@ -67,6 +69,7 @@ impl Context { graphics: Default::default(), output: Default::default(), used_ids: Default::default(), + id_interner: Default::default(), paint_stats: Default::default(), }) } diff --git a/emigui/src/id2.rs b/emigui/src/id2.rs new file mode 100644 index 00000000..a6d7acc3 --- /dev/null +++ b/emigui/src/id2.rs @@ -0,0 +1,124 @@ +use std::{borrow::Cow, sync::Arc}; + +use ahash::AHashMap; + +// TODO: is stored Id string with own hash for faster lookups +// i.e. pub struct Id(u64, Arc); +#[derive(Clone, Eq, Hash, PartialEq)] +pub struct Id(Arc); + +impl Id { + pub fn as_string(&self) -> &String { + &self.0 + } +} + +impl std::fmt::Debug for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +// ---------------------------------------------------------------------- + +// #[derive(Eq, Hash, PartialEq)] +// pub enum Component<'a> { +// /// E.g. name of a window +// String(Cow<'a, str>), + +// /// For loop indices, hashes etc +// Int(u64), +// } + +// impl<'a> Component<'a> { +// fn to_owned(self) -> Component<'static> { +// match self { +// Component::String(s) => Component::String(s.into()), +// Component::Int(int) => Component::Int(int), +// } +// } +// } + +// impl<'a> std::fmt::Debug for Component<'a> { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// match self { +// Component::String(v) => v.fmt(f), +// Component::Int(v) => v.fmt(f), +// } +// } +// } + +// impl<'a> std::fmt::Display for Component<'a> { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// match self { +// Component::String(v) => v.fmt(f), +// Component::Int(v) => v.fmt(f), +// } +// } +// } + +// ---------------------------------------------------------------------- + +type Generation = u64; + +// One per context +#[derive(Default)] +pub struct IdInterner { + /// used to garbage-collect id:s which hasn't been used in a while + generation: Generation, + + /// Maps + children: AHashMap<(Id, Cow<'static, str>), (Generation, Id)>, +} + +impl IdInterner { + pub fn new_root(&self, root_id: &str) -> Id { + Id(Arc::new(root_id.to_string())) + } + + /// Append `comp` to `parent_id`. + /// This is pretty cheap if the same lookup was done last frame, + /// else it will cost a memory allocation + pub fn child<'a>(&mut self, parent_id: &'a Id, comp: &'a str) -> Id { + if let Some(existing) = self.children.get_mut(&(parent_id.clone(), comp.into())) { + existing.0 = self.generation; + existing.1.clone() + } else { + let child_id = Id(Arc::new(format!("{}/{}", parent_id, comp))); + self.children.insert( + (parent_id.clone(), comp.into()), + (self.generation, child_id.clone()), + ); + child_id + } + } + + /// Called by the context once per frame + pub fn gc(&mut self) { + let current_gen = self.generation; + self.children.retain(|_k, v| v.0 == current_gen); + self.generation += 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_id() { + let interner = parking_lot::Mutex::new(IdInterner::default()); + let root: Id = interner.lock().new_root("root"); + let child_a: Id = interner.lock().child(&root, Component::Int(42)); + let child_b: Id = interner.lock().child(&root, Component::Int(42)); + + assert!(root != child_a); + assert_eq!(child_a, child_b); + } +} diff --git a/emigui/src/id3.rs b/emigui/src/id3.rs new file mode 100644 index 00000000..e6f31295 --- /dev/null +++ b/emigui/src/id3.rs @@ -0,0 +1,92 @@ +use ahash::AHashMap; +// use serde_derive::{Deserialize, Serialize}; +use std::sync::Arc; + +#[derive(Clone, Eq, Hash, PartialEq)] +pub struct Id(Arc); + +impl Id { + pub fn as_string(&self) -> &String { + &self.0 + } +} + +impl std::fmt::Debug for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +// ---------------------------------------------------------------------- + +type Generation = u64; + +// One per context +#[derive(Default)] +pub struct IdInterner { + /// used to garbage-collect id:s which hasn't been used in a while + generation: Generation, + + /// Maps + children: AHashMap>, +} + +impl IdInterner { + pub fn new_root(&self, root_id: &str) -> Id { + Id(Arc::new(root_id.to_string())) + } + + /// Append `comp` to `parent_id`. + /// This is pretty cheap if the same lookup was done last frame, + /// else it will cost a memory allocation + pub fn child(&mut self, parent_id: &Id, comp: &str) -> Id { + if let Some(map) = self.children.get_mut(parent_id) { + if let Some((gen, child_id)) = map.get_mut(comp) { + *gen = self.generation; + child_id.clone() + } else { + let child_id = Id(Arc::new(format!("{}/{}", parent_id, comp))); + map.insert(comp.to_owned(), (self.generation, child_id.clone())); + child_id + } + } else { + let child_id = Id(Arc::new(format!("{}/{}", parent_id, comp))); + let mut map = AHashMap::new(); + map.insert(comp.to_owned(), (self.generation, child_id.clone())); + self.children.insert(parent_id.clone(), map); + child_id + } + } + + /// Called by the context once per frame + pub fn gc(&mut self) { + let current_gen = self.generation; + for value in self.children.values_mut() { + value.retain(|_comp, (gen, _id)| *gen == current_gen); + } + self.children.retain(|_k, v| !v.is_empty()); + self.generation += 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_id() { + let interner = parking_lot::Mutex::new(IdInterner::default()); + let root: Id = interner.lock().new_root("root"); + let child_a: Id = interner.lock().child(&root, "child"); + let child_b: Id = interner.lock().child(&root, "child"); + + assert!(root != child_a); + assert_eq!(child_a, child_b); + } +} diff --git a/emigui/src/id4.rs b/emigui/src/id4.rs new file mode 100644 index 00000000..07bab896 --- /dev/null +++ b/emigui/src/id4.rs @@ -0,0 +1,135 @@ +use ahash::AHashMap; +use std::sync::Arc; + +#[derive(Clone)] +pub struct Id { + hash: u64, // precomputed as an optimization + string: Arc, +} + +impl Id { + fn from_string(string: String) -> Id { + use std::hash::{Hash, Hasher}; + let mut hasher = ahash::AHasher::default(); + string.hash(&mut hasher); + let hash = hasher.finish(); + Id { + hash, + string: Arc::new(string), + } + } +} + +impl std::hash::Hash for Id { + fn hash(&self, hasher: &mut H) { + hasher.write_u64(self.hash); + } +} + +impl std::cmp::Eq for Id {} + +impl std::cmp::PartialEq for Id { + fn eq(&self, other: &Id) -> bool { + self.hash == other.hash + } +} + +impl std::fmt::Debug for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.string.fmt(f) + } +} + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.string.fmt(f) + } +} + +impl serde::Serialize for Id { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (*self.string).serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Id { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Id::from_string(serde::Deserialize::deserialize( + deserializer, + )?)) + } +} +// ---------------------------------------------------------------------- + +type Generation = u64; + +// One per context +#[derive(Default)] +pub struct IdInterner { + /// used to garbage-collect id:s which hasn't been used in a while + generation: Generation, + + /// Maps + children: AHashMap>, +} + +impl IdInterner { + pub fn new_root(&self, root_id: &str) -> Id { + Id::from_string(root_id.to_string()) + } + + /// Append `comp` to `parent_id`. + /// This is pretty cheap if the same lookup was done last frame, + /// else it will cost a memory allocation + pub fn child(&mut self, parent_id: &Id, comp: &str) -> Id { + if let Some(map) = self.children.get_mut(parent_id) { + if let Some((gen, child_id)) = map.get_mut(comp) { + *gen = self.generation; + child_id.clone() + } else { + let child_id = Id::from_string(format!("{}/{}", parent_id, comp)); + map.insert(comp.to_owned(), (self.generation, child_id.clone())); + child_id + } + } else { + let child_id = Id::from_string(format!("{}/{}", parent_id, comp)); + let mut map = AHashMap::default(); + map.insert(comp.to_owned(), (self.generation, child_id.clone())); + self.children.insert(parent_id.clone(), map); + child_id + } + } + + /// Called by the context once per frame + pub fn gc(&mut self) { + let current_gen = self.generation; + for value in self.children.values_mut() { + value.retain(|_comp, (gen, _id)| *gen == current_gen); + } + self.children.retain(|_k, v| !v.is_empty()); + self.generation += 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_id() { + let interner = parking_lot::Mutex::new(IdInterner::default()); + let root: Id = interner.lock().new_root("root"); + let child_a: Id = interner.lock().child(&root, "child"); + let child_b: Id = interner.lock().child(&root, "child"); + + assert!(root != child_a); + assert_eq!(child_a, child_b); + assert_eq!(child_a.to_string(), "root/child"); + } +} diff --git a/emigui/src/lib.rs b/emigui/src/lib.rs index 43a3fa43..d20e7416 100644 --- a/emigui/src/lib.rs +++ b/emigui/src/lib.rs @@ -25,7 +25,8 @@ pub mod containers; mod context; pub mod examples; -mod id; +// mod id; +mod id4; mod input; mod introspection; mod layers; @@ -41,7 +42,7 @@ pub mod widgets; pub use { context::Context, - id::Id, + id4::Id, input::*, layers::*, layout::*,