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