tick_queue/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/tick-queue
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5/*!
6# Tick Queue Crate
7
8The `tick-queue` crate provides utilities for managing a sequence of items.
9Each item is associated with a unique tick identifier ([`TickId`]), ensuring that items are processed in the correct order.
10
11The crate offers functionality for pushing items, iterating over them, and managing the internal state of the item queue.
12It supports both direct manipulation of the item queue and indexed iteration.
13
14## Example
15
16```rust
17use tick_queue::{Queue, ItemInfo};
18use tick_id::TickId;
19
20// Create a new Queue instance with an initial tick
21let mut queue = Queue::new(TickId::new(0));
22
23// Push items into the queue
24queue.push(TickId::new(0), "Step 1").unwrap();
25queue.push(TickId::new(1), "Step 2").unwrap();
26
27// Pop the first item
28let item = queue.pop();
29assert_eq!(item.unwrap().item, "Step 1");
30
31// Iterate over remaining items
32for item in queue.iter() {
33  println!("Tick {}: {}", item.tick_id, item.item);
34}
35```
36
37*/
38
39use std::collections::VecDeque;
40use std::fmt::{Debug, Display, Formatter};
41use tick_id::TickId;
42
43#[derive(Debug, PartialEq, Eq, Clone)]
44pub struct ItemInfo<T> {
45    pub item: T,
46    pub tick_id: TickId,
47}
48
49impl<T: Display> Display for ItemInfo<T> {
50    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
51        write!(f, "{}: {}", self.tick_id, self.item)
52    }
53}
54
55#[derive(Debug)]
56pub struct Queue<T> {
57    items: VecDeque<ItemInfo<T>>,
58    expected_write_id: TickId, // Tracks the next TickId to be written, ensuring continuity even when the queue is empty
59}
60
61impl<T> Default for Queue<T> {
62    fn default() -> Self {
63        Self {
64            items: VecDeque::default(),
65            expected_write_id: TickId::default(),
66        }
67    }
68}
69
70impl<T> Queue<T> {
71    pub fn iter(&self) -> impl Iterator<Item = &ItemInfo<T>> {
72        self.items.iter()
73    }
74}
75
76impl<T> IntoIterator for Queue<T> {
77    type Item = ItemInfo<T>;
78    type IntoIter = std::collections::vec_deque::IntoIter<ItemInfo<T>>;
79
80    /// Consumes the `Queue` collection and returns an iterator over the items.
81    ///
82    /// This allows the use of the `for` loop and other iterator methods
83    /// without manually calling `iter()`.
84    fn into_iter(self) -> Self::IntoIter {
85        self.items.into_iter()
86    }
87}
88
89pub struct FromIndexIterator<'a, T> {
90    deque: &'a VecDeque<ItemInfo<T>>,
91    #[allow(unused)]
92    start_index: usize,
93    current_index: usize,
94}
95
96impl<'a, T> FromIndexIterator<'a, T> {
97    #[must_use]
98    pub const fn new(deque: &'a VecDeque<ItemInfo<T>>, start_index: usize) -> Self {
99        Self {
100            deque,
101            start_index,
102            current_index: start_index,
103        }
104    }
105}
106
107impl<T: Clone> Iterator for FromIndexIterator<'_, T> {
108    type Item = ItemInfo<T>;
109
110    fn next(&mut self) -> Option<Self::Item> {
111        let item = self.deque.get(self.current_index)?;
112        self.current_index += 1;
113        Some(item.clone())
114    }
115}
116
117pub const TICK_ID_MAX: u32 = u32::MAX;
118
119#[derive(Debug)]
120pub enum QueueError {
121    WrongTickId {
122        expected: TickId,
123        encountered: TickId,
124    },
125}
126
127impl<T: Clone> Queue<T> {
128    #[must_use]
129    pub const fn new(tick_id: TickId) -> Self {
130        Self {
131            items: VecDeque::new(),
132            expected_write_id: tick_id,
133        }
134    }
135
136    /// Clears the queue and resets the expected read and write tick IDs.
137    pub fn clear(&mut self, initial_tick_id: TickId) {
138        self.items.clear();
139        self.expected_write_id = initial_tick_id;
140    }
141
142    /// Pushes an item into the queue at the specified `TickId`.
143    ///
144    /// This method ensures that the item is added at the correct position in the tick sequence. The
145    /// `tick_id` must match the expected `TickId` for the queue to maintain an unbroken sequence.
146    ///
147    /// # Parameters
148    /// - `tick_id`: The `TickId` where the item should be inserted. It must match the queue's expected next `TickId`.
149    /// - `item`: The item to be inserted into the queue.
150    ///
151    /// # Returns
152    /// - `Ok(())` if the item is successfully added to the queue.
153    /// - `Err(QueueError)` if the provided `tick_id` does not match the expected `TickId`.
154    ///
155    /// # Errors
156    /// - Returns a `QueueError::WrongTickId` if the `tick_id` provided does not match the expected
157    ///   `TickId`, which maintains the sequential order of the queue.
158    ///
159    pub fn push(&mut self, tick_id: TickId, item: T) -> Result<(), QueueError> {
160        if self.expected_write_id != tick_id {
161            Err(QueueError::WrongTickId {
162                expected: self.expected_write_id,
163                encountered: tick_id,
164            })?;
165        }
166
167        self.push_internal(item);
168
169        Ok(())
170    }
171
172    fn push_internal(&mut self, item: T) {
173        let info = ItemInfo {
174            item,
175            tick_id: self.expected_write_id,
176        };
177        self.items.push_back(info);
178        self.expected_write_id += 1;
179    }
180
181    #[must_use]
182    pub fn debug_get(&self, index: usize) -> Option<&ItemInfo<T>> {
183        self.items.get(index)
184    }
185
186    #[must_use]
187    pub fn pop(&mut self) -> Option<ItemInfo<T>> {
188        self.items.pop_front()
189    }
190
191    pub fn discard_up_to(&mut self, tick_id: TickId) {
192        while let Some(info) = self.items.front() {
193            if info.tick_id >= tick_id {
194                break;
195            }
196
197            self.items.pop_front();
198        }
199
200        if self.is_empty() {
201            self.expected_write_id = tick_id + 1;
202        }
203    }
204
205    pub fn discard_count(&mut self, count: usize) {
206        if count >= self.items.len() {
207            self.items.clear();
208        } else {
209            self.items.drain(..count);
210        }
211    }
212
213    /// Pops up to a certain amount of items from the front of the queue and returns
214    /// the first `TickId` and a vector of `T`. Returns `None` if the queue
215    /// is empty.
216    ///
217    /// # Parameters
218    /// - `count`: The number of items to pop (or fewer if not enough items are available).
219    ///
220    /// # Returns
221    /// - `Some((TickId, Vec<T>))` if there are items available.
222    /// - `None` if the queue is empty.
223    ///
224    /// # Example
225    /// ```rust
226    /// use tick_id::TickId;
227    /// use tick_queue::Queue;
228    /// let mut items = Queue::new(TickId::new(0));
229    /// items.push(TickId::new(0), "Step 1").unwrap();
230    /// items.push(TickId::new(1), "Step 2").unwrap();
231    ///
232    /// let result = items.take(5);  // Will return up to 5 items (in this case 2)
233    /// if let Some((tick_id, popped_items)) = result {
234    ///     assert_eq!(tick_id, TickId::new(0));
235    ///     assert_eq!(popped_items, vec!["Step 1", "Step 2"]);
236    /// }
237    /// ```
238    #[must_use]
239    pub fn take(&mut self, count: usize) -> Option<(TickId, Vec<T>)> {
240        let first_tick_id = self.front_tick_id()?;
241
242        let items_to_take: Vec<T> = self
243            .items
244            .drain(..count.min(self.items.len()))
245            .map(|item_info| item_info.item)
246            .collect();
247
248        Some((first_tick_id, items_to_take))
249    }
250
251    #[must_use]
252    pub fn front_tick_id(&self) -> Option<TickId> {
253        self.items.front().map(|item_info| item_info.tick_id)
254    }
255
256    #[must_use]
257    pub const fn expected_write_tick_id(&self) -> TickId {
258        self.expected_write_id
259    }
260
261    #[must_use]
262    pub fn back_tick_id(&self) -> Option<TickId> {
263        self.items.back().map(|item_info| item_info.tick_id)
264    }
265
266    #[must_use]
267    pub fn len(&self) -> usize {
268        self.items.len()
269    }
270
271    #[must_use]
272    pub fn is_empty(&self) -> bool {
273        self.items.is_empty()
274    }
275
276    #[must_use]
277    pub fn to_vec(&self) -> Vec<T> {
278        let (front_slice, back_slice) = self.items.as_slices();
279        front_slice
280            .iter()
281            .chain(back_slice.iter())
282            .map(|item_info| item_info.item.clone())
283            .collect()
284    }
285
286    #[must_use]
287    pub const fn iter_index(&self, start_index: usize) -> FromIndexIterator<T> {
288        FromIndexIterator::new(&self.items, start_index)
289    }
290}