public inbox for yew-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [yew-devel] [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo
@ 2025-09-29 13:12 Dominik Csapak
  2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 2/3] state: keyed slab tree: add key -> node_id cache Dominik Csapak
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Dominik Csapak @ 2025-09-29 13:12 UTC (permalink / raw)
  To: yew-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/state/tree_store/keyed_slab_tree.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/state/tree_store/keyed_slab_tree.rs b/src/state/tree_store/keyed_slab_tree.rs
index b910bec..e26780c 100644
--- a/src/state/tree_store/keyed_slab_tree.rs
+++ b/src/state/tree_store/keyed_slab_tree.rs
@@ -90,7 +90,7 @@ impl<T> KeyedSlabTreeNodeMut<'_, T> {
         self.visit_children(visitor);
     }
 
-    /// Retunrs the unique record key.
+    /// Returns the unique record key.
     pub fn key(&self) -> Key {
         self.tree.extract_key(self.record())
     }
-- 
2.47.3



_______________________________________________
yew-devel mailing list
yew-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [yew-devel] [PATCH yew-widget-toolkit 2/3] state: keyed slab tree: add key -> node_id cache
  2025-09-29 13:12 [yew-devel] [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dominik Csapak
@ 2025-09-29 13:12 ` Dominik Csapak
  2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 3/3] state: keyed slab tree: panic on inserting duplicate keys in debug mode Dominik Csapak
  2025-10-03  6:22 ` [yew-devel] applied: [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dietmar Maurer
  2 siblings, 0 replies; 4+ messages in thread
From: Dominik Csapak @ 2025-09-29 13:12 UTC (permalink / raw)
  To: yew-devel

The methods `find_node_by_key` and `find_node_by_key_mut` have to
linearly search for the key in the tree. When doing that for a large
number of keys, this becomes very slow.

To speed that up, introduce a `key_cache`, which is just a
`HashMap<Key, uszie>` that points to the node_id in the slab tree.

We have to be careful to update this cache with every insert or remove
operation.

* on insert, add the new entry into the cache
* on removal, remove the entry and do it recursively for all children of
  that node
* on bulk append: iterate over the newly added subtree and add the keys
* on tree/root replace: recalculate the cache for the whole tree.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/state/tree_store/keyed_slab_tree.rs | 94 ++++++++++++++++++-------
 1 file changed, 69 insertions(+), 25 deletions(-)

diff --git a/src/state/tree_store/keyed_slab_tree.rs b/src/state/tree_store/keyed_slab_tree.rs
index e26780c..df163a2 100644
--- a/src/state/tree_store/keyed_slab_tree.rs
+++ b/src/state/tree_store/keyed_slab_tree.rs
@@ -1,5 +1,5 @@
-use std::cmp::Ordering;
 use std::collections::HashSet;
+use std::{cmp::Ordering, collections::HashMap};
 
 use slab::Slab;
 
@@ -30,6 +30,8 @@ pub struct KeyedSlabTree<T> {
 
     listeners: Slab<Callback<()>>,
 
+    key_cache: HashMap<Key, usize>,
+
     pub(crate) view_root: bool,
 }
 
@@ -98,7 +100,7 @@ impl<T> KeyedSlabTreeNodeMut<'_, T> {
     /// Find a node by key.
     pub fn find_node_by_key(&self, key: &Key) -> Option<KeyedSlabTreeNodeRef<T>> {
         self.tree
-            .find_subnode_by_key(self.node_id, key)
+            .find_subnode_by_key(key)
             .map(|node_id| KeyedSlabTreeNodeRef {
                 node_id,
                 tree: self.tree,
@@ -108,7 +110,7 @@ impl<T> KeyedSlabTreeNodeMut<'_, T> {
     /// Find a node by key (mutable).
     pub fn find_node_by_key_mut(&mut self, key: &Key) -> Option<KeyedSlabTreeNodeMut<T>> {
         self.tree
-            .find_subnode_by_key(self.node_id, key)
+            .find_subnode_by_key(key)
             .map(|node_id| KeyedSlabTreeNodeMut {
                 node_id,
                 tree: self.tree,
@@ -189,7 +191,7 @@ impl<T> KeyedSlabTreeNodeRef<'_, T> {
     /// Find a node by key.
     pub fn find_node_by_key(&self, key: &Key) -> Option<KeyedSlabTreeNodeRef<T>> {
         self.tree
-            .find_subnode_by_key(self.node_id, key)
+            .find_subnode_by_key(key)
             .map(|node_id| KeyedSlabTreeNodeRef {
                 node_id,
                 tree: self.tree,
@@ -240,6 +242,7 @@ impl<T> KeyedSlabTree<T> {
             filter: None,
             listeners: Slab::new(),
             view_root: true,
+            key_cache: HashMap::new(),
         }
     }
 
@@ -374,7 +377,10 @@ impl<T> KeyedSlabTree<T> {
     ///
     /// The current tree (if any) is discarded.
     pub fn set_root(&mut self, record: T) -> KeyedSlabTreeNodeMut<T> {
+        self.key_cache.clear();
+        let key = self.extract_key(&record);
         let node = self.tree.set_root(record);
+        self.key_cache.insert(key, node.node_id);
         KeyedSlabTreeNodeMut {
             node_id: node.node_id,
             tree: self,
@@ -386,6 +392,7 @@ impl<T> KeyedSlabTree<T> {
     /// The current tree (if any) is discarded.
     pub fn set_root_tree(&mut self, data: impl Into<SlabTree<T>>) {
         self.tree.set_root_tree(data);
+        self.key_cache = self.recalculate_cache();
     }
 
     /// Set the whole tree, but safe/restore expanded state
@@ -396,7 +403,7 @@ impl<T> KeyedSlabTree<T> {
             Some(root) => root.extract_expanded_state(),
             None => HashSet::new(),
         };
-        self.tree.set_root_tree(data);
+        self.set_root_tree(data);
         if let Some(mut root) = self.root_mut() {
             root.apply_expanded_state(&expanded_state);
         }
@@ -437,15 +444,62 @@ impl<T> KeyedSlabTree<T> {
         level: usize,
         parent: usize,
     ) -> usize {
-        self.tree
-            .append_subtree_node(subtree, subtree_node, level, parent)
+        let node_id = self
+            .tree
+            .append_subtree_node(subtree, subtree_node, level, parent);
+
+        let key_cache = self.calculate_cache(node_id);
+        self.key_cache.extend(key_cache);
+
+        node_id
+    }
+
+    fn remove_subtree_from_cache(&mut self, node_id: usize) {
+        if let Some(entry) = self.tree.get(node_id) {
+            self.key_cache.remove(&self.extract_key(&entry.record));
+            if let Some(children) = entry.children.clone() {
+                for child in children {
+                    self.remove_subtree_from_cache(child);
+                }
+            }
+        }
     }
 
     fn remove_node_id(&mut self, node_id: usize) -> Option<T> {
+        self.remove_subtree_from_cache(node_id);
         self.tree.remove_node_id(node_id)
     }
 
+    fn calculate_cache(&self, node_id: usize) -> HashMap<Key, usize> {
+        let mut map = HashMap::new();
+        if let Some(node) = self.get(node_id) {
+            let key = self.extract_key(&node.record);
+            map.insert(key, node_id);
+            match node.children.clone() {
+                Some(list) => {
+                    for child_id in list {
+                        map.extend(self.calculate_cache(child_id));
+                    }
+                }
+                None => {}
+            }
+        }
+
+        map
+    }
+
+    fn recalculate_cache(&self) -> HashMap<Key, usize> {
+        match self.tree.root_id {
+            Some(root_id) => self.calculate_cache(root_id),
+            None => HashMap::new(),
+        }
+    }
+
     fn remove_tree_node_id(&mut self, node_id: usize) -> Option<KeyedSlabTree<T>> {
+        let key_cache = self.calculate_cache(node_id);
+        for key in key_cache.keys() {
+            self.key_cache.remove(key);
+        }
         match self.tree.remove_tree_node_id(node_id) {
             Some(tree) => Some(KeyedSlabTree {
                 extract_key: self.extract_key.clone(),
@@ -456,37 +510,27 @@ impl<T> KeyedSlabTree<T> {
                 filter: None,
                 listeners: Slab::new(),
                 view_root: true,
+                key_cache,
             }),
             None => None,
         }
     }
 
     fn insert_record(&mut self, record: T, parent_id: Option<usize>) -> usize {
-        self.tree.insert_record(record, parent_id)
+        let key = self.extract_key(&record);
+        let node_id = self.tree.insert_record(record, parent_id);
+        self.key_cache.insert(key, node_id);
+        node_id
     }
 
-    fn find_subnode_by_key(&self, node_id: usize, key: &Key) -> Option<usize> {
-        let entry = self.get(node_id)?;
-
-        if key == &self.extract_key(&entry.record) {
-            return Some(node_id);
-        }
-
-        if let Some(children) = &entry.children {
-            for child_id in children {
-                if let Some(pos) = self.find_subnode_by_key(*child_id, key) {
-                    return Some(pos);
-                }
-            }
-        }
-
-        None
+    fn find_subnode_by_key(&self, key: &Key) -> Option<usize> {
+        self.key_cache.get(key).copied()
     }
 
     fn find_node_by_key(&self, key: &Key) -> Option<usize> {
         match self.tree.root_id {
             None => None,
-            Some(root_id) => self.find_subnode_by_key(root_id, key),
+            Some(_root_id) => self.find_subnode_by_key(key),
         }
     }
 
-- 
2.47.3



_______________________________________________
yew-devel mailing list
yew-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [yew-devel] [PATCH yew-widget-toolkit 3/3] state: keyed slab tree: panic on inserting duplicate keys in debug mode
  2025-09-29 13:12 [yew-devel] [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dominik Csapak
  2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 2/3] state: keyed slab tree: add key -> node_id cache Dominik Csapak
@ 2025-09-29 13:12 ` Dominik Csapak
  2025-10-03  6:22 ` [yew-devel] applied: [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dietmar Maurer
  2 siblings, 0 replies; 4+ messages in thread
From: Dominik Csapak @ 2025-09-29 13:12 UTC (permalink / raw)
  To: yew-devel

check on insert/append if the keys already exist, but only in debug mode
to not unnecessarily slow down release builds.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/state/tree_store/keyed_slab_tree.rs | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/state/tree_store/keyed_slab_tree.rs b/src/state/tree_store/keyed_slab_tree.rs
index df163a2..c7044d1 100644
--- a/src/state/tree_store/keyed_slab_tree.rs
+++ b/src/state/tree_store/keyed_slab_tree.rs
@@ -449,6 +449,12 @@ impl<T> KeyedSlabTree<T> {
             .append_subtree_node(subtree, subtree_node, level, parent);
 
         let key_cache = self.calculate_cache(node_id);
+        #[cfg(debug_assertions)]
+        for key in key_cache.keys() {
+            if self.key_cache.contains_key(key) {
+                panic!("append_subtree_node: duplicate key {key:?}");
+            }
+        }
         self.key_cache.extend(key_cache);
 
         node_id
@@ -519,6 +525,10 @@ impl<T> KeyedSlabTree<T> {
     fn insert_record(&mut self, record: T, parent_id: Option<usize>) -> usize {
         let key = self.extract_key(&record);
         let node_id = self.tree.insert_record(record, parent_id);
+        #[cfg(debug_assertions)]
+        if self.key_cache.contains_key(&key) {
+            panic!("insert_record: duplicate key {key:?}");
+        }
         self.key_cache.insert(key, node_id);
         node_id
     }
-- 
2.47.3



_______________________________________________
yew-devel mailing list
yew-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [yew-devel] applied: [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo
  2025-09-29 13:12 [yew-devel] [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dominik Csapak
  2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 2/3] state: keyed slab tree: add key -> node_id cache Dominik Csapak
  2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 3/3] state: keyed slab tree: panic on inserting duplicate keys in debug mode Dominik Csapak
@ 2025-10-03  6:22 ` Dietmar Maurer
  2 siblings, 0 replies; 4+ messages in thread
From: Dietmar Maurer @ 2025-10-03  6:22 UTC (permalink / raw)
  To: Yew framework devel list at Proxmox


[-- Attachment #1.1: Type: text/plain, Size: 21 bytes --]

applied all 3 patches

[-- Attachment #1.2: Type: text/html, Size: 333 bytes --]

[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

_______________________________________________
yew-devel mailing list
yew-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2025-10-03  6:22 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-09-29 13:12 [yew-devel] [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dominik Csapak
2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 2/3] state: keyed slab tree: add key -> node_id cache Dominik Csapak
2025-09-29 13:12 ` [yew-devel] [PATCH yew-widget-toolkit 3/3] state: keyed slab tree: panic on inserting duplicate keys in debug mode Dominik Csapak
2025-10-03  6:22 ` [yew-devel] applied: [PATCH yew-widget-toolkit 1/3] state: keyed slab tree: fix typo Dietmar Maurer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal