blob: b092219a40294381d2fbf351a451b7eef1fa9bbc [file] [log] [blame]
James Kuszmaul97f750d2019-01-20 20:08:03 -08001#ifndef AOS_CONTAINERS_PRIORITY_QUEUE_H_
2#define AOS_CONTAINERS_PRIORITY_QUEUE_H_
3
4#include <array>
5#include <iterator>
6
7namespace aos {
8
9// Creates a priority queue which will never exceed a particular size.
10// Data: The type of the data to store.
11// buffer_size: The max number of Data to store.
12// Compare: A comparison function. If std::less were used here, then the
13// smallest element would be discarded first, see
14// https://en.cppreference.com/w/cpp/named_req/Compare
15// The lowest priority elements will be discarded first to maintain buffer_size.
16// Note that as a "queue" this is a bit incomplete because there is no mechanism
17// to pop from the queue.
18template <typename Data, size_t buffer_size, typename Compare>
19class PriorityQueue {
20 public:
21 class iterator {
22 public:
23 explicit iterator(PriorityQueue *queue, size_t idx)
24 : queue_(queue), idx_(idx) {}
25 iterator &operator++() {
26 idx_ = queue_->next_idx(idx_);
27 return *this;
28 }
29 iterator operator++(int) {
30 iterator retval = *this;
31 ++(*this);
32 return retval;
33 }
34 iterator &operator--() {
35 idx_ = queue_->prev_idx(idx_);
36 return *this;
37 }
38 iterator operator--(int) {
39 iterator retval = *this;
40 --(*this);
41 return retval;
42 }
43 bool operator==(iterator other) const {
44 return queue_ == other.queue_ && idx_ == other.idx_;
45 }
46 bool operator!=(iterator other) const { return !(*this == other); }
47 Data &operator*() { return queue_->get(idx_); }
48 Data *operator->() { return &queue_->get(idx_); }
49
50 private:
51 PriorityQueue *queue_;
52 size_t idx_;
53 };
54
55 constexpr PriorityQueue() {}
56
57 // Inserts data into the queue and returns an iterator for the inserted
58 // element. If the queue was already full, then the lowest priority element
59 // will be discarded. If data is lower priority than all the current elements
60 // and the queue is full, then data is ignored and end() is returned.
61 // PushFromBottom starts the search from the bottom of the queue.
62 // TODO(james): If performance becomes an issue, improve search.
63 iterator PushFromBottom(const Data &data) {
64 size_t lower_idx = buffer_size;
65 size_t upper_idx = bottom_;
66 // Find our spot in the queue:
67 while (upper_idx != buffer_size && !cmp_(data, list_[upper_idx].data)) {
68 lower_idx = upper_idx;
69 upper_idx = list_[upper_idx].upper_idx;
70 if (upper_idx == buffer_size) {
71 break;
72 }
73 }
74 return InsertInList(data, lower_idx, upper_idx);
75 }
76
77 size_t size() const { return size_; }
78 bool empty() const { return size_ == 0; }
79 bool full() const { return size_ == buffer_size; }
80
James Kuszmaul4b04eaf2019-02-10 17:13:25 -080081 // Removes all the elements from the queue:
82 void clear() {
83 size_ = 0;
84 bottom_ = buffer_size;
85 top_ = buffer_size;
86 }
87
James Kuszmaul97f750d2019-01-20 20:08:03 -080088 Data &top() { return list_[top_].data; }
James Kuszmaul4b04eaf2019-02-10 17:13:25 -080089 const Data &top() const { return list_[top_].data; }
James Kuszmaul97f750d2019-01-20 20:08:03 -080090 Data &get(size_t idx) { return list_[idx].data; }
James Kuszmaul4b04eaf2019-02-10 17:13:25 -080091 const Data &get(size_t idx) const { return list_[idx].data; }
James Kuszmaul97f750d2019-01-20 20:08:03 -080092 iterator begin() { return iterator(this, bottom_); }
93 iterator end() { return iterator(this, buffer_size); }
94
95 // Gets the index of the next (higher valued) element in the queue.
96 size_t next_idx(size_t idx) const { return list_[idx].upper_idx; }
97 // Gets the index of the previous (lower valued) element in the queue.
98 size_t prev_idx(size_t idx) const { return list_[idx].lower_idx; }
Austin Schuh60e77942022-05-16 17:48:24 -070099
James Kuszmaul97f750d2019-01-20 20:08:03 -0800100 private:
101 struct Datum {
102 // A list element with data and the indices of the next highest/lowest
103 // priority elements.
104 Data data;
105 // Values of buffer_size indicate that we are at the beginning or end of
106 // the queue.
107 size_t lower_idx = buffer_size;
108 size_t upper_idx = buffer_size;
109 };
110
111 // Insert an element above lower_idx and below upper_idx.
112 iterator InsertInList(const Data &data, size_t lower_idx, size_t upper_idx) {
113 // For inserting new elements, when we are initially filling the queue we
114 // will increment upwards in the array; once full, we just evict the
115 // lowest priority element.
116 size_t insertion_idx = size();
117 if (full()) {
118 if (upper_idx == bottom_) {
119 // this item is lower priority than everyone else, don't insert it.
120 return end();
121 }
122 // Eject the lowest priority element.
123 insertion_idx = bottom_;
124 if (lower_idx == insertion_idx) {
125 lower_idx = buffer_size;
126 }
127 --size_;
128 bottom_ = list_[bottom_].upper_idx;
129 list_[bottom_].lower_idx = buffer_size;
130 }
131 if (upper_idx != buffer_size) {
132 list_[upper_idx].lower_idx = insertion_idx;
133 }
134 if (lower_idx != buffer_size) {
135 list_[lower_idx].upper_idx = insertion_idx;
136 }
137 if (bottom_ == upper_idx) {
138 bottom_ = insertion_idx;
139 }
140 if (top_ == lower_idx) {
141 top_ = insertion_idx;
142 }
143 list_[insertion_idx].data = data;
144 list_[insertion_idx].upper_idx = upper_idx;
145 list_[insertion_idx].lower_idx = lower_idx;
146 ++size_;
147 return iterator(this, insertion_idx);
148 }
149 ::std::array<Datum, buffer_size> list_;
150 // Index of the bottom and top of the queue.
151 size_t bottom_ = buffer_size, top_ = buffer_size;
152 // Number of elements currently in the queue.
153 size_t size_ = 0;
154 Compare cmp_;
155};
156} // namespace aos
157
158#endif // AOS_CONTAINERS_PRIORITY_QUEUE_H_