bounded_node/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fuchsia_inspect::{Node, NumericProperty as _, Property as _, UintProperty};
6use fuchsia_inspect_contrib::inspectable::InspectableU64;
7use std::collections::VecDeque;
8
9/// A `Node` with a bounded number of child `Node`s.
10///
11/// Child `Node`s at indices `[begin, end)` are guaranteed to be fully constructed.
12/// ```
13/// # use fuchsia_inspect::Inspector;
14/// # use diagnostics_assertions::assert_data_tree;
15/// # use bounded_node::BoundedNode;
16///
17/// let inspector = Inspector::default();
18/// let mut bounded_node =
19///     BoundedNode::from_node_and_capacity(inspector.root().create_child("bounded-node"), 2);
20///
21/// struct Item {
22///     _node: Node,
23/// }
24/// bounded_node.push(|n| {
25///     n.record_string("dropped-field", "dropped-value");
26///     Item { _node: n }
27/// });
28/// bounded_node.push(|n| {
29///     n.record_string("some-field", "some-value");
30///     Item { _node: n }
31/// });
32/// bounded_node.push(|n| {
33///     n.record_string("other-field", "other-value");
34///     Item { _node: n }
35/// });
36///
37/// assert_data_tree!(
38///     inspector,
39///     root: {
40///         "bounded-node": {
41///             "capacity": 2u64,
42///             "begin": 1u64,
43///             "end": 3u64,
44///             "children": {
45///                 "1": { "some-field": "some-value" },
46///                 "2": { "other-field": "other-value" }
47///             }
48///         }
49///     }
50/// )
51/// ```
52#[derive(Debug)]
53pub struct BoundedNode<V> {
54    #[expect(dead_code)]
55    node: Node,
56    children_node: Node,
57    capacity: usize,
58    begin: UintProperty,
59    end: InspectableU64,
60    vs: VecDeque<V>,
61}
62
63impl<V> BoundedNode<V> {
64    /// Creates a `BoundedNode`. `BoundedNode`s with `capacity` zero do not store any `V`s.
65    pub fn from_node_and_capacity(node: Node, capacity: usize) -> Self {
66        node.record_uint("capacity", capacity as u64);
67        let children_node = node.create_child("children");
68        let begin = node.create_uint("begin", 0);
69        let end = InspectableU64::new(0, &node, "end");
70        Self { node, children_node, capacity, begin, end, vs: VecDeque::new() }
71    }
72
73    /// Push a child `Node` to the exported Inspect tree. `f` is called with the new `Node` and
74    /// returns a type `V` that should contain that `Node` and any other desired Inspect objects.
75    /// Drops oldest child `Node` if `capacity` would be exceeded.
76    pub fn push(&mut self, f: impl FnOnce(Node) -> V) {
77        if self.capacity == 0 {
78            // Increment `begin` first so the valid range is always empty.
79            self.begin.add(1);
80            *self.end.get_mut() += 1;
81            return;
82        }
83        let v = f(self.children_node.create_child(self.end.to_string()));
84        // Increment `end` after pushing the new Node, to preserve the valid Node range invariant.
85        self.vs.push_front(v);
86        *self.end.get_mut() += 1;
87        // Increment `begin` before (possibly) dropping the oldest Node.
88        self.begin.set((*self.end).saturating_sub(self.capacity as u64));
89        self.vs.truncate(self.capacity);
90    }
91}
92
93#[cfg(test)]
94mod test {
95    use super::*;
96    use diagnostics_assertions::assert_data_tree;
97    use fuchsia_inspect::Inspector;
98
99    struct Item {
100        _node: Node,
101    }
102    impl Item {
103        fn new(node: Node, i: i64) -> Self {
104            node.record_int("i", i);
105            Self { _node: node }
106        }
107    }
108
109    #[test]
110    fn zero_capacity_push() {
111        let inspector = Inspector::default();
112        let mut bounded_node =
113            BoundedNode::from_node_and_capacity(inspector.root().create_child("bounded-node"), 0);
114
115        bounded_node.push(|n| Item::new(n, 0));
116
117        assert_data_tree!(
118            inspector,
119            root: {
120                "bounded-node": {
121                    "capacity": 0u64,
122                    "begin": 1u64,
123                    "end": 1u64,
124                    "children": {},
125                }
126            }
127        )
128    }
129
130    #[test]
131    fn push() {
132        let inspector = Inspector::default();
133        let mut bounded_node =
134            BoundedNode::from_node_and_capacity(inspector.root().create_child("bounded-node"), 1);
135
136        bounded_node.push(|n| Item::new(n, 0));
137
138        assert_data_tree!(
139            inspector,
140            root: {
141                "bounded-node": {
142                    "capacity": 1u64,
143                    "begin": 0u64,
144                    "end": 1u64,
145                    "children": {
146                        "0": { i: 0i64 }
147                    },
148                }
149            }
150        )
151    }
152
153    #[test]
154    fn push_triggers_drop_of_oldest() {
155        let inspector = Inspector::default();
156        let mut bounded_node =
157            BoundedNode::from_node_and_capacity(inspector.root().create_child("bounded-node"), 3);
158
159        for i in 0..4 {
160            bounded_node.push(|n| Item::new(n, i));
161        }
162
163        assert_data_tree!(
164            inspector,
165            root: {
166                "bounded-node": {
167                    "capacity": 3u64,
168                    "begin": 1u64,
169                    "end": 4u64,
170                    "children": {
171                        "1": { i: 1i64 },
172                        "2": { i: 2i64 },
173                        "3": { i: 3i64 },
174                    }
175                }
176            }
177        )
178    }
179}