multi_stack_queue/
lib.rs

1//! A crate for stack-allocated fixed-length multiqueues. A multiqueue is an array of a given number of queues,
2//! each able to be accessed independently.
3//!
4//! In term, this crate should include a feature that enables the user to specify what the multiqueue must do
5//! in the case the `pop` or `push` method cannot operate (e.g. empty or full individual queue.).
6//! For instance, one could wish the operation is, in such a case, applied to the following queue.
7//!
8//! This crate was motivated by the creation of a multiple-round-robin-based scheduler in a toy micro-kernel.
9//! Each queue holds all the threads within the same priority level.
10//! Attempting to create a new thread in an already full priority level would simply decrease its priority
11//! until a suitable non-full queue is found.
12//!
13//! Based on an original idea from [Pollux3737](https://github.com/Pollux3737).
14
15use std::mem::MaybeUninit;
16
17/// Errors that may be encountered during use of the [`MultiStackQueue`]
18///
19/// * `QueueFull` - Returned by the `push` method when trying to append a value to a queue that is already full
20/// * `QueueEmpty` - Returned by the `pop` method when trying to pop a value from an empty queue
21/// * `QueueIndexOutOfBounds` - When trying to access a queue beyond the multiqueue
22/// * `UnknownError` - This should never happen. Used for development
23#[derive(Debug, Copy, Clone, Eq, PartialEq)]
24pub enum MSQError {
25    QueueFull,
26    QueueEmpty,
27    QueueIndexOutOfBounds,
28    UnknowmError,
29}
30
31/// An abstract structure containin multiple stack-allocated bounded queues.
32///
33/// Each queue is stored as an `[Option<T>; N]` and the multiqueue stores
34//// the complete data in an `[[Option<T>; N]; M].
35
36///
37/// # Usage
38///
39/// The generic definition is the following :
40///
41/// ```ignore
42/// MultiStackQueue<T, const N: usize, const M: usize>
43/// ```
44///
45/// With :
46///
47/// * `T` - type contained in the queues
48/// * `N` - length of each queue
49/// * `M` - number of queues
50///
51/// # Example usecases
52///
53/// * When writing a simple micro-kernel, the scheduler may need some sort of multiple Round-Robins.
54/// Having it allocated on the stack removes the need for a heap allocator, which can be useful
55/// when working on this kind of ressource-limited target.
56///
57/// # Examples
58///
59/// ```
60/// use multi_stack_queue::MultiStackQueue;
61///
62/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
63/// struct TestStruct {
64///     a: usize,
65///     b: bool,   
66/// }
67///
68/// let mut msq: MultiStackQueue<TestStruct, 16, 8> = MultiStackQueue::new();
69/// let value = TestStruct { a: 42, b: false };
70///
71/// msq.push(7, value).unwrap();
72///
73/// assert_eq!(msq.pop(7).unwrap(), value);
74/// ```
75///
76pub struct MultiStackQueue<T, const N: usize, const M: usize> {
77    data: [[Option<T>; N]; M],
78    ins: [usize; M],
79    outs: [usize; M],
80    empty: [bool; M],
81}
82
83impl<T, const N: usize, const M: usize> MultiStackQueue<T, N, M> {
84    /// Returns a new empty multiqueue.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use multi_stack_queue::MultiStackQueue;
90    /// // Returns a fresh empty multiqueue containing 8 queues of `usize` with size 16
91    /// let a: MultiStackQueue<usize, 16, 8> = MultiStackQueue::new();
92    ///
93    /// #[derive(Clone, Copy)]
94    /// struct TestStruct {
95    ///     a: usize,
96    ///     b: bool    
97    /// }
98    ///
99    /// let random_data = TestStruct { a: 42, b: false };
100    ///
101    /// let msq: MultiStackQueue<TestStruct, 4, 2> = MultiStackQueue::new();
102    /// ```
103    ///
104    pub fn new() -> Self {
105        let d: [[Option<T>; N]; M] = unsafe { MaybeUninit::uninit().assume_init() };
106        MultiStackQueue {
107            data: d,
108            ins: [0usize; M],
109            outs: [0usize; M],
110            empty: [true; M],
111        }
112    }
113    /// Appends a value to the multiqueue.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use multi_stack_queue::MultiStackQueue;
119    ///
120    /// #[derive(Clone, Copy)]
121    /// struct TestStruct {
122    ///     a: usize,
123    ///     b: bool    
124    /// }
125    ///
126    /// let random_data = TestStruct { a: 42, b: false };
127    ///
128    /// let mut msq: MultiStackQueue<TestStruct, 4, 2> = MultiStackQueue::new();
129    ///
130    /// msq.push(0, random_data).unwrap();
131    /// ```
132    ///
133    pub fn push(&mut self, id: usize, value: T) -> Result<(), MSQError> {
134        if id >= M {
135            return Err(MSQError::QueueIndexOutOfBounds);
136        }
137        self.try_and_push(id, value)
138    }
139    // Inner `push` function
140    fn try_and_push(&mut self, id: usize, value: T) -> Result<(), MSQError> {
141        if self.ins[id] == self.outs[id] && !self.empty[id] {
142            // Queue is full
143            Err(MSQError::QueueFull)
144        } else {
145            self.data[id][self.ins[id]] = Some(value);
146            self.ins[id] = (self.ins[id] + 1) % N;
147            self.empty[id] = false;
148            Ok(())
149        }
150    }
151    /// Pops a value from the multiqueue.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use multi_stack_queue::MultiStackQueue;
157    ///
158    /// #[derive(Clone, Copy)]
159    /// struct TestStruct {
160    ///     a: usize,
161    ///     b: bool    
162    /// }
163    ///
164    /// let random_data = TestStruct { a: 42, b: false };
165    ///
166    /// let mut msq: MultiStackQueue<TestStruct, 4, 2> = MultiStackQueue::new();
167    ///
168    /// msq.push(0, random_data).unwrap();
169    /// msq.pop(0).unwrap();
170    /// ```
171    ///
172    pub fn pop(&mut self, id: usize) -> Result<T, MSQError> {
173        if id >= M {
174            return Err(MSQError::QueueIndexOutOfBounds);
175        }
176        self.try_and_pop(id)
177    }
178    /// Inner `pop` function
179    fn try_and_pop(&mut self, id: usize) -> Result<T, MSQError> {
180        if self.empty[id] {
181            Err(MSQError::QueueEmpty)
182        } else {
183            // TODO The unwrap is not ideal
184            let res = self.data[id][self.outs[id]].take().unwrap();
185            self.outs[id] = (self.outs[id] + 1) % N;
186            if self.outs[id] == self.ins[id] {
187                self.empty[id] = true;
188            }
189            Ok(res)
190        }
191    }
192    /// Returns whether a particular queue is full
193    /// # Examples
194    ///
195    /// ```
196    /// use multi_stack_queue::MultiStackQueue;
197    ///
198    /// let mut msq: MultiStackQueue<usize, 4, 2> = MultiStackQueue::new();
199    ///
200    /// assert!(!msq.is_full(0));
201    /// for _ in 0..4 {
202    ///     msq.push(0, 0);
203    /// }
204    /// assert!(msq.is_full(0));
205    /// ```
206    ///
207    pub fn is_full(&self, id: usize) -> bool {
208        !self.empty[id] && self.ins[id] == self.outs[id]
209    }
210    /// Returns whether a particular queue is empty
211    /// # Examples
212    ///
213    /// ```
214    /// use multi_stack_queue::MultiStackQueue;
215    ///
216    /// let mut msq: MultiStackQueue<usize, 4, 2> = MultiStackQueue::new();
217    ///
218    /// assert!(msq.is_empty(0));
219    /// msq.push(0, 0);
220    /// assert!(!msq.is_empty(0));
221    /// ```
222    ///
223    pub fn is_empty(&self, id: usize) -> bool {
224        self.empty[id]
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use crate::MultiStackQueue;
231
232    /// Simple test structure
233    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
234    struct TestStruct {
235        a: usize,
236        b: bool,
237    }
238
239    /// Testing the creation of a MSQ
240    #[test]
241    fn creation() {
242        let a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
243    }
244
245    /// Testing one `push` operation
246    #[test]
247    fn push_once() {
248        let mut a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
249        let val = TestStruct { a: 42, b: true };
250        a.push(12, val).unwrap();
251    }
252
253    /// Testing one push-pop cycle
254    #[test]
255    fn push_and_pop_once() {
256        let mut a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
257        let val = TestStruct { a: 42, b: true };
258        a.push(12, val).unwrap();
259        assert_eq!(a.pop(12).unwrap(), val);
260        assert!(a.is_empty(12));
261    }
262
263    /// Testing push-pop-pop
264    #[test]
265    #[should_panic]
266    fn push_and_pop_twice() {
267        let mut a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
268        let val = TestStruct { a: 42, b: true };
269        a.push(12, val).unwrap();
270        a.pop(12).unwrap();
271        a.pop(12).unwrap();
272    }
273
274    /// testing a single pop operation
275    #[test]
276    #[should_panic]
277    fn pop_empty() {
278        let mut a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
279        a.pop(12).unwrap();
280    }
281
282    /// Testing the filling of a queue
283    #[test]
284    fn fill() {
285        let mut a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
286        let val = TestStruct { a: 42, b: true };
287        for _ in 0..16 {
288            a.push(12, val).unwrap();
289        }
290    }
291
292    /// Testing the overflow of a queue
293    #[test]
294    #[should_panic]
295    fn fill_overflow() {
296        let mut a: MultiStackQueue<TestStruct, 16, 32> = MultiStackQueue::new();
297        let val = TestStruct { a: 42, b: true };
298        for _ in 0..=16 {
299            a.push(12, val).unwrap();
300        }
301    }
302
303    /// Testing that the queue works as intended
304    #[test]
305    fn fifo() {
306        let mut a: MultiStackQueue<usize, 16, 32> = MultiStackQueue::new();
307        a.push(0, 1).unwrap();
308        a.push(0, 2).unwrap();
309        assert_eq!(a.pop(0).unwrap(), 1);
310        assert_eq!(a.pop(0).unwrap(), 2);
311    }
312}