blob: 8fb5e5c63ed0b23fee8f96aae6acfc399aabaa20 [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; }
99 private:
100 struct Datum {
101 // A list element with data and the indices of the next highest/lowest
102 // priority elements.
103 Data data;
104 // Values of buffer_size indicate that we are at the beginning or end of
105 // the queue.
106 size_t lower_idx = buffer_size;
107 size_t upper_idx = buffer_size;
108 };
109
110 // Insert an element above lower_idx and below upper_idx.
111 iterator InsertInList(const Data &data, size_t lower_idx, size_t upper_idx) {
112 // For inserting new elements, when we are initially filling the queue we
113 // will increment upwards in the array; once full, we just evict the
114 // lowest priority element.
115 size_t insertion_idx = size();
116 if (full()) {
117 if (upper_idx == bottom_) {
118 // this item is lower priority than everyone else, don't insert it.
119 return end();
120 }
121 // Eject the lowest priority element.
122 insertion_idx = bottom_;
123 if (lower_idx == insertion_idx) {
124 lower_idx = buffer_size;
125 }
126 --size_;
127 bottom_ = list_[bottom_].upper_idx;
128 list_[bottom_].lower_idx = buffer_size;
129 }
130 if (upper_idx != buffer_size) {
131 list_[upper_idx].lower_idx = insertion_idx;
132 }
133 if (lower_idx != buffer_size) {
134 list_[lower_idx].upper_idx = insertion_idx;
135 }
136 if (bottom_ == upper_idx) {
137 bottom_ = insertion_idx;
138 }
139 if (top_ == lower_idx) {
140 top_ = insertion_idx;
141 }
142 list_[insertion_idx].data = data;
143 list_[insertion_idx].upper_idx = upper_idx;
144 list_[insertion_idx].lower_idx = lower_idx;
145 ++size_;
146 return iterator(this, insertion_idx);
147 }
148 ::std::array<Datum, buffer_size> list_;
149 // Index of the bottom and top of the queue.
150 size_t bottom_ = buffer_size, top_ = buffer_size;
151 // Number of elements currently in the queue.
152 size_t size_ = 0;
153 Compare cmp_;
154};
155} // namespace aos
156
157#endif // AOS_CONTAINERS_PRIORITY_QUEUE_H_