orx_tree/
memory.rs

1use crate::{Tree, TreeVariant, pinned_storage::PinnedStorage};
2use orx_selfref_col::{MemoryReclaimNever, MemoryReclaimOnThreshold, MemoryReclaimer, Utilization};
3
4/// Trees use a pinned vector ([`PinnedVec`]) as its underlying storage.
5/// Deletions from the tree are lazy in the sense that the nodes are closed; not removed from the vector.
6/// Therefore, deletions leave a gap in the underlying collection.
7/// How these nodes will be reclaimed is determined by the `MemoryPolicy`.
8///
9/// This is important because during memory reclaim, nodes of the collection are reorganized which
10/// invalidates the node indices which are obtained prior to this operation.
11/// Node indices, or [`NodeIdx`], are analogous to usize indices of a slice and allow for safe direct
12/// constant time access to the node it is created for.
13///
14/// There are three available policies:
15///
16/// * [`Auto`]
17///   * The default policy.
18///   * It automatically reclaims memory whenever the utilization falls below 75%.
19///   * Automatic triggers of memory reclaims might lead to implicit invalidation of node indices due to [`ReorganizedCollection`].
20///   * It is important to note that, even when using Auto policy, tree growth will never trigger memory reclaim.
21///     Only node removing mutations such as [`prune`] can trigger node reorganization.
22/// * [`AutoWithThreshold`]
23///   * This is a generalization of the Auto policy; in particular, Auto is equivalent to `AutoWithThreshold<2>`.
24///   * Its constant parameter `D` defines the utilization threshold to trigger the memory reclaim operation.
25///   * Specifically, memory of closed nodes will be reclaimed whenever the ratio of closed nodes to all nodes exceeds one over `2^D`.
26///     The greater the `D`, the greater the utilization threshold.
27/// * [`Lazy`]
28///   * It never reclaims memory.
29///   * It never leads to implicit invalidation of node indices.
30///     In other words, a node index can only be invalidated if we prune that node from the tree ([`RemovedNode`]).
31///
32/// An ideal use pattern can conveniently be achieved by using auto and lazy policies together,
33/// using the free memory policy transformation methods.
34/// Such a use case is demonstrated in the example below.
35///
36/// [`PinnedVec`]: orx_pinned_vec::PinnedVec
37/// [`NodeIdx`]: crate::NodeIdx
38/// [`prune`]: crate::NodeMut::prune
39/// [`ReorganizedCollection`]: crate::NodeIdxError::ReorganizedCollection
40/// [`RemovedNode`]: crate::NodeIdxError::RemovedNode
41///
42/// # Examples
43///
44/// ## Auto Memory Claim
45///
46/// Since Auto is the default memory policy, we do not need to include it in the tree type signature.
47/// The following example demonstrates how this policy impacts the validity of node indices.
48///
49/// The follow up example will demonstrate how to switch between the [`Lazy`] and [`Auto`] policies
50/// to make best use of these policies and use both node indices and memory efficiently.
51///
52/// ```
53/// use orx_tree::*;
54///
55/// // # 1 - BUILD UP
56///
57/// //      1
58/// //     ╱ ╲
59/// //    ╱   ╲
60/// //   2     3
61/// //  ╱ ╲   ╱ ╲
62/// // 4   5 6   7
63/// // |     |  ╱ ╲
64/// // 8     9 10  11
65///
66/// fn bfs_values(tree: &DynTree<i32>) -> Vec<i32> {
67///     tree.root().walk::<Bfs>().copied().collect()
68/// }
69///
70/// // # 1. GROW
71///
72/// // equivalently => DynTree::<i32, Auto>::new(1)
73/// let mut tree = DynTree::new(1);
74///
75/// let [id2, id3] = tree.root_mut().push_children([2, 3]);
76/// let [id4, _] = tree.node_mut(&id2).push_children([4, 5]);
77/// let [id8] = tree.node_mut(&id4).push_children([8]);
78/// let [id6, id7] = tree.node_mut(&id3).push_children([6, 7]);
79/// tree.node_mut(&id6).push_child(9);
80/// tree.node_mut(&id7).push_children([10, 11]);
81///
82/// assert_eq!(bfs_values(&tree), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
83///
84/// // all id's above are valid => the tree only grew
85/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
86/// assert!(tree.get_node(&id4).is_some()); // get_node => Some(Node)
87/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
88/// let _node7 = tree.node(&id7); // no panic
89///
90/// // # 2 - SHRINK BUT WITHOUT A MEMORY RECLAIM
91///
92/// // let's close two nodes (nodes 4 & 8)
93/// // this is not enough to trigger a memory reclaim
94/// tree.node_mut(&id4).prune();
95/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 7, 9, 10, 11]);
96///
97/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
98/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
99/// let node7 = tree.node(&id7); // no panic
100///
101/// // what about id4 & id8 => invalidated due to RemovedNode
102/// assert!(!tree.is_node_idx_valid(&id4));
103/// assert!(tree.get_node(&id4).is_none());
104/// assert_eq!(tree.try_node(&id4), Err(NodeIdxError::RemovedNode));
105/// // let node4 = id4.node(&tree); // panics!!!
106///
107/// // # 3 - SHRINK TRIGGERING MEMORY RECLAIM
108///
109/// // let's close more nodes (7, 10, 11) to trigger the memory reclaim
110/// tree.node_mut(&id7).prune();
111/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 9]);
112///
113/// // even node 2 is still on the tree;
114/// // its idx is invalidated => ReorganizedCollection
115/// assert!(!tree.is_node_idx_valid(&id2));
116/// assert_eq!(
117///     tree.try_node(&id2),
118///     Err(NodeIdxError::ReorganizedCollection)
119/// );
120///
121/// // all indices obtained prior to reorganization are now invalid
122/// // we can restore the valid indices again
123///
124/// let id2 = tree.root().get_child(0).unwrap().idx();
125/// assert!(tree.is_node_idx_valid(&id2));
126/// assert!(tree.try_node(&id2).is_ok());
127/// let n2 = tree.node(&id2);
128/// assert_eq!(n2.data(), &2);
129/// ```
130///
131/// ## Lazy Memory Claim: Preventing Invalid Indices
132///
133/// Now assume that we want to make sure that the indices we cached during growth
134/// are valid during the stages #2 and #3.
135/// We can achieve this by switching to Lazy policy and only after stage #3,
136/// when we are done using the indices, we can switch back to Auto policy.
137/// Note that these transformations of the memory policies are free.
138///
139/// ```
140/// use orx_tree::*;
141///
142/// // # 1 - BUILD UP
143///
144/// //      1
145/// //     ╱ ╲
146/// //    ╱   ╲
147/// //   2     3
148/// //  ╱ ╲   ╱ ╲
149/// // 4   5 6   7
150/// // |     |  ╱ ╲
151/// // 8     9 10  11
152///
153/// fn bfs_values<M: MemoryPolicy>(tree: &DynTree<i32, M>) -> Vec<i32> {
154///     tree.root().walk::<Bfs>().copied().collect()
155/// }
156///
157/// // # 1. GROW
158///
159/// // or just => DynTree::<i32, Lazy>::new(1);
160/// let mut tree = DynTree::new(1).into_lazy_reclaim();
161///
162/// let [id2, id3] = tree.root_mut().push_children([2, 3]);
163/// let [id4, _] = tree.node_mut(&id2).push_children([4, 5]);
164/// let [id8] = tree.node_mut(&id4).push_children([8]);
165/// let [id6, id7] = tree.node_mut(&id3).push_children([6, 7]);
166/// tree.node_mut(&id6).push_child(9);
167/// tree.node_mut(&id7).push_children([10, 11]);
168///
169/// assert_eq!(bfs_values(&tree), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
170///
171/// // all id's above are valid => we are in Lazy mode & the tree only grew
172/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
173/// assert!(tree.get_node(&id4).is_some()); // get_node => Some(Node)
174/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
175/// let _node7 = tree.node(&id7); // no panic!
176///
177/// // # 2 - SHRINK, NO MEMORY RECLAIM
178///
179/// // let's close two nodes (nodes 4 & 8)
180/// tree.node_mut(&id4).prune();
181/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 7, 9, 10, 11]);
182///
183/// assert!(tree.is_node_idx_valid(&id2)); // is_valid_for => true
184/// assert!(tree.try_node(&id6).is_ok()); // try_get_node => Ok(Node)
185/// let node7 = tree.node(&id7); // no panic
186///
187/// // only id4 & id8 are affected (explicit) => invalidated due to RemovedNode
188/// assert!(!tree.is_node_idx_valid(&id4));
189/// assert!(tree.get_node(&id4).is_none());
190/// assert_eq!(tree.try_node(&id4), Err(NodeIdxError::RemovedNode));
191/// // let node4 = id4.node(&tree); // panics!
192///
193/// // # 3 - SHRINK HEAVILY, STILL NO MEMORY RECLAIM
194///
195/// // let's close more nodes (7, 10, 11)
196/// // this would've triggered memory reclaim in Auto policy, but not in Lazy policy
197/// tree.node_mut(&id7).prune();
198/// assert_eq!(bfs_values(&tree), [1, 2, 3, 5, 6, 9]);
199///
200/// // all indices are still valid ✓
201/// assert!(tree.is_node_idx_valid(&id2));
202/// assert!(tree.try_node(&id2).is_ok());
203/// let n2 = tree.node(&id2);
204/// assert_eq!(n2.data(), &2);
205///
206/// // # 4 - END OF HEAVY MUTATIONS, RECLAIM THE MEMORY
207///
208/// // since utilization was below 75% (6 active nodes / 11)
209/// // memory is reclaimed immediately once switch to Auto.
210/// // now, all prior indices are invalid
211/// let tree: DynTree<i32, Auto> = tree.into_auto_reclaim();
212/// assert!(!tree.is_node_idx_valid(&id2));
213/// assert!(tree.get_node(&id3).is_none());
214/// assert_eq!(
215///     tree.try_node(&id4),
216///     Err(NodeIdxError::ReorganizedCollection)
217/// );
218/// ```
219pub trait MemoryPolicy: 'static {
220    /// Memory reclaim policy for the specific tree variant `V`.
221    type MemoryReclaimPolicy<V>: orx_selfref_col::MemoryPolicy<V>
222    where
223        V: TreeVariant;
224}
225
226/// The `Lazy` policy never reclaims closed nodes.
227///
228/// * It never reclaims memory.
229/// * It never leads to implicit invalidation of node indices.
230///   In other words, a node index can only be invalidated if we remove that node from the tree ([`RemovedNode`]).
231///
232/// Compared to `Auto`, lazy approach has the following pros & cons:
233///
234/// * (+) Node indices ([`NodeIdx<V>`]) created for a lazy tree will never be implicitly invalidated.
235///   Recall that, analogous to random access to an array element with its index,
236///   node indices allow for constant time access to the corresponding node.
237///   A node index can be invalidated if either of the following happens
238///   (i) the node is removed from the tree,
239///   (ii) other nodes are removed from the tree triggering a memory reclaim operation.
240///   With lazy policy, the second case can never happen.
241/// * (-) Uses more memory due to the gaps of the closed nodes especially when there exist many deletions
242///   from the tree.
243///
244/// Notice that the con can never be observed for grow-only situations; or might be insignificant when the
245/// number of removals is not very large. In such situations, it is the recommended memory reclaim policy.
246///
247/// [`NodeIdx<V>`]: orx_selfref_col::NodeIdx
248/// [`RemovedNode`]: crate::NodeIdxError::RemovedNode
249pub struct Lazy;
250impl MemoryPolicy for Lazy {
251    type MemoryReclaimPolicy<V>
252        = MemoryReclaimNever
253    where
254        V: TreeVariant;
255}
256
257/// The `Auto` policy reclaims closed nodes whenever the utilization falls below 75%.
258///
259/// * The default policy; and hence, the generic parameter can be omitted.
260/// * It automatically reclaims memory whenever the utilization falls below 75%.
261/// * Automatic triggers of memory reclaims might lead to implicit invalidation of node indices due to [`ReorganizedCollection`].
262/// * It is important to note that, even when using Auto policy, tree growth will never trigger memory reclaim.
263///   Only node removing mutations such as [`prune`] can trigger node reorganization.
264///
265/// Compared to `Lazy`, auto approach has the following pros & cons:
266///
267/// * (+) Node indices ([`NodeIdx<V>`]) created for an auto-reclaim tree will can be implicitly invalidated.
268///   Recall that, analogous to random access to an array element with its index,
269///   node indices allow for constant time access to the corresponding node.
270///   A node index can be invalidated if either of the following happens
271///   (i) the node is removed from the tree,
272///   (ii) other nodes are removed from the tree triggering a memory reclaim operation.
273///   With auto policy, both the explicit (i) and implicit (ii) invalidation can occur.
274/// * (-) Uses memory efficiently making sure that utilization can never go below a constant threshold.
275///
276/// [`NodeIdx<V>`]: orx_selfref_col::NodeIdx
277///
278/// Since Auto is the default memory policy, we do not need to include it in the tree type signature.
279///
280/// In order to observe the impact of the memory reclaim policy on validity of the node indices,
281/// please see the Examples section of [`MemoryPolicy`].
282///
283/// [`ReorganizedCollection`]: crate::NodeIdxError::ReorganizedCollection
284/// [`prune`]: crate::NodeMut::prune
285pub struct Auto;
286impl MemoryPolicy for Auto {
287    type MemoryReclaimPolicy<V>
288        = MemoryReclaimOnThreshold<2, V, V::Reclaimer>
289    where
290        V: TreeVariant;
291}
292
293/// The `AutoWithThreshold` policy reclaims closed nodes whenever the utilization falls below a certain threshold
294/// which is determined by the constant parameter `D`.
295///
296/// * This is a generalization of the [`Auto`] policy; in particular, Auto is equivalent to `AutoWithThreshold<2>`.
297/// * Its constant parameter `D` defines the utilization threshold to trigger the memory reclaim operation.
298/// * Specifically, memory of closed nodes will be reclaimed whenever the ratio of closed nodes to all nodes exceeds one over `2^D`.
299///   The greater the `D`, the greater the utilization threshold.
300///   * when `D = 0`: memory will be reclaimed when utilization is below 0.00% (equivalent to never).
301///   * when `D = 1`: memory will be reclaimed when utilization is below 50.00%.
302///   * when `D = 2`: memory will be reclaimed when utilization is below 75.00%.
303///   * when `D = 3`: memory will be reclaimed when utilization is below 87.50%.
304///   * when `D = 4`: memory will be reclaimed when utilization is below 93.75%.
305///
306/// Compared to `Lazy`, auto approach has the following pros & cons:
307///
308/// * (+) Node indices ([`NodeIdx<V>`]) created for an auto-reclaim tree will can be implicitly invalidated.
309///   Recall that, analogous to random access to an array element with its index,
310///   node indices allow for constant time access to the corresponding node.
311///   A node index can be invalidated if either of the following happens
312///   (i) the node is removed from the tree,
313///   (ii) other nodes are removed from the tree triggering a memory reclaim operation.
314///   With auto policy, both the explicit (i) and implicit (ii) invalidation can occur.
315/// * (-) Uses memory efficiently making sure that utilization can never go below a constant threshold.
316///
317/// [`NodeIdx<V>`]: orx_selfref_col::NodeIdx
318///
319/// Since Auto is the default memory policy, we do not need to include it in the tree type signature.
320///
321/// In order to observe the impact of the memory reclaim policy on validity of the node indices,
322/// please see the Examples section of [`MemoryPolicy`].
323pub struct AutoWithThreshold<const D: usize>;
324impl<const D: usize> MemoryPolicy for AutoWithThreshold<D> {
325    type MemoryReclaimPolicy<V>
326        = MemoryReclaimOnThreshold<D, V, V::Reclaimer>
327    where
328        V: TreeVariant;
329}
330
331// tree methods
332
333impl<V, M, P> Tree<V, M, P>
334where
335    V: TreeVariant,
336    M: MemoryPolicy,
337    P: PinnedStorage,
338{
339    /// Returns current node utilization of the collection.
340    pub fn memory_utilization(&self) -> Utilization {
341        self.0.utilization()
342    }
343}
344
345// tree memory policy transformations: into_lazy_reclaim
346
347impl<V, P> Tree<V, Auto, P>
348where
349    V: TreeVariant,
350    P: PinnedStorage,
351{
352    /// Transforms the tree's memory reclaim policy from [`Auto`] to [`Lazy`]:
353    ///
354    /// * `Tree<V>` => `Tree<V, Lazy>` // Auto can be omitted on since it is the default
355    /// * `DynTree<u62>` => `DynTree<u32, Lazy>`
356    /// * `BinaryTree<u62>` => `BinaryTree<u32, Lazy>`
357    ///
358    /// This is free operation and does not require any allocation or copies.
359    pub fn into_lazy_reclaim(self) -> Tree<V, Lazy, P> {
360        Tree(self.0.into())
361    }
362}
363
364impl<const D: usize, V, P> Tree<V, AutoWithThreshold<D>, P>
365where
366    V: TreeVariant,
367    P: PinnedStorage,
368{
369    /// Transforms the tree's memory reclaim policy from [`AutoWithThreshold`] to [`Lazy`]:
370    ///
371    /// * `Tree<V, AutoWithThreshold<3>>` => `Tree<V, Lazy>`
372    /// * `DynTree<u62, AutoWithThreshold<4>>` => `DynTree<u32, Lazy>`
373    /// * `BinaryTree<u62, AutoWithThreshold<1>>` => `BinaryTree<u32, Lazy>`
374    ///
375    /// This is free operation and does not require any allocation or copies.
376    pub fn into_lazy_reclaim(self) -> Tree<V, Lazy, P> {
377        Tree(self.0.into())
378    }
379}
380
381// tree memory policy transformations: into_auto_reclaim
382
383impl<V, P> Tree<V, Lazy, P>
384where
385    V: TreeVariant,
386    P: PinnedStorage,
387{
388    /// Transforms the tree's memory reclaim policy from [`Lazy`] to [`Auto`]:
389    ///
390    /// * `Tree<V, Lazy>` => `Tree<V>` // Auto can be omitted on since it is the default
391    /// * `DynTree<u62, Lazy>` => `DynTree<u32>`
392    /// * `BinaryTree<u62, Lazy>` => `BinaryTree<u32>`
393    ///
394    /// This is free operation and does not require any allocation or copies.
395    pub fn into_auto_reclaim(self) -> Tree<V, Auto, P> {
396        let mut tree = Tree(self.0.into());
397        let will_reclaim =
398            <Auto as MemoryPolicy>::MemoryReclaimPolicy::<V>::col_needs_memory_reclaim(&tree.0);
399
400        if will_reclaim {
401            let nodes_moved = <V::Reclaimer as MemoryReclaimer<V>>::reclaim_nodes(&mut tree.0);
402            tree.0.update_state(nodes_moved);
403        }
404        tree
405    }
406
407    /// Transforms the tree's memory reclaim policy from [`Lazy`] to [`AutoWithThreshold`]:
408    ///
409    /// * `Tree<V, Lazy>` => `Tree<V, AutoWithThreshold<3>>`
410    /// * `DynTree<u62, Lazy>` => `DynTree<u32, AutoWithThreshold<1>>`
411    /// * `BinaryTree<u62, Lazy>` => `BinaryTree<u32, AutoWithThreshold<4>>`
412    ///
413    /// This is free operation and does not require any allocation or copies.
414    pub fn into_auto_reclaim_with_threshold<const D: usize>(
415        self,
416    ) -> Tree<V, AutoWithThreshold<D>, P> {
417        let mut tree = Tree(self.0.into());
418        let will_reclaim =
419            <AutoWithThreshold<D> as MemoryPolicy>::MemoryReclaimPolicy::<V>::col_needs_memory_reclaim(&tree.0);
420        if will_reclaim {
421            <V::Reclaimer as MemoryReclaimer<V>>::reclaim_nodes(&mut tree.0);
422        }
423        tree
424    }
425}