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