From e232b688f656f132060890a90f9b8158273a9392 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 31 Jan 2024 15:13:15 +0100 Subject: [PATCH 1/5] add support for verbatims in ketrees --- Cargo.lock | 26 ++++- Cargo.toml | 1 + commons/zenoh-keyexpr/Cargo.toml | 1 + commons/zenoh-keyexpr/benches/keyexpr_tree.rs | 14 +++ commons/zenoh-keyexpr/src/key_expr/fuzzer.rs | 5 +- .../src/keyexpr_tree/iters/inclusion.rs | 18 ++-- .../src/keyexpr_tree/iters/intersection.rs | 82 ++++++++++---- .../zenoh-keyexpr/src/keyexpr_tree/test.rs | 101 +++++++++++++++--- 8 files changed, 200 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a03e85fd79..e1db85da87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,15 +115,16 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if 1.0.0", "getrandom 0.2.10", "once_cell", "serde", "version_check", + "zerocopy", ] [[package]] @@ -4737,6 +4738,7 @@ dependencies = [ name = "zenoh-keyexpr" version = "0.11.0-dev" dependencies = [ + "ahash", "criterion", "hashbrown 0.14.0", "keyed-set", @@ -5194,6 +5196,26 @@ dependencies = [ "zenoh_backend_traits", ] +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.33", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 918c432073..9af42deeae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ description = "Zenoh: Zero Overhead Pub/sub, Store/Query and Compute." # (https://github.com/rust-lang/cargo/issues/11329) [workspace.dependencies] aes = "0.8.2" +ahash = "0.8.7" anyhow = { version = "1.0.69", default-features = false } # Default features are disabled due to usage in no_std crates async-executor = "1.5.0" async-global-executor = "2.3.1" diff --git a/commons/zenoh-keyexpr/Cargo.toml b/commons/zenoh-keyexpr/Cargo.toml index 920db98696..59e71ebee1 100644 --- a/commons/zenoh-keyexpr/Cargo.toml +++ b/commons/zenoh-keyexpr/Cargo.toml @@ -28,6 +28,7 @@ default = ["std"] std = ["zenoh-result/std", "dep:schemars"] [dependencies] +ahash = { workspace = true } keyed-set = { workspace = true } rand = { workspace = true, features = ["alloc", "getrandom"] } schemars = { workspace = true, optional = true } diff --git a/commons/zenoh-keyexpr/benches/keyexpr_tree.rs b/commons/zenoh-keyexpr/benches/keyexpr_tree.rs index f9151fdea1..7c5ba60a5a 100644 --- a/commons/zenoh-keyexpr/benches/keyexpr_tree.rs +++ b/commons/zenoh-keyexpr/benches/keyexpr_tree.rs @@ -49,6 +49,8 @@ fn main() { let mut ketree: KeBoxTree<_> = KeBoxTree::new(); let mut vectree: KeBoxTree<_, bool, VecSetProvider> = KeBoxTree::new(); let mut hashtree: KeBoxTree<_, bool, HashMapProvider> = KeBoxTree::new(); + let mut ahashtree: KeBoxTree<_, bool, HashMapProvider> = + KeBoxTree::new(); let (kearctree, mut token): (KeArcTree, _) = KeArcTree::new().unwrap(); let mut map = HashMap::new(); for key in keys.iter() { @@ -58,6 +60,7 @@ fn main() { }); b.run_once("vectree_insert", || vectree.insert(key, 0)); b.run_once("hashtree_insert", || hashtree.insert(key, 0)); + b.run_once("ahashtree_insert", || ahashtree.insert(key, 0)); b.run_once("hashmap_insert", || map.insert(key.to_owned(), 0)); } for key in keys.iter() { @@ -65,6 +68,7 @@ fn main() { b.run_once("kearctree_fetch", || kearctree.node(&token, key)); b.run_once("vectree_fetch", || vectree.node(key)); b.run_once("hashtree_fetch", || hashtree.node(key)); + b.run_once("ahashtree_fetch", || ahashtree.node(key)); b.run_once("hashmap_fetch", || map.get(key)); } for key in keys.iter() { @@ -81,6 +85,9 @@ fn main() { b.run_once("hashtree_intersect", || { hashtree.intersecting_nodes(key).count() }); + b.run_once("ahashtree_intersect", || { + ahashtree.intersecting_nodes(key).count() + }); b.run_once("hashmap_intersect", || { map.iter().filter(|(k, _)| key.intersects(k)).count() }); @@ -92,6 +99,9 @@ fn main() { }); b.run_once("vectree_include", || vectree.included_nodes(key).count()); b.run_once("hashtree_include", || hashtree.included_nodes(key).count()); + b.run_once("ahashtree_include", || { + ahashtree.included_nodes(key).count() + }); b.run_once("hashmap_include", || { map.iter().filter(|(k, _)| key.includes(k)).count() }); @@ -102,21 +112,25 @@ fn main() { "kearctree_insert", "vectree_insert", "hashtree_insert", + "ahashtree_insert", "hashmap_insert", "ketree_fetch", "kearctree_fetch", "vectree_fetch", "hashtree_fetch", + "ahashtree_fetch", "hashmap_fetch", "ketree_intersect", "kearctree_intersect", "vectree_intersect", "hashtree_intersect", + "ahashtree_intersect", "hashmap_intersect", "ketree_include", "kearctree_include", "vectree_include", "hashtree_include", + "ahashtree_include", "hashmap_include", ] { let b = results.benches.get(name).unwrap(); diff --git a/commons/zenoh-keyexpr/src/key_expr/fuzzer.rs b/commons/zenoh-keyexpr/src/key_expr/fuzzer.rs index 1405dc4802..869b7b63a1 100644 --- a/commons/zenoh-keyexpr/src/key_expr/fuzzer.rs +++ b/commons/zenoh-keyexpr/src/key_expr/fuzzer.rs @@ -15,7 +15,10 @@ use super::OwnedKeyExpr; fn random_chunk(rng: &'_ mut impl rand::Rng) -> impl Iterator + '_ { let n = rng.gen_range(1..3); - (0..n).map(move |_| rng.sample(rand::distributions::Uniform::from(b'a'..b'c'))) + rng.gen_bool(0.05) + .then_some(b'@') + .into_iter() + .chain((0..n).map(move |_| rng.sample(rand::distributions::Uniform::from(b'a'..b'c')))) } fn make(ke: &mut Vec, rng: &mut impl rand::Rng) { diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/inclusion.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/inclusion.rs index 86b20de9f1..bd875be1b9 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/inclusion.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/inclusion.rs @@ -96,6 +96,7 @@ where }; } let chunk = node.chunk(); + let chunk_is_verbatim = chunk.as_bytes()[0] == b'@'; for i in *start..*end { let kec_start = self.ke_indices[i]; if kec_start == self.key.len() { @@ -107,8 +108,10 @@ where let subkey = unsafe { keyexpr::from_slice_unchecked(&key[..kec_end]) }; if unlikely(subkey == "**") { - push!(kec_start); - push!(kec_start + kec_end + 1); + if !chunk_is_verbatim { + push!(kec_start); + push!(kec_start + kec_end + 1); + } let post_key = &key[kec_end + 1..]; match post_key.iter().position(|&c| c == b'/') { Some(sec_end) => { @@ -133,7 +136,7 @@ where } None => { let key = unsafe { keyexpr::from_slice_unchecked(key) }; - if unlikely(key == "**") { + if unlikely(key == "**") && chunk.as_bytes()[0] != b'@' { push!(kec_start); node_matches = true; } else if key.includes(chunk) { @@ -256,6 +259,7 @@ where }; } let chunk = node.chunk(); + let chunk_is_verbatim = chunk.as_bytes()[0] == b'@'; for i in *start..*end { let kec_start = self.ke_indices[i]; if kec_start == self.key.len() { @@ -267,8 +271,10 @@ where let subkey = unsafe { keyexpr::from_slice_unchecked(&key[..kec_end]) }; if unlikely(subkey == "**") { - push!(kec_start); - push!(kec_start + kec_end + 1); + if !chunk_is_verbatim { + push!(kec_start); + push!(kec_start + kec_end + 1); + } let post_key = &key[kec_end + 1..]; match post_key.iter().position(|&c| c == b'/') { Some(sec_end) => { @@ -293,7 +299,7 @@ where } None => { let key = unsafe { keyexpr::from_slice_unchecked(key) }; - if unlikely(key == "**") { + if unlikely(key == "**") && chunk.as_bytes()[0] != b'@' { push!(kec_start); node_matches = true; } else if key.includes(chunk) { diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/intersection.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/intersection.rs index 8acffecaf3..e46305adbf 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/intersection.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/intersection.rs @@ -95,14 +95,25 @@ where }; } let chunk = node.chunk(); + let chunk_is_verbatim = chunk.as_bytes()[0] == b'@'; if unlikely(chunk.as_bytes() == b"**") { - // If the current node is `**`, it is guaranteed to match + // If the current node is `**`, it is guaranteed to match... node_matches = true; // and may consume any number of chunks from the KE push!(self.ke_indices[*start]); - for i in self.ke_indices[*start]..self.key.len() { - if self.key.as_bytes()[i] == b'/' { - push!(i + 1); + if self.key.len() != self.ke_indices[*start] { + if self.key.as_bytes()[self.ke_indices[*start]] != b'@' { + for i in self.ke_indices[*start]..self.key.len() { + if self.key.as_bytes()[i] == b'/' { + push!(i + 1); + if self.key.as_bytes()[i + 1] == b'@' { + node_matches = false; // ...unless the KE contains a verbatim chunk. + break; + } + } + } + } else { + node_matches = false; } } } else { @@ -121,9 +132,11 @@ where let subkey = unsafe { keyexpr::from_slice_unchecked(&key[..kec_end]) }; if unlikely(subkey.as_bytes() == b"**") { - // If the chunk is `**`: - // children will have to process it again - push!(kec_start); + if !chunk_is_verbatim { + // If the query chunk is `**`: + // children will have to process it again + push!(kec_start); + } // and we need to process this chunk as if the `**` wasn't there, // but with the knowledge that the next chunk won't be `**`. let post_key = &key[kec_end + 1..]; @@ -144,6 +157,7 @@ where } .intersects(chunk) { + push!(self.key.len()); node_matches = true; } } @@ -155,7 +169,7 @@ where None => { // If it's the last chunk of the query, check whether it's `**` let key = unsafe { keyexpr::from_slice_unchecked(key) }; - if unlikely(key.as_bytes() == b"**") { + if unlikely(key.as_bytes() == b"**") && !chunk_is_verbatim { // If yes, it automatically matches, and must be reused from now on for iteration. push!(kec_start); node_matches = true; @@ -274,40 +288,57 @@ where macro_rules! push { ($index: expr) => { let index = $index; - if new_end == new_start - || self.ke_indices[new_start..new_end] - .iter() - .rev() - .all(|c| *c < index) - { + if new_end == new_start || self.ke_indices[new_end - 1] < index { self.ke_indices.push(index); new_end += 1; } }; } let chunk = node.chunk(); - if unlikely(chunk == "**") { + let chunk_is_verbatim = chunk.as_bytes()[0] == b'@'; + if unlikely(chunk.as_bytes() == b"**") { + // If the current node is `**`, it is guaranteed to match... node_matches = true; + // and may consume any number of chunks from the KE push!(self.ke_indices[*start]); - for i in self.ke_indices[*start]..self.key.len() { - if self.key.as_bytes()[i] == b'/' { - push!(i + 1); + if self.key.len() != self.ke_indices[*start] { + if self.key.as_bytes()[self.ke_indices[*start]] != b'@' { + for i in self.ke_indices[*start]..self.key.len() { + if self.key.as_bytes()[i] == b'/' { + push!(i + 1); + if self.key.as_bytes()[i + 1] == b'@' { + node_matches = false; // ...unless the KE contains a verbatim chunk. + break; + } + } + } + } else { + node_matches = false; } } } else { + // The current node is not `**` + // For all candidate chunks of the KE for i in *start..*end { + // construct that chunk, while checking whether or not it's the last one let kec_start = self.ke_indices[i]; - if kec_start == self.key.len() { + if unlikely(kec_start == self.key.len()) { break; } let key = &self.key.as_bytes()[kec_start..]; match key.iter().position(|&c| c == b'/') { Some(kec_end) => { + // If we aren't in the last chunk let subkey = unsafe { keyexpr::from_slice_unchecked(&key[..kec_end]) }; - if unlikely(subkey == "**") { - push!(kec_start); - push!(kec_start + kec_end + 1); + if unlikely(subkey.as_bytes() == b"**") { + if !chunk_is_verbatim { + // If the query chunk is `**`: + // children will have to process it again + push!(kec_start); + } + // and we need to process this chunk as if the `**` wasn't there, + // but with the knowledge that the next chunk won't be `**`. let post_key = &key[kec_end + 1..]; match post_key.iter().position(|&c| c == b'/') { Some(sec_end) => { @@ -326,6 +357,7 @@ where } .intersects(chunk) { + push!(self.key.len()); node_matches = true; } } @@ -335,11 +367,15 @@ where } } None => { + // If it's the last chunk of the query, check whether it's `**` let key = unsafe { keyexpr::from_slice_unchecked(key) }; - if unlikely(key == "**") { + if unlikely(key.as_bytes() == b"**") && !chunk_is_verbatim { + // If yes, it automatically matches, and must be reused from now on for iteration. push!(kec_start); node_matches = true; } else if chunk.intersects(key) { + // else, if it intersects with the chunk, make sure the children of the node + // are searched for `**` push!(self.key.len()); node_matches = true; } diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs index 933c14c354..0a65b46588 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs @@ -106,7 +106,7 @@ fn into_ke(s: &str) -> &keyexpr { keyexpr::new(s).unwrap() } -fn test_keyset>(keys: &[K]) { +fn test_keyset + Debug>(keys: &[K]) { let mut tree = KeBoxTree::new(); let mut map = HashMap::new(); for (v, k) in keys.iter().map(|k| k.deref()).enumerate() { @@ -116,6 +116,7 @@ fn test_keyset>(keys: &[K]) { assert_eq!(node.weight(), map.get(&node.keyexpr()).unwrap().as_ref()); } for target in keys { + let target = target.deref(); let mut expected = HashMap::new(); for (k, v) in &map { if target.intersects(k) { @@ -123,15 +124,27 @@ fn test_keyset>(keys: &[K]) { } } let mut exclone = expected.clone(); - for node in tree.intersecting_nodes(target) { - let ke = node.keyexpr(); + for node in tree.intersecting_nodes(dbg!(target)) { + let ke = dbg!(node.keyexpr()); let weight = node.weight(); - assert_eq!(expected.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + expected + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } for node in tree.intersecting_nodes_mut(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(exclone.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + exclone + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } assert!( expected.is_empty(), @@ -154,12 +167,24 @@ fn test_keyset>(keys: &[K]) { for node in tree.included_nodes(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(expected.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + expected + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } for node in tree.included_nodes_mut(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(exclone.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + exclone + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } assert!( expected.is_empty(), @@ -169,7 +194,7 @@ fn test_keyset>(keys: &[K]) { ); assert!( exclone.is_empty(), - "MISSING MUTABLE INTERSECTS FOR {}: {:?}", + "MISSING MUTABLE INCLUDES FOR {}: {:?}", target.deref(), &exclone ); @@ -190,6 +215,7 @@ fn test_keyset_vec>(keys: &[K]) { assert_eq!(node.weight(), map.get(&node.keyexpr()).unwrap().as_ref()); } for target in keys { + let target = target.deref(); let mut expected = HashMap::new(); for (k, v) in &map { if target.intersects(k) { @@ -200,12 +226,24 @@ fn test_keyset_vec>(keys: &[K]) { for node in tree.intersecting_nodes(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(expected.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + expected + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } for node in tree.intersecting_nodes_mut(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(exclone.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + exclone + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } assert!( expected.is_empty(), @@ -228,12 +266,24 @@ fn test_keyset_vec>(keys: &[K]) { for node in tree.included_nodes(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(expected.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + expected + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } for node in tree.included_nodes_mut(target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(exclone.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + exclone + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } assert!( expected.is_empty(), @@ -243,7 +293,7 @@ fn test_keyset_vec>(keys: &[K]) { ); assert!( exclone.is_empty(), - "MISSING MUTABLE INTERSECTS FOR {}: {:?}", + "MISSING MUTABLE INCLUDES FOR {}: {:?}", target.deref(), &exclone ); @@ -264,6 +314,7 @@ fn test_keyarctree>(keys: &[K]) { assert_eq!(node.weight(), map.get(&node.keyexpr()).unwrap().as_ref()); } for target in keys { + let target = target.deref(); let mut expected = HashMap::new(); for (k, v) in &map { if target.intersects(k) { @@ -273,7 +324,13 @@ fn test_keyarctree>(keys: &[K]) { for node in tree.0.intersecting_nodes(&tree.1, target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(expected.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + expected + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } assert!( expected.is_empty(), @@ -289,7 +346,13 @@ fn test_keyarctree>(keys: &[K]) { for node in tree.0.included_nodes(&tree.1, target) { let ke = node.keyexpr(); let weight = node.weight(); - assert_eq!(expected.remove(&ke).unwrap().as_ref(), weight) + assert_eq!( + expected + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) } assert!( expected.is_empty(), @@ -306,7 +369,7 @@ fn test_keyarctree>(keys: &[K]) { #[test] fn keyed_set_tree() { - let keys: [&keyexpr; 8] = [ + let keys: [&keyexpr; 14] = [ "a/b/**/c/**", "a/b/c", "a/b/c", @@ -315,6 +378,12 @@ fn keyed_set_tree() { "**/d", "d/b/c", "**/b/c", + "**/@c/**", + "**", + "**/@c", + "@c/**", + "@c/a", + "a/@c", ] .map(into_ke); test_keyset(&keys); From 5b81d2b43ac6845188779ac18d21d6f27670e710 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 31 Jan 2024 18:07:30 +0100 Subject: [PATCH 2/5] add nodes_including iterator for unshared ownership ketrees --- commons/zenoh-keyexpr/Cargo.toml | 2 +- .../src/keyexpr_tree/box_tree.rs | 27 ++ .../src/keyexpr_tree/iters/includer.rs | 337 ++++++++++++++++++ .../src/keyexpr_tree/iters/mod.rs | 2 + .../zenoh-keyexpr/src/keyexpr_tree/test.rs | 48 ++- .../src/keyexpr_tree/traits/mod.rs | 12 + 6 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 commons/zenoh-keyexpr/src/keyexpr_tree/iters/includer.rs diff --git a/commons/zenoh-keyexpr/Cargo.toml b/commons/zenoh-keyexpr/Cargo.toml index 59e71ebee1..41456af1ec 100644 --- a/commons/zenoh-keyexpr/Cargo.toml +++ b/commons/zenoh-keyexpr/Cargo.toml @@ -28,7 +28,6 @@ default = ["std"] std = ["zenoh-result/std", "dep:schemars"] [dependencies] -ahash = { workspace = true } keyed-set = { workspace = true } rand = { workspace = true, features = ["alloc", "getrandom"] } schemars = { workspace = true, optional = true } @@ -41,6 +40,7 @@ hashbrown = { workspace = true } # NOTE: May cause problems when testing no_std stuff. Check this tool: https://docs.rs/crate/cargo-no-dev-deps/0.1.0 [dev-dependencies] +ahash = { workspace = true } criterion = { workspace = true } lazy_static = { workspace = true } rand = { workspace = true, features = ["default"] } diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs index 39efd3af75..60b797bf4a 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs @@ -117,6 +117,20 @@ where IterOrOption::Opt(node) } } + + type IncluderItem = ::Item; + type Includer = IterOrOption< + Includer<'a, Children, Box>, Weight>, + &'a Self::Node, + >; + fn nodes_including(&'a self, ke: &'a keyexpr) -> Self::Includer { + if self.wildness.get() || ke.is_wild() { + Includer::new(&self.children, ke).into() + } else { + let node = self.node(ke); + IterOrOption::Opt(node) + } + } } impl< 'a, @@ -218,6 +232,19 @@ where IterOrOption::Opt(node) } } + type IncluderItemMut = ::Item; + type IncluderMut = IterOrOption< + IncluderMut<'a, Children, Box>, Weight>, + &'a mut Self::Node, + >; + fn nodes_including_mut(&'a mut self, ke: &'a keyexpr) -> Self::IncluderMut { + if self.wildness.get() || ke.is_wild() { + IncluderMut::new(&mut self.children, ke).into() + } else { + let node = self.node_mut(ke); + IterOrOption::Opt(node) + } + } fn prune_where bool>(&mut self, mut predicate: F) { let mut wild = false; diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/includer.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/includer.rs new file mode 100644 index 0000000000..a22d0804b1 --- /dev/null +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/includer.rs @@ -0,0 +1,337 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +use crate::keyexpr_tree::*; +use alloc::vec::Vec; + +struct StackFrame<'a, Children: IChildrenProvider, Node: UIKeyExprTreeNode, Weight> +where + Children::Assoc: IChildren + 'a, + >::Node: 'a, +{ + iterator: >::Iter<'a>, + start: usize, + end: usize, + _marker: core::marker::PhantomData, +} +pub struct Includer<'a, Children: IChildrenProvider, Node: UIKeyExprTreeNode, Weight> +where + Children::Assoc: IChildren + 'a, +{ + key: &'a keyexpr, + ke_indices: Vec, + iterators: Vec>, +} + +impl<'a, Children: IChildrenProvider, Node: UIKeyExprTreeNode, Weight> + Includer<'a, Children, Node, Weight> +where + Children::Assoc: IChildren + 'a, +{ + pub(crate) fn new(children: &'a Children::Assoc, key: &'a keyexpr) -> Self { + let mut ke_indices = Vec::with_capacity(32); + ke_indices.push(0); + let mut iterators = Vec::with_capacity(16); + iterators.push(StackFrame { + iterator: children.children(), + start: 0, + end: 1, + _marker: Default::default(), + }); + Self { + key, + ke_indices, + iterators, + } + } +} + +impl< + 'a, + Children: IChildrenProvider, + Node: UIKeyExprTreeNode + 'a, + Weight, + > Iterator for Includer<'a, Children, Node, Weight> +where + Children::Assoc: IChildren + 'a, +{ + type Item = &'a Node; + fn next(&mut self) -> Option { + loop { + let StackFrame { + iterator, + start, + end, + _marker, + } = self.iterators.last_mut()?; + match iterator.next() { + Some(node) => { + let mut node_matches = false; + let new_start = *end; + let mut new_end = *end; + macro_rules! push { + ($index: expr) => { + let index = $index; + if new_end == new_start + || self.ke_indices[new_start..new_end] + .iter() + .rev() + .all(|c| *c < index) + { + self.ke_indices.push(index); + new_end += 1; + } + }; + } + let chunk = node.chunk(); + unsafe { node.as_node().__keyexpr() }; + let chunk_is_super = chunk == "**"; + if chunk_is_super { + let mut latest_idx = usize::MAX; + 'outer: for i in *start..*end { + let mut kec_start = self.ke_indices[i]; + if kec_start == self.key.len() { + node_matches = true; + break; + } + if latest_idx <= kec_start && latest_idx != usize::MAX { + continue; + } + loop { + push!(kec_start); + latest_idx = kec_start; + let key = &self.key.as_bytes()[kec_start..]; + if key[0] == b'@' { + break; + } + match key.iter().position(|&c| c == b'/') { + Some(kec_end) => kec_start += kec_end + 1, + None => { + node_matches = true; + break 'outer; + } + } + } + } + } else { + for i in *start..*end { + let kec_start = self.ke_indices[i]; + if kec_start == self.key.len() { + break; + } + let key = &self.key.as_bytes()[kec_start..]; + unsafe { keyexpr::from_slice_unchecked(key) }; + match key.iter().position(|&c| c == b'/') { + Some(kec_end) => { + let subkey = + unsafe { keyexpr::from_slice_unchecked(&key[..kec_end]) }; + if chunk.includes(subkey) { + push!(kec_start + kec_end + 1); + } + } + None => { + let key = unsafe { keyexpr::from_slice_unchecked(key) }; + if chunk.includes(key) { + push!(self.key.len()); + node_matches = true; + } + } + } + } + } + if new_end > new_start { + let iterator = unsafe { node.as_node().__children() }.children(); + self.iterators.push(StackFrame { + iterator, + start: new_start, + end: new_end, + _marker: Default::default(), + }) + } + if node_matches { + return Some(node.as_node()); + } + } + None => { + if let Some(StackFrame { start, .. }) = self.iterators.pop() { + self.ke_indices.truncate(start); + } + } + } + } + } +} +struct StackFrameMut<'a, Children: IChildrenProvider, Node: UIKeyExprTreeNode, Weight> +where + Children::Assoc: IChildren + 'a, + >::Node: 'a, +{ + iterator: >::IterMut<'a>, + start: usize, + end: usize, + _marker: core::marker::PhantomData, +} + +pub struct IncluderMut< + 'a, + Children: IChildrenProvider, + Node: UIKeyExprTreeNode, + Weight, +> where + Children::Assoc: IChildren + 'a, +{ + key: &'a keyexpr, + ke_indices: Vec, + iterators: Vec>, +} + +impl<'a, Children: IChildrenProvider, Node: UIKeyExprTreeNode, Weight> + IncluderMut<'a, Children, Node, Weight> +where + Children::Assoc: IChildren + 'a, +{ + pub(crate) fn new(children: &'a mut Children::Assoc, key: &'a keyexpr) -> Self { + let mut ke_indices = Vec::with_capacity(32); + ke_indices.push(0); + let mut iterators = Vec::with_capacity(16); + iterators.push(StackFrameMut { + iterator: children.children_mut(), + start: 0, + end: 1, + _marker: Default::default(), + }); + Self { + key, + ke_indices, + iterators, + } + } +} + +impl< + 'a, + Children: IChildrenProvider, + Node: IKeyExprTreeNodeMut + 'a, + Weight, + > Iterator for IncluderMut<'a, Children, Node, Weight> +where + Children::Assoc: IChildren + 'a, +{ + type Item = &'a mut >::Node; + fn next(&mut self) -> Option { + loop { + let StackFrameMut { + iterator, + start, + end, + _marker, + } = self.iterators.last_mut()?; + match iterator.next() { + Some(node) => { + let mut node_matches = false; + let new_start = *end; + let mut new_end = *end; + macro_rules! push { + ($index: expr) => { + let index = $index; + if new_end == new_start + || self.ke_indices[new_start..new_end] + .iter() + .rev() + .all(|c| *c < index) + { + self.ke_indices.push(index); + new_end += 1; + } + }; + } + let chunk = node.chunk(); + let chunk_is_super = chunk == "**"; + if chunk_is_super { + let mut latest_idx = usize::MAX; + 'outer: for i in *start..*end { + let mut kec_start = self.ke_indices[i]; + if kec_start == self.key.len() { + node_matches = true; + break; + } + if latest_idx <= kec_start && latest_idx != usize::MAX { + continue; + } + loop { + push!(kec_start); + latest_idx = kec_start; + let key = &self.key.as_bytes()[kec_start..]; + if key[0] == b'@' { + break; + } + match key.iter().position(|&c| c == b'/') { + Some(kec_end) => kec_start += kec_end + 1, + None => { + node_matches = true; + break 'outer; + } + } + } + } + } else { + for i in *start..*end { + let kec_start = self.ke_indices[i]; + if kec_start == self.key.len() { + break; + } + let key = &self.key.as_bytes()[kec_start..]; + unsafe { keyexpr::from_slice_unchecked(key) }; + match key.iter().position(|&c| c == b'/') { + Some(kec_end) => { + let subkey = + unsafe { keyexpr::from_slice_unchecked(&key[..kec_end]) }; + if chunk.includes(subkey) { + push!(kec_start + kec_end + 1); + } + } + None => { + let key = unsafe { keyexpr::from_slice_unchecked(key) }; + if chunk.includes(key) { + push!(self.key.len()); + node_matches = true; + } + } + } + } + } + if new_end > new_start { + let iterator = unsafe { &mut *(node.as_node_mut() as *mut Node) } + .children_mut() + .children_mut(); + self.iterators.push(StackFrameMut { + iterator, + start: new_start, + end: new_end, + _marker: Default::default(), + }) + } + if node_matches { + return Some(node); + } + } + None => { + if let Some(StackFrameMut { start, .. }) = self.iterators.pop() { + self.ke_indices.truncate(start); + } + } + } + } + } +} diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/mod.rs index 3550ed0431..19545dbdd7 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/iters/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/iters/mod.rs @@ -18,3 +18,5 @@ mod intersection; pub use intersection::{Intersection, IntersectionMut}; mod inclusion; pub use inclusion::{Inclusion, InclusionMut}; +mod includer; +pub use includer::{Includer, IncluderMut}; diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs index 0a65b46588..cd640a53c5 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs @@ -124,8 +124,8 @@ fn test_keyset + Debug>(keys: &[K]) { } } let mut exclone = expected.clone(); - for node in tree.intersecting_nodes(dbg!(target)) { - let ke = dbg!(node.keyexpr()); + for node in tree.intersecting_nodes(target) { + let ke = node.keyexpr(); let weight = node.weight(); assert_eq!( expected @@ -198,6 +198,46 @@ fn test_keyset + Debug>(keys: &[K]) { target.deref(), &exclone ); + for (k, v) in &map { + if k.includes(target) { + assert!(expected.insert(k, v).is_none()); + } + } + exclone = expected.clone(); + for node in tree.nodes_including(dbg!(target)) { + let ke = node.keyexpr(); + let weight = node.weight(); + assert_eq!( + expected + .remove(dbg!(&ke)) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) + } + for node in tree.nodes_including_mut(target) { + let ke = node.keyexpr(); + let weight = node.weight(); + assert_eq!( + exclone + .remove(&ke) + .unwrap_or_else(|| panic!("Couldn't find {ke} in {target}'s expected output")) + .as_ref(), + weight + ) + } + assert!( + expected.is_empty(), + "MISSING INCLUDES FOR {}: {:?}", + target.deref(), + &expected + ); + assert!( + exclone.is_empty(), + "MISSING MUTABLE INCLUDES FOR {}: {:?}", + target.deref(), + &exclone + ); #[cfg(feature = "std")] { println!("{} OK", target.deref()); @@ -369,7 +409,7 @@ fn test_keyarctree>(keys: &[K]) { #[test] fn keyed_set_tree() { - let keys: [&keyexpr; 14] = [ + let keys: [&keyexpr; 16] = [ "a/b/**/c/**", "a/b/c", "a/b/c", @@ -384,6 +424,8 @@ fn keyed_set_tree() { "@c/**", "@c/a", "a/@c", + "b/a$*a/b/bb", + "**/b$*/bb", ] .map(into_ke); test_keyset(&keys); diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs index a1b0f3aead..1a77d4fea8 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs @@ -37,6 +37,12 @@ pub trait IKeyExprTree<'a, Weight> { /// /// Note that nodes without a `Weight` will also be yielded by the iterator. fn included_nodes(&'a self, key: &'a keyexpr) -> Self::Inclusion; + type IncluderItem; + type Includer: Iterator; + /// Iterates over all nodes of the tree whose KE is includes the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. + fn nodes_including(&'a self, key: &'a keyexpr) -> Self::Includer; } /// The basic mutable methods of all all KeTrees @@ -65,6 +71,12 @@ pub trait IKeyExprTreeMut<'a, Weight>: IKeyExprTree<'a, Weight> { /// /// Note that nodes without a `Weight` will also be yielded by the iterator. fn included_nodes_mut(&'a mut self, key: &'a keyexpr) -> Self::InclusionMut; + type IncluderItemMut; + type IncluderMut: Iterator; + /// Iterates over all nodes of the tree whose KE includes the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. + fn nodes_including_mut(&'a mut self, key: &'a keyexpr) -> Self::IncluderMut; /// Prunes node from the tree where the predicate returns `true`. /// /// Note that nodes that still have children will not be pruned. From 55479cf6f2136e59af5858e746afb3a402b5ec44 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Wed, 31 Jan 2024 18:10:01 +0100 Subject: [PATCH 3/5] add nodes_including iterator for shared ownership ketrees --- .../src/keyexpr_tree/arc_tree.rs | 51 +++++++++++++++++++ .../src/keyexpr_tree/traits/mod.rs | 6 +++ 2 files changed, 57 insertions(+) diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs index 4571ab050e..8e1f608041 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs @@ -329,6 +329,57 @@ where IterOrOption::Opt(self.node_mut(token, key).map(Into::into)) } } + + type IncluderItem = Self::Node; + type Includer = IterOrOption< + TokenPacker< + Includer< + 'a, + Children, + Arc, Wildness, Children, Token>, Token>>, + Weight, + >, + &'a Token, + >, + Self::IncluderItem, + >; + fn nodes_including(&'a self, token: &'a Token, key: &'a keyexpr) -> Self::Includer { + let inner = ketree_borrow(&self.inner, token); + if inner.wildness.get() || key.is_wild() { + IterOrOption::Iter(TokenPacker { + iter: Includer::new(&inner.children, key), + token, + }) + } else { + IterOrOption::Opt(self.node(token, key)) + } + } + type IncluderItemMut = Self::TreeIterItemMut; + type IncluderMut = IterOrOption< + TokenPacker< + Includer< + 'a, + Children, + Arc, Wildness, Children, Token>, Token>>, + Weight, + >, + &'a mut Token, + >, + Self::IncluderItemMut, + >; + fn nodes_including_mut(&'a self, token: &'a mut Token, key: &'a keyexpr) -> Self::IncluderMut { + let inner = ketree_borrow(&self.inner, token); + if inner.wildness.get() || key.is_wild() { + unsafe { + IterOrOption::Iter(TokenPacker { + iter: Includer::new(core::mem::transmute(&inner.children), key), + token, + }) + } + } else { + IterOrOption::Opt(self.node_mut(token, key).map(Into::into)) + } + } type PruneNode = KeArcTreeNode, Wildness, Children, Token>; fn prune_where bool>( diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs index 1a77d4fea8..c83072aed1 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs @@ -111,6 +111,12 @@ pub trait ITokenKeyExprTree<'a, Weight, Token> { type InclusionItemMut; type InclusionMut: Iterator; fn included_nodes_mut(&'a self, token: &'a mut Token, key: &'a keyexpr) -> Self::InclusionMut; + type IncluderItem; + type Includer: Iterator; + fn nodes_including(&'a self, token: &'a Token, key: &'a keyexpr) -> Self::Includer; + type IncluderItemMut; + type IncluderMut: Iterator; + fn nodes_including_mut(&'a self, token: &'a mut Token, key: &'a keyexpr) -> Self::IncluderMut; type PruneNode: IKeyExprTreeNodeMut; fn prune_where bool>(&self, token: &mut Token, predicate: F); } From 95ab9c768cd4420be57e7ebfc75b1bdea67b1642 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Thu, 1 Feb 2024 16:42:31 +0100 Subject: [PATCH 4/5] Improve documentation for KeTrees --- commons/zenoh-keyexpr/benches/keyexpr_tree.rs | 8 +- .../src/keyexpr_tree/arc_tree.rs | 11 +- .../src/keyexpr_tree/box_tree.rs | 21 +- commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs | 45 ++- .../zenoh-keyexpr/src/keyexpr_tree/test.rs | 2 +- .../src/keyexpr_tree/traits/mod.rs | 326 +++++++++++++----- .../src/replica/storage.rs | 10 +- 7 files changed, 308 insertions(+), 115 deletions(-) diff --git a/commons/zenoh-keyexpr/benches/keyexpr_tree.rs b/commons/zenoh-keyexpr/benches/keyexpr_tree.rs index 7c5ba60a5a..4047e3cf5c 100644 --- a/commons/zenoh-keyexpr/benches/keyexpr_tree.rs +++ b/commons/zenoh-keyexpr/benches/keyexpr_tree.rs @@ -46,11 +46,11 @@ fn main() { let mut intersections = Averager::default(); let results = Benchmarker::benchmark(|b| { let keys = KeySet::generate(total, wildness, no_double_stars); - let mut ketree: KeBoxTree<_> = KeBoxTree::new(); - let mut vectree: KeBoxTree<_, bool, VecSetProvider> = KeBoxTree::new(); - let mut hashtree: KeBoxTree<_, bool, HashMapProvider> = KeBoxTree::new(); + let mut ketree = KeBoxTree::new(); + let mut vectree: KeBoxTree<_, bool, VecSetProvider> = KeBoxTree::default(); + let mut hashtree: KeBoxTree<_, bool, HashMapProvider> = KeBoxTree::default(); let mut ahashtree: KeBoxTree<_, bool, HashMapProvider> = - KeBoxTree::new(); + KeBoxTree::default(); let (kearctree, mut token): (KeArcTree, _) = KeArcTree::new().unwrap(); let mut map = HashMap::new(); for key in keys.iter() { diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs index 8e1f608041..c2a7ff5375 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/arc_tree.rs @@ -56,7 +56,8 @@ fn ketree_borrow_mut<'a, T, Token: TokenTrait>( /// A shared KeTree. /// /// The tree and its nodes have shared ownership, while their mutability is managed through the `Token`. -/// The `(node, &token)` tuple implements [`core::ops::Deref`], while `(node, &mut token)` implements [`core::ops::DerefMut`]. +/// +/// Most of its methods are declared in the [`ITokenKeyExprTree`] trait. pub struct KeArcTree< Weight, Token: TokenTrait = DefaultToken, @@ -82,6 +83,14 @@ impl< > KeArcTree { /// Constructs the KeArcTree, returning it and its token, unless constructing the Token failed. + /// + /// # Type inference papercut + /// Despite some of `KeArcTree`'s generic parameters having default values, those are only taken into + /// account by the compiler when a type is named with some parameters omitted, and not when a type is + /// infered with the same parameters unconstrained. + /// + /// The simplest way to resolve this is to eventually assign to tree part of the return value + /// to a variable or field whose type is named `KeArcTree<_>` (the `Weight` parameter can generally be infered). pub fn new() -> Result<(Self, DefaultToken), ::ConstructionError> { let token = DefaultToken::new()?; Ok((Self::with_token(&token), token)) diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs index 60b797bf4a..5aa23e78ac 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/box_tree.rs @@ -27,6 +27,8 @@ use super::impls::KeyedSetProvider; use super::support::IterOrOption; /// A fully owned KeTree. +/// +/// Note that most of `KeBoxTree`'s methods are declared in the [`IKeyExprTree`] and [`IKeyExprTreeMut`] traits. #[repr(C)] pub struct KeBoxTree< Weight, @@ -37,17 +39,13 @@ pub struct KeBoxTree< wildness: Wildness, } -impl< - Weight, - Wildness: IWildness, - Children: IChildrenProvider>>, - > KeBoxTree +impl KeBoxTree +where + DefaultChildrenProvider: + IChildrenProvider>>, { pub fn new() -> Self { - KeBoxTree { - children: Default::default(), - wildness: Wildness::non_wild(), - } + Default::default() } } impl< @@ -57,7 +55,10 @@ impl< > Default for KeBoxTree { fn default() -> Self { - Self::new() + KeBoxTree { + children: Default::default(), + wildness: Wildness::non_wild(), + } } } diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs index cc17278e38..151c89fc0e 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs @@ -13,6 +13,49 @@ // //! KeTrees are specialized data structures to work with sets of values addressed by key expressions. +//! +//! Think of it like HashMaps that were specifically designed to allow iterating over key expressions related to a given query +//! in a fast and semantically correct way. +//! +//! # KeTrees +//! Key Expression Trees (KeTrees) are trees that split any KE inserted into them into its chunks. +//! +//! For example, if you take an empty KeTree, and insert the `(a/b, 0)`, `(a/b/c, 1)`, `(a/b/d, 2)`, `(a/e, 3)` and (a/@f, 4) pairs into it, +//! the tree will be as follows: +//! ```text +//! a --- e = 3 +//! |- b = 0 --- c = 1 +//! | |- d = 2 +//! |- @f = 4 +//! ``` +//! +//! Note that the `a` node exists, despite no value being assigned to it. If you iterate over all nodes, the iterator may yield +//! `(a, None), (a/e, Some(3)), (a/b, Some(0)), (a/b/c, Some(1)), (a/b/d, Some(2)), (a/@f, Some(4))`. +//! +//! # Ownership +//! KeTrees come in two flavours: +//! - [`KeBoxTree`] is the easier flavour. Much like a HashMap, it uniquely owns all of its nodes and data. +//! - [`KeArcTree`] allows the shared ownership of nodes, allowing you to store subsections of the tree elsewhere +//! without worrying about lifetimes. +//! +//! # Usage +//! KeTrees were designed to maximize code reuse. As such, their core properties are reflected through the [`IKeyExprTree`] and [`IKeyExprTreeMut`] traits, +//! and the [`IKeyExprTreeExt`] and [`IKeyExprTreeExtMut`] traits provide additional convenience methods. +//! +//! # Iterators +//! KeTrees provide iterators for the following operations: +//! - Iterating on all nodes ([`IKeyExprTree::tree_iter`]/[`IKeyExprTreeMut::tree_iter_mut`]) +//! - Iterating on key-value pairs in the KeTree ([`IKeyExprTreeExt::key_value_pairs`]) +//! - Iterating on nodes whose KE intersects with a queried KE ([`IKeyExprTree::intersecting_nodes`], [`IKeyExprTreeMut::intersecting_nodes_mut`]) +//! - Iterating on nodes whose KE are included by a queried KE ([`IKeyExprTree::included_nodes`], [`IKeyExprTreeMut::included_nodes_mut`]) +//! - Iterating on nodes whose KE includes a queried KE ([`IKeyExprTree::nodes_including`], [`IKeyExprTreeMut::nodes_including_mut`]) +//! +//! While the order of iteration is not guaranteed, a node will never be yielded before one of its parents if both are to appear. +//! For example, iterating on nodes that intersect with `**` in the previously defined tree is guaranteed to yield one of the following sequences: +//! - `(a, None), (a/e, Some(3)), (a/b, Some(0)), (a/b/c, Some(1)), (a/b/d, Some(2))` +//! - `(a, None), (a/e, Some(3)), (a/b, Some(0)), (a/b/d, Some(2)), (a/b/c, Some(1))` +//! - `(a, None), (a/b, Some(0)), (a/b/c, Some(1)), (a/b/d, Some(2)), (a/e, Some(3))` +//! - `(a, None), (a/b, Some(0)), (a/b/d, Some(2)), (a/b/c, Some(1)), (a/e, Some(3))` use crate::{keyexpr, OwnedKeyExpr}; /// Allows importing all of the KeTree traits at once. @@ -21,7 +64,7 @@ pub use traits::*; /// An implementation of a KeTree with shared-ownership of nodes, using [`token_cell`] to allow safe access to the tree's data. /// -/// This implementation allows sharing references to members of the KeTree. +/// This implementation allows sharing ownership of members of the KeTree. pub mod arc_tree; pub use arc_tree::{DefaultToken, KeArcTree}; /// An implementation of a KeTree that owns all of its nodes. diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs index cd640a53c5..fc2372a67b 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/test.rs @@ -246,7 +246,7 @@ fn test_keyset + Debug>(keys: &[K]) { } fn test_keyset_vec>(keys: &[K]) { - let mut tree = KeBoxTree::new(); + let mut tree = KeBoxTree::default(); let mut map = HashMap::new(); for (v, k) in keys.iter().map(|k| k.deref()).enumerate() { insert_vecset(&mut tree, &mut map, k, v); diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs index c83072aed1..ea335ececb 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs @@ -16,63 +16,168 @@ use crate::{keyexpr, OwnedKeyExpr}; use alloc::boxed::Box; pub mod default_impls; -/// The basic immutable methods of all all KeTrees +/// The basic immutable methods of all KeTrees. pub trait IKeyExprTree<'a, Weight> { + /// The type of a given node in the KeTree. + /// + /// The methods of nodes are exposed in the [`IKeyExprTreeNode`] and [`IKeyExprTreeNodeMut`] traits type Node: IKeyExprTreeNodeMut; - /// Accesses the node at `key` if it exists, treating KEs as if they were litteral keys. + + /// Accesses the node at `key` if it exists, treating KEs as if they were completely verbatim keys. + /// + /// Returns `None` if `key` is not present in the KeTree. fn node(&'a self, key: &keyexpr) -> Option<&Self::Node>; + + /// Returns a reference to the weight of the node at `key` if it exists. + fn weight_at(&'a self, key: &keyexpr) -> Option<&'a Weight> { + self.node(key) + .and_then(>::weight) + } + type TreeIterItem; type TreeIter: Iterator; - /// Iterates over the whole tree, including nodes with no weight. + + /// Iterates over the whole tree, including nodes with no weight. + /// + /// [`IKeyExprTree::key_value_pairs`] provides an iterator over all key-value pairs in the tree. fn tree_iter(&'a self) -> Self::TreeIter; + + /// Iterates through weighted nodes, yielding their KE and Weight. + #[allow(clippy::type_complexity)] + fn key_value_pairs( + &'a self, + ) -> core::iter::FilterMap< + Self::TreeIter, + fn(Self::TreeIterItem) -> Option<(OwnedKeyExpr, &'a Weight)>, + > + where + Self::TreeIterItem: AsNode>, + { + self.tree_iter().filter_map(|node| { + unsafe { core::mem::transmute::<_, Option<&Weight>>(node.as_node().weight()) } + .map(|w| (node.as_node().keyexpr(), w)) + }) + } + type IntersectionItem; type Intersection: Iterator; + /// Iterates over all nodes of the tree whose KE intersects with the given `key`. /// /// Note that nodes without a `Weight` will also be yielded by the iterator. + /// + /// You can obtain an iterator over key-value pairs using `iter.filter_map(|node| node.weight.map(|w| (node.keyexpr(), w)))`, + /// keep in mind that the full keyexpr of nodes is not stored in them by default, but computed using the tree: + /// if you need to get a node's key often, inserting its keyexpr in the `Weight` could be a good idea. fn intersecting_nodes(&'a self, key: &'a keyexpr) -> Self::Intersection; + + /// Returns an iterator over the KEs contained in the tree that intersect with `key` + fn intersecting_keys( + &'a self, + key: &'a keyexpr, + ) -> Keys + where + Self::IntersectionItem: AsNode, + Self::Node: IKeyExprTreeNode, + { + self.intersecting_nodes(key) + .filter_map(filter_map_weighted_node_to_key) + } + type InclusionItem; type Inclusion: Iterator; + /// Iterates over all nodes of the tree whose KE is included by the given `key`. /// /// Note that nodes without a `Weight` will also be yielded by the iterator. + /// + /// You can obtain an iterator over key-value pairs using `iter.filter_map(|node| node.weight.map(|w| (node.keyexpr(), w)))`, + /// keep in mind that the full keyexpr of nodes is not stored in them by default, but computed using the tree: + /// if you need to get a node's key often, inserting its keyexpr in the `Weight` could be a good idea. fn included_nodes(&'a self, key: &'a keyexpr) -> Self::Inclusion; + + /// Returns an iterator over the KEs contained in the tree that are included by `key` + fn included_keys(&'a self, key: &'a keyexpr) -> Keys + where + Self::InclusionItem: AsNode, + Self::Node: IKeyExprTreeNode, + { + self.included_nodes(key) + .filter_map(filter_map_weighted_node_to_key) + } + type IncluderItem; type Includer: Iterator; - /// Iterates over all nodes of the tree whose KE is includes the given `key`. + + /// Iterates over all nodes of the tree whose KE includes the given `key`. /// /// Note that nodes without a `Weight` will also be yielded by the iterator. + /// + /// You can obtain an iterator over key-value pairs using `iter.filter_map(|node| node.weight.map(|w| (node.keyexpr(), w)))`, + /// keep in mind that the full keyexpr of nodes is not stored in them by default, but computed using the tree: + /// if you need to get a node's key often, inserting its keyexpr in the `Weight` could be a good idea. fn nodes_including(&'a self, key: &'a keyexpr) -> Self::Includer; + + /// Returns an iterator over the KEs contained in the tree that include `key` + fn keys_including(&'a self, key: &'a keyexpr) -> Keys + where + Self::IncluderItem: AsNode, + Self::Node: IKeyExprTreeNode, + { + self.nodes_including(key) + .filter_map(filter_map_weighted_node_to_key) + } } -/// The basic mutable methods of all all KeTrees +/// The basic mutable methods of all KeTrees. pub trait IKeyExprTreeMut<'a, Weight>: IKeyExprTree<'a, Weight> { - /// Mutably accesses the node at `key` if it exists, treating KEs as if they were litteral keys. - fn node_mut<'b>(&'b mut self, key: &keyexpr) -> Option<&'b mut Self::Node>; - /// Clears the weight of the node at `key`. + /// Mutably accesses the node at `key` if it exists, treating KEs as if they were completely verbatim keys. /// - /// To actually destroy nodes, [`IKeyExprTreeMut::prune_where`] or [`IKeyExprTreeExtMut::prune`] must be called. - fn remove(&mut self, key: &keyexpr) -> Option; + /// Returns `None` if `key` is not present. Use [`IKeyExprTreeMut::node_mut_or_create`] if you wish to construct the node if it doesn't exist. + fn node_mut<'b>(&'b mut self, key: &keyexpr) -> Option<&'b mut Self::Node>; + + /// Returns a mutable reference to the weight of the node at `key`. + fn weight_at_mut(&'a mut self, key: &keyexpr) -> Option<&'a mut Weight> { + self.node_mut(key) + .and_then(>::weight_mut) + } + /// Mutably accesses the node at `key`, creating it if necessary. fn node_mut_or_create<'b>(&'b mut self, key: &keyexpr) -> &'b mut Self::Node; + + /// Inserts a weight at `key`, returning the previous weight if it existed. + fn insert(&mut self, key: &keyexpr, weight: Weight) -> Option { + self.node_mut_or_create(key).insert_weight(weight) + } + + /// Clears the weight of the node at `key`, but doesn't actually destroy the node. + /// + /// To actually destroy nodes, [`IKeyExprTreeMut::prune_where`] or [`IKeyExprTreeMut::prune`] must be called. + fn remove(&mut self, key: &keyexpr) -> Option; + type TreeIterItemMut; type TreeIterMut: Iterator; - /// Iterates over the whole tree, including nodes with no weight. + + /// Iterates over the whole tree, including nodes with no weight. fn tree_iter_mut(&'a mut self) -> Self::TreeIterMut; + type IntersectionItemMut; type IntersectionMut: Iterator; + /// Iterates over all nodes of the tree whose KE intersects with the given `key`. /// /// Note that nodes without a `Weight` will also be yielded by the iterator. fn intersecting_nodes_mut(&'a mut self, key: &'a keyexpr) -> Self::IntersectionMut; type InclusionItemMut; type InclusionMut: Iterator; + /// Iterates over all nodes of the tree whose KE is included by the given `key`. /// /// Note that nodes without a `Weight` will also be yielded by the iterator. fn included_nodes_mut(&'a mut self, key: &'a keyexpr) -> Self::InclusionMut; type IncluderItemMut; type IncluderMut: Iterator; + /// Iterates over all nodes of the tree whose KE includes the given `key`. /// /// Note that nodes without a `Weight` will also be yielded by the iterator. @@ -81,44 +186,161 @@ pub trait IKeyExprTreeMut<'a, Weight>: IKeyExprTree<'a, Weight> { /// /// Note that nodes that still have children will not be pruned. fn prune_where bool>(&mut self, predicate: F); + + /// Prunes empty nodes from the tree, unless they have at least one non-empty descendent. + fn prune(&mut self) { + self.prune_where(|node| node.weight().is_none()) + } } /// The basic operations of a KeTree when a Token is necessary to acess data. pub trait ITokenKeyExprTree<'a, Weight, Token> { + /// An immutable guard to a node of the tree. type Node: IKeyExprTreeNode; + // A mutable guard to a node of the tree. type NodeMut: IKeyExprTreeNodeMut; + + /// Accesses the node at `key` if it exists, treating KEs as if they were completely verbatim keys. + /// + /// Returns `None` if `key` is not present in the KeTree. fn node(&'a self, token: &'a Token, key: &keyexpr) -> Option; + + /// Mutably accesses the node at `key` if it exists, treating KEs as if they were completely verbatim keys. + /// + /// Returns `None` if `key` is not present. Use [`IKeyExprTreeMut::node_mut_or_create`] if you wish to construct the node if it doesn't exist. fn node_mut(&'a self, token: &'a mut Token, key: &keyexpr) -> Option; + + /// Mutably accesses the node at `key`, creating it if necessary. fn node_or_create(&'a self, token: &'a mut Token, key: &keyexpr) -> Self::NodeMut; + + /// Inserts a weight at `key`, returning the previous weight if it existed. + fn insert(&'a self, token: &'a mut Token, at: &keyexpr, weight: Weight) -> Option { + self.node_or_create(token, at).insert_weight(weight) + } + + /// Clears the weight of the node at `key`, but doesn't actually destroy the node. + /// + /// To actually destroy nodes, [`ITokenKeyExprTree::prune_where`] or [`ITokenKeyExprTree::prune`] must be called. + fn remove(&'a mut self, token: &'a mut Token, key: &keyexpr) -> Option { + self.node_mut(token, key) + .and_then(|mut node| node.take_weight()) + } + type TreeIterItem; type TreeIter: Iterator; + + /// Iterates over the whole tree, including nodes with no weight. fn tree_iter(&'a self, token: &'a Token) -> Self::TreeIter; + type TreeIterItemMut; type TreeIterMut: Iterator; + + /// Iterates over the whole tree, including nodes with no weight. fn tree_iter_mut(&'a self, token: &'a mut Token) -> Self::TreeIterMut; + type IntersectionItem; type Intersection: Iterator; + + /// Iterates over all nodes of the tree whose KE intersects with the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. + /// + /// You can obtain an iterator over key-value pairs using `iter.filter_map(|node| node.weight.map(|w| (node.keyexpr(), w)))`, + /// keep in mind that the full keyexpr of nodes is not stored in them by default, but computed using the tree: + /// if you need to get a node's key often, inserting its keyexpr in the `Weight` could be a good idea. fn intersecting_nodes(&'a self, token: &'a Token, key: &'a keyexpr) -> Self::Intersection; + + /// Returns an iterator over the KEs contained in the tree that intersect with `key` + fn intersecting_keys( + &'a self, + token: &'a Token, + key: &'a keyexpr, + ) -> Keys + where + Self::IntersectionItem: AsNode, + Self::Node: IKeyExprTreeNode, + { + self.intersecting_nodes(token, key) + .filter_map(filter_map_weighted_node_to_key) + } + type IntersectionItemMut; type IntersectionMut: Iterator; + + /// Iterates over all nodes of the tree whose KE intersects with the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. fn intersecting_nodes_mut( &'a self, token: &'a mut Token, key: &'a keyexpr, ) -> Self::IntersectionMut; + type InclusionItem; type Inclusion: Iterator; + + /// Iterates over all nodes of the tree whose KE is included by the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. fn included_nodes(&'a self, token: &'a Token, key: &'a keyexpr) -> Self::Inclusion; + + /// Returns an iterator over the KEs contained in the tree that are included by `key` + fn included_keys( + &'a self, + token: &'a Token, + key: &'a keyexpr, + ) -> Keys + where + Self::InclusionItem: AsNode, + Self::Node: IKeyExprTreeNode, + { + self.included_nodes(token, key) + .filter_map(filter_map_weighted_node_to_key) + } + type InclusionItemMut; type InclusionMut: Iterator; + + /// Iterates over all nodes of the tree whose KE is included by the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. fn included_nodes_mut(&'a self, token: &'a mut Token, key: &'a keyexpr) -> Self::InclusionMut; + type IncluderItem; type Includer: Iterator; + + /// Iterates over all nodes of the tree whose KE includes the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. fn nodes_including(&'a self, token: &'a Token, key: &'a keyexpr) -> Self::Includer; + + /// Returns an iterator over the KEs contained in the tree that include `key` + fn keys_including( + &'a self, + token: &'a Token, + key: &'a keyexpr, + ) -> Keys + where + Self::IncluderItem: AsNode, + Self::Node: IKeyExprTreeNode, + { + self.nodes_including(token, key) + .filter_map(filter_map_weighted_node_to_key) + } + type IncluderItemMut; type IncluderMut: Iterator; + + /// Iterates over all nodes of the tree whose KE includes the given `key`. + /// + /// Note that nodes without a `Weight` will also be yielded by the iterator. fn nodes_including_mut(&'a self, token: &'a mut Token, key: &'a keyexpr) -> Self::IncluderMut; + type PruneNode: IKeyExprTreeNodeMut; + fn prune_where bool>(&self, token: &mut Token, predicate: F); + fn prune(&self, token: &mut Token) { + self.prune_where(token, |node| node.weight().is_none()) + } } pub trait IKeyExprTreeNode: UIKeyExprTreeNode { @@ -251,89 +473,9 @@ pub trait AsNodeMut: AsNode { } type Keys = core::iter::FilterMap Option>; -fn filter_map_weighted_node_to_key, I: AsNode, W>( +fn filter_map_weighted_node_to_key, I: AsNode, W>( item: I, ) -> Option { let node: &N = item.as_node(); node.weight().is_some().then(|| node.keyexpr()) } - -/// Extension methods for KeTrees -pub trait IKeyExprTreeExt<'a, Weight>: IKeyExprTree<'a, Weight> { - /// Returns a reference to the weight of the node at `key` - fn weight_at(&'a self, key: &keyexpr) -> Option<&'a Weight> { - self.node(key) - .and_then(>::weight) - } - /// Returns an iterator over the KEs contained in the tree that intersect with `key` - fn intersecting_keys( - &'a self, - key: &'a keyexpr, - ) -> Keys - where - Self::IntersectionItem: AsNode, - Self::Node: IKeyExprTreeNode, - { - self.intersecting_nodes(key) - .filter_map(filter_map_weighted_node_to_key) - } - /// Returns an iterator over the KEs contained in the tree that are included by `key` - fn included_keys(&'a self, key: &'a keyexpr) -> Keys - where - Self::InclusionItem: AsNode, - Self::Node: IKeyExprTreeNode, - { - self.included_nodes(key) - .filter_map(filter_map_weighted_node_to_key) - } - /// Iterates through weighted nodes, yielding their KE and Weight. - #[allow(clippy::type_complexity)] - fn key_value_pairs( - &'a self, - ) -> core::iter::FilterMap< - Self::TreeIter, - fn(Self::TreeIterItem) -> Option<(OwnedKeyExpr, &'a Weight)>, - > - where - Self::TreeIterItem: AsNode>, - { - self.tree_iter().filter_map(|node| { - unsafe { core::mem::transmute::<_, Option<&Weight>>(node.as_node().weight()) } - .map(|w| (node.as_node().keyexpr(), w)) - }) - } -} - -/// Extension methods for mutable KeTrees. -pub trait IKeyExprTreeExtMut<'a, Weight>: IKeyExprTreeMut<'a, Weight> { - /// Returns a mutable reference to the weight of the node at `key`. - fn weight_at_mut(&'a mut self, key: &keyexpr) -> Option<&'a mut Weight> { - self.node_mut(key) - .and_then(>::weight_mut) - } - /// Inserts a weight at `key`, returning the previous weight if it existed. - fn insert(&mut self, key: &keyexpr, weight: Weight) -> Option { - self.node_mut_or_create(key).insert_weight(weight) - } - /// Prunes empty nodes from the tree, unless they have at least one non-empty descendent. - fn prune(&mut self) { - self.prune_where(|node| node.weight().is_none()) - } -} - -impl<'a, Weight, T: IKeyExprTree<'a, Weight>> IKeyExprTreeExt<'a, Weight> for T {} -impl<'a, Weight, T: IKeyExprTreeMut<'a, Weight>> IKeyExprTreeExtMut<'a, Weight> for T {} - -pub trait ITokenKeyExprTreeExt<'a, Weight, Token>: ITokenKeyExprTree<'a, Weight, Token> { - fn insert(&'a self, token: &'a mut Token, at: &keyexpr, weight: Weight) -> Option { - self.node_or_create(token, at).insert_weight(weight) - } - - fn prune(&self, token: &mut Token) { - self.prune_where(token, |node| node.weight().is_none()) - } -} -impl<'a, Weight, Token, T: ITokenKeyExprTree<'a, Weight, Token>> - ITokenKeyExprTreeExt<'a, Weight, Token> for T -{ -} diff --git a/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs b/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs index 84f592d899..115ed1e8d9 100644 --- a/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs +++ b/plugins/zenoh-plugin-storage-manager/src/replica/storage.rs @@ -30,10 +30,8 @@ use zenoh_backend_traits::config::{GarbageCollectionConfig, StorageConfig}; use zenoh_backend_traits::{Capability, History, Persistence, StorageInsertionResult, StoredData}; use zenoh_keyexpr::key_expr::OwnedKeyExpr; use zenoh_keyexpr::keyexpr_tree::impls::KeyedSetProvider; -use zenoh_keyexpr::keyexpr_tree::IKeyExprTreeMut; -use zenoh_keyexpr::keyexpr_tree::{ - support::NonWild, support::UnknownWildness, IKeyExprTreeExt, IKeyExprTreeExtMut, KeBoxTree, -}; +use zenoh_keyexpr::keyexpr_tree::{support::NonWild, support::UnknownWildness, KeBoxTree}; +use zenoh_keyexpr::keyexpr_tree::{IKeyExprTree, IKeyExprTreeMut}; use zenoh_result::bail; use zenoh_util::{zenoh_home, Timed, TimedEvent, Timer}; @@ -85,8 +83,8 @@ impl StorageService { strip_prefix: config.strip_prefix, storage: Mutex::new(store_intercept.storage), capability: store_intercept.capability, - tombstones: Arc::new(RwLock::new(KeBoxTree::new())), - wildcard_updates: Arc::new(RwLock::new(KeBoxTree::new())), + tombstones: Arc::new(RwLock::new(KeBoxTree::default())), + wildcard_updates: Arc::new(RwLock::new(KeBoxTree::default())), in_interceptor: store_intercept.in_interceptor, out_interceptor: store_intercept.out_interceptor, replication, From ddb0a807b38cb93eac07da78e863484e29861ef3 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Mon, 5 Feb 2024 11:08:12 +0100 Subject: [PATCH 5/5] Improve documentation for KeTree nodes --- commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs | 6 ++-- .../src/keyexpr_tree/traits/mod.rs | 33 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs index 151c89fc0e..e2833a912f 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/mod.rs @@ -39,8 +39,10 @@ //! without worrying about lifetimes. //! //! # Usage -//! KeTrees were designed to maximize code reuse. As such, their core properties are reflected through the [`IKeyExprTree`] and [`IKeyExprTreeMut`] traits, -//! and the [`IKeyExprTreeExt`] and [`IKeyExprTreeExtMut`] traits provide additional convenience methods. +//! KeTrees were designed to maximize code reuse. As such, their core properties are reflected through the [`IKeyExprTree`] and [`IKeyExprTreeMut`] traits. +//! +//! KeTrees are made up of node, where nodes may or may not have a value (called `weight`) associated with them. To access these weighs, as well as other +//! properties of a node, you can go throught the [`IKeyExprTreeNode`] and [`IKeyExprTreeNodeMut`] traits. //! //! # Iterators //! KeTrees provide iterators for the following operations: diff --git a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs index ea335ececb..dd06cf14b8 100644 --- a/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs +++ b/commons/zenoh-keyexpr/src/keyexpr_tree/traits/mod.rs @@ -343,16 +343,33 @@ pub trait ITokenKeyExprTree<'a, Weight, Token> { } } +/// The non-mutating methods of a KeTree node. pub trait IKeyExprTreeNode: UIKeyExprTreeNode { + /// Access the parent node if it exists (the node isn't the first chunk of a key-expression). fn parent(&self) -> Option<&Self::Parent> { unsafe { self.__parent() } } + /// Compute this node's full key expression. + /// + /// Note that KeTrees don't normally store each node's full key expression. + /// If you need to repeatedly access a node's full key expression, it is suggested + /// to store that key expression as part of the node's `Weight` (it's optional value). fn keyexpr(&self) -> OwnedKeyExpr { unsafe { self.__keyexpr() } } + + /// Access the node's weight (or value). + /// + /// Weights can be assigned to a node through many of [`IKeyExprTreeNodeMut`]'s methods, as well as through [`IKeyExprTreeMut::insert`]. + /// + /// Nodes may not have a value in any of the following cases: + /// - The node is a parent to other nodes, but was never assigned a weight itself (or that weight has been removed). + /// - The node is a leaf of the KeTree whose value was [`IKeyExprTreeMut::remove`]d, but [`IKeyExprTreeMut::prune`] hasn't been called yet. fn weight(&self) -> Option<&Weight> { unsafe { self.__weight() } } + + /// Access a node's children. fn children(&self) -> &Self::Children { unsafe { self.__children() } } @@ -369,17 +386,33 @@ pub trait UIKeyExprTreeNode { unsafe fn __children(&self) -> &Self::Children; } +/// The mutating methods of a KeTree node. pub trait IKeyExprTreeNodeMut: IKeyExprTreeNode { + /// Mutably access the parent node if it exists (the node isn't the first chunk of a key-expression). fn parent_mut(&mut self) -> Option<&mut Self::Parent>; + + /// Mutably access the node's weight. fn weight_mut(&mut self) -> Option<&mut Weight>; + + /// Remove the node's weight. fn take_weight(&mut self) -> Option; + + /// Assign a weight to the node, returning the previous weight if the node had one. fn insert_weight(&mut self, weight: Weight) -> Option; + + /// Mutably access the node's children. fn children_mut(&mut self) -> &mut Self::Children; } + +/// Nodes from a token-locked tree need a token to obtain read/write permissions. +/// +/// This trait allows tokenizing a node, allowing to use [`IKeyExprTreeNode`] and [`IKeyExprTreeNodeMut`]'s methods on it. pub trait ITokenKeyExprTreeNode<'a, Weight, Token> { type Tokenized: IKeyExprTreeNode; + /// Wrap the node with the an immutable reference to the token to allow immutable access to its contents. fn tokenize(&'a self, token: &'a Token) -> Self::Tokenized; type TokenizedMut: IKeyExprTreeNodeMut; + /// Wrap the node with a mutable reference to the token to allow mutable access to its contents fn tokenize_mut(&'a self, token: &'a mut Token) -> Self::TokenizedMut; } impl<'a, T: 'a, Weight, Token: 'a> ITokenKeyExprTreeNode<'a, Weight, Token> for T