WIP: readable ids

This commit is contained in:
Emil Ernerfeldt 2020-05-24 19:33:49 +02:00
parent 05583b892b
commit 9daba42f00
5 changed files with 358 additions and 3 deletions

View file

@ -32,10 +32,11 @@ pub struct Context {
output: Mutex<Output>,
/// Used to debug name clashes of e.g. windows
used_ids: Mutex<AHashMap<Id, Pos2>>,
id_interner: Arc<Mutex<crate::id4::IdInterner>>,
paint_stats: Mutex<PaintStats>,
}
// 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(),
})
}

124
emigui/src/id2.rs Normal file
View file

@ -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<String>);
#[derive(Clone, Eq, Hash, PartialEq)]
pub struct Id(Arc<String>);
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);
}
}

92
emigui/src/id3.rs Normal file
View file

@ -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<String>);
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<Id, AHashMap<String, (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(&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);
}
}

135
emigui/src/id4.rs Normal file
View file

@ -0,0 +1,135 @@
use ahash::AHashMap;
use std::sync::Arc;
#[derive(Clone)]
pub struct Id {
hash: u64, // precomputed as an optimization
string: Arc<String>,
}
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<H: std::hash::Hasher>(&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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
(*self.string).serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Id {
fn deserialize<D>(deserializer: D) -> Result<Id, D::Error>
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<Id, AHashMap<String, (Generation, Id)>>,
}
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");
}
}

View file

@ -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::*,