journald_export_parser_rs/shiftbuffer.rs
1//! Operate on the tail of a data stream that is managed using a buffer of
2//! limited size.
3//!
4//! A [ShiftBuffer] enables pointer arithmetics on data that is occasionally
5//! 'shifted'; i.e. the data is being moved to the beginning of the buffer to
6//! retain its size while allowing for further data to be read into the buffer.
7//! To that end, [ShiftBuffer] maintains a sliding window which can be extended
8//! (the upper end moves up) or shrunk (the lower end moves up). To access the
9//! data within the window, the buffer can be indexed using a [Pointer].
10//!
11//! Typically, this is used in a scenario where one wants to operate on the tail
12//! of a continuous data stream while only allocating a fixed buffer. Whenever
13//! the buffer is 'shifted', it is conceptually moved forward in the data
14//! stream.
15//!
16//! The following is an illustration of the state before and after a shift.
17//! Here, the cursor is a pointer into the window. Technically, a pointer
18//! retains its position within the original data stream. We call this position
19//! 'absolute' and it can be revealed with [Pointer::abs].
20//!
21//! ```text
22//! before:
23//! |<----------- buffer ----------->|
24//! | |<----- window ----->|
25//! ^
26//! |
27//! ~~ data stream ~~~~~~~~[cursor]~~~~~
28//! after: |
29//! v
30//! |<----------- buffer ----------->|
31//! |<----- window ----->|<- free -->|
32//! ```
33//!
34//! Following the illustration above, this is the state after the window has
35//! been extended:
36//!
37//! ```text
38//! ~~ data stream ~~~~~~~~[cursor]~~~~~~~~~~
39//! |
40//! v
41//! |<----------- buffer ----------->|
42//! |<------- window --------->|< f >|
43//! ```
44//!
45//! In a typical scenario, one would call [ShiftBuffer::make_room] whenever more
46//! data needs to be read into the buffer. This method either shifts the window
47//! or doubles the buffer size, depending on whether the window currently covers
48//! the entire buffer or not.
49
50use std::ops::{Add, AddAssign, Index, IndexMut, Range, Sub, SubAssign};
51
52#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy, Default)]
53pub struct Pointer(usize);
54
55impl Pointer {
56 /// The pointer returns the _absolute_ position in the byte stream that was
57 /// consumed using the shift buffer.
58 pub fn abs(&self) -> usize {
59 self.0
60 }
61}
62
63impl Add<usize> for Pointer {
64 type Output = Pointer;
65
66 fn add(self, rhs: usize) -> Self::Output {
67 Self(self.0 + rhs)
68 }
69}
70
71impl AddAssign<usize> for Pointer {
72 fn add_assign(&mut self, rhs: usize) {
73 self.0 += rhs
74 }
75}
76
77impl Sub<usize> for Pointer {
78 type Output = Pointer;
79
80 fn sub(self, rhs: usize) -> Self::Output {
81 Self(self.0 - rhs)
82 }
83}
84
85impl SubAssign<usize> for Pointer {
86 fn sub_assign(&mut self, rhs: usize) {
87 self.0 -= rhs
88 }
89}
90
91impl Sub<Pointer> for Pointer {
92 type Output = usize;
93
94 fn sub(self, rhs: Pointer) -> Self::Output {
95 self.0 - rhs.0
96 }
97}
98
99pub struct ShiftBuffer<T> {
100 buf: Vec<T>,
101 // The absolute position of the lower end of the window in the overall byte
102 // stream. To put it differently, this is the total sum of all advances.
103 offset: Pointer,
104 lower: Pointer,
105 upper: Pointer,
106}
107
108impl<T: Default + Copy> ShiftBuffer<T> {
109 pub fn new(init_size: usize) -> Self {
110 let buf = (0..init_size).map(|_| T::default()).collect();
111 Self {
112 buf,
113 offset: Pointer::default(),
114 lower: Pointer::default(),
115 upper: Pointer::default(),
116 }
117 }
118
119 /// Moves the lower end of the window by `n`.
120 pub fn shrink(&mut self, n: usize) -> Pointer {
121 assert!(self.lower + n < self.upper);
122 self.lower += n;
123 self.lower
124 }
125
126 /// Moves the upper end of the window by `n`.
127 pub fn extend(&mut self, n: usize) -> Pointer {
128 assert!(self.relative_pos(self.upper) + n <= self.buf.len());
129 self.upper += n;
130 self.upper
131 }
132
133 /// Make room in the buffer for more data.
134 ///
135 /// If the upper end of the window is not at the stop position of the
136 /// internal buffer, this method has no effect on the state of the buffer.
137 ///
138 /// Otherwise, it performs either of two operations: if the lower end is at
139 /// the beginning of the buffer (the window covers the entire buffer), the
140 /// buffer is extended. Otherwise, the buffer is shifted; i.e., all entries
141 /// prior to the lower end are discarded and the content is moved to the
142 /// beginning of the buffer.
143 ///
144 /// In all cases, the return value of this method is the same as for
145 /// [ShiftBuffer::free].
146 pub fn make_room(&mut self) -> &mut [T] {
147 if self.relative_pos(self.upper) == self.buf.len() {
148 if self.lower == self.offset {
149 self.buf.extend((0..self.buf.len()).map(|_| T::default()))
150 } else {
151 self.shift();
152 }
153 }
154 self.free()
155 }
156
157 pub fn shift(&mut self) {
158 let d = self.upper.abs() - self.lower.abs();
159 for p in 0..d {
160 self.buf[p] = self.buf[p + d]
161 }
162 self.offset = self.lower;
163 }
164
165 pub fn free(&mut self) -> &mut [T] {
166 let r = self.relative_pos(self.upper);
167 &mut self.buf[r..]
168 }
169
170 pub fn lower(&self) -> Pointer {
171 self.lower
172 }
173
174 pub fn upper(&self) -> Pointer {
175 self.upper
176 }
177
178 pub fn relative_pos(&self, p: Pointer) -> usize {
179 debug_assert!(self.lower <= p && p <= self.upper);
180 p - self.offset
181 }
182
183 /// Create a shift buffer that contains a copy of the current window.
184 pub fn clone_window(&self) -> ShiftBuffer<T> {
185 let (l, u) = (self.lower, self.upper);
186 ShiftBuffer {
187 buf: self[l..u].to_vec(),
188 offset: l,
189 lower: l,
190 upper: u,
191 }
192 }
193}
194
195impl<T: Default + Copy> Index<Pointer> for ShiftBuffer<T> {
196 type Output = T;
197
198 fn index(&self, index: Pointer) -> &Self::Output {
199 debug_assert!(self.lower <= index && index <= self.upper);
200 &self.buf[self.relative_pos(index)]
201 }
202}
203
204impl<T: Default + Copy> IndexMut<Pointer> for ShiftBuffer<T> {
205 fn index_mut(&mut self, index: Pointer) -> &mut Self::Output {
206 debug_assert!(self.lower <= index && index <= self.upper);
207 let r = self.relative_pos(index);
208 &mut self.buf[r]
209 }
210}
211
212impl<T: Default + Copy> Index<Range<Pointer>> for ShiftBuffer<T> {
213 type Output = [T];
214
215 fn index(&self, r: Range<Pointer>) -> &Self::Output {
216 debug_assert!(r.start <= r.end);
217 debug_assert!(self.lower <= r.start && r.start <= self.upper);
218 debug_assert!(self.lower <= r.end && r.end <= self.upper);
219 &self.buf[self.relative_pos(r.start)..self.relative_pos(r.end)]
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::ShiftBuffer;
226
227 #[test]
228 fn store_simple_string() {
229 let input_string = "ABC";
230 let mut sbuf = ShiftBuffer::<u8>::new(1 << 10);
231 let (lower, upper) = (sbuf.lower(), sbuf.extend(3));
232
233 let mut cursor = lower;
234 for b in input_string.as_bytes() {
235 sbuf[cursor] = *b;
236 cursor += 1;
237 }
238
239 assert_eq!(&sbuf[lower..upper], input_string.as_bytes());
240 }
241}