texcraft_stdext/collections/circularbuffer.rs
1//! A circular buffer.
2//!
3//! See the documentation on the [CircularBuffer] type.
4
5use std::ops::IndexMut;
6
7/// A circular buffer.
8///
9/// A circular buffer is an ordered buffer with a fixed capacity and the property that if it is full
10/// and a new element is pushed to the front, the oldest element is deleted.
11///
12/// The buffer is created using the [new](CircularBuffer::new) method, elements are pushed to the
13/// front using [push](CircularBuffer::push), and retrieved using
14/// [std::ops::Index::index] trait method.
15///
16/// ```
17/// # use texcraft_stdext::collections::circularbuffer::CircularBuffer;
18/// # use std::ops::Index;
19/// let mut buf = CircularBuffer::new(3);
20/// buf.push(0);
21/// buf.push(1);
22/// assert_eq![buf.index(0), &1];
23/// assert_eq![buf.index(1), &0];
24/// buf.push(2);
25/// buf.push(3);
26/// assert_eq![buf.index(0), &3];
27/// assert_eq![buf.index(1), &2];
28/// assert_eq![buf.index(2), &1];
29/// ```
30pub struct CircularBuffer<T> {
31 data: Vec<T>,
32 head: usize,
33}
34
35impl<T> CircularBuffer<T> {
36 /// Create a new circular buffer with the provided capacity.
37 pub fn new(capacity: usize) -> CircularBuffer<T> {
38 CircularBuffer {
39 data: Vec::with_capacity(capacity),
40 head: 0,
41 }
42 }
43
44 /// Push a new element to the front of the buffer.
45 pub fn push(&mut self, elem: T) {
46 if self.data.len() < self.data.capacity() {
47 self.data.push(elem);
48 self.head = self.data.len() - 1;
49 return;
50 }
51 self.head = (self.head + 1) % self.capacity();
52 *self.data.index_mut(self.head) = elem;
53 }
54
55 /// Return the buffer's capacity.
56 pub fn capacity(&self) -> usize {
57 self.data.capacity()
58 }
59
60 fn internal_index(&self, i: usize) -> usize {
61 (self.head + self.capacity() - i) % self.capacity()
62 }
63}
64
65impl<T> std::ops::Index<usize> for CircularBuffer<T> {
66 type Output = T;
67
68 /// Get the element at the provided index.
69 ///
70 /// The first element in the buffer has index 0, the next element index 1, etc.
71 ///
72 /// This method may panic if the index is valid.
73 fn index(&self, i: usize) -> &T {
74 &self.data[self.internal_index(i)]
75 }
76}
77
78impl<T: Clone> CircularBuffer<T> {
79 /// Clone the element at the provided index to the front of the buffer.
80 ///
81 /// The buffer contains the following optimization: if the buffer is full and the
82 /// referenced element is the tail element, this method is a no-op.
83 /// If the method's clone function has side effects, these will not be seen, as in
84 /// the following example.
85 ///
86 /// ```
87 /// # use texcraft_stdext::collections::circularbuffer::CircularBuffer;
88 /// # use std::rc::Rc;
89 /// # use std::cell::RefCell;
90 /// /// A struct whose clone function has a side effect; i.e., is not pure.
91 /// /// The side effect is that a shared counter owned by all clones is incremented.
92 /// /// The shared counter thus records the number of times a clone has occured.
93 /// struct ImpureClone {
94 /// counter: Rc<RefCell<i64>>,
95 /// }
96 ///
97 /// impl Clone for ImpureClone {
98 /// fn clone(&self) -> Self {
99 /// let old_count = *self.counter.borrow();
100 /// *self.counter.borrow_mut() = old_count + 1;
101 /// ImpureClone {
102 /// counter: self.counter.clone()
103 /// }
104 /// }
105 /// }
106 ///
107 /// let mut buf = CircularBuffer::new(2);
108 /// let counter = Rc::new(RefCell::new(0));
109 /// buf.push(ImpureClone{counter: counter.clone()});
110 /// assert_eq![*counter.borrow(), 0];
111 ///
112 /// buf.clone_to_front(0);
113 /// assert_eq![*counter.borrow(), 1];
114 ///
115 /// // Clone from the tail - no clone occurs!
116 /// buf.clone_to_front(1);
117 /// assert_eq![*counter.borrow(), 1];
118 /// ```
119 pub fn clone_to_front(&mut self, i: usize) -> &mut T {
120 let i = self.internal_index(i);
121 if self.data.len() < self.data.capacity() {
122 self.push(self.data[i].clone());
123 return self.data.last_mut().unwrap();
124 }
125 let tail_i = (self.head + 1) % self.capacity();
126 match i.cmp(&tail_i) {
127 std::cmp::Ordering::Equal => {
128 // If we're cloning the current tail, we just make it the head by incrementing
129 // the head ptr. No need to clone in this case!
130 }
131 std::cmp::Ordering::Less => {
132 let (front, back) = self.data.split_at_mut(tail_i);
133 back[0].clone_from(&front[i]);
134 }
135 std::cmp::Ordering::Greater => {
136 let (front, back) = self.data.split_at_mut(i);
137 front[tail_i].clone_from(&back[0]);
138 }
139 }
140 self.head = tail_i;
141 self.data.index_mut(self.head)
142 }
143}