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}