xref: /aosp_15_r20/external/angle/src/common/FixedQueue.h (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker //
2*8975f5c5SAndroid Build Coastguard Worker // Copyright 2023 The ANGLE Project Authors. All rights reserved.
3*8975f5c5SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
4*8975f5c5SAndroid Build Coastguard Worker // found in the LICENSE file.
5*8975f5c5SAndroid Build Coastguard Worker //
6*8975f5c5SAndroid Build Coastguard Worker // FixedQueue.h:
7*8975f5c5SAndroid Build Coastguard Worker //   An array based fifo queue class that supports concurrent push and pop.
8*8975f5c5SAndroid Build Coastguard Worker //
9*8975f5c5SAndroid Build Coastguard Worker 
10*8975f5c5SAndroid Build Coastguard Worker #ifndef COMMON_FIXEDQUEUE_H_
11*8975f5c5SAndroid Build Coastguard Worker #define COMMON_FIXEDQUEUE_H_
12*8975f5c5SAndroid Build Coastguard Worker 
13*8975f5c5SAndroid Build Coastguard Worker #include "common/debug.h"
14*8975f5c5SAndroid Build Coastguard Worker 
15*8975f5c5SAndroid Build Coastguard Worker #include <algorithm>
16*8975f5c5SAndroid Build Coastguard Worker #include <array>
17*8975f5c5SAndroid Build Coastguard Worker #include <atomic>
18*8975f5c5SAndroid Build Coastguard Worker 
19*8975f5c5SAndroid Build Coastguard Worker namespace angle
20*8975f5c5SAndroid Build Coastguard Worker {
21*8975f5c5SAndroid Build Coastguard Worker // class FixedQueue: An vector based fifo queue class that supports concurrent push and
22*8975f5c5SAndroid Build Coastguard Worker // pop. Caller must ensure queue is not empty before pop and not full before push. This class
23*8975f5c5SAndroid Build Coastguard Worker // supports concurrent push and pop from different threads, but only with single producer single
24*8975f5c5SAndroid Build Coastguard Worker // consumer usage. If caller want to push from two different threads, proper mutex must be used to
25*8975f5c5SAndroid Build Coastguard Worker // ensure the access is serialized. You can also call updateCapacity to adjust the storage size, but
26*8975f5c5SAndroid Build Coastguard Worker // caller must take proper mutex lock to ensure no one is accessing the storage. In a typical usage
27*8975f5c5SAndroid Build Coastguard Worker // case is that you have two mutex lock, enqueueLock and dequeueLock. You use enqueueLock to push
28*8975f5c5SAndroid Build Coastguard Worker // and use dequeueLock to pop. You dont need the lock for checking size (i.e, call empty/full). You
29*8975f5c5SAndroid Build Coastguard Worker // take both lock in a given order to call updateCapacity. See unit test
30*8975f5c5SAndroid Build Coastguard Worker // FixedQueue.ConcurrentPushPopWithResize for example.
31*8975f5c5SAndroid Build Coastguard Worker template <class T>
32*8975f5c5SAndroid Build Coastguard Worker class FixedQueue final : angle::NonCopyable
33*8975f5c5SAndroid Build Coastguard Worker {
34*8975f5c5SAndroid Build Coastguard Worker   public:
35*8975f5c5SAndroid Build Coastguard Worker     using Storage         = std::vector<T>;
36*8975f5c5SAndroid Build Coastguard Worker     using value_type      = typename Storage::value_type;
37*8975f5c5SAndroid Build Coastguard Worker     using size_type       = typename Storage::size_type;
38*8975f5c5SAndroid Build Coastguard Worker     using reference       = typename Storage::reference;
39*8975f5c5SAndroid Build Coastguard Worker     using const_reference = typename Storage::const_reference;
40*8975f5c5SAndroid Build Coastguard Worker 
41*8975f5c5SAndroid Build Coastguard Worker     FixedQueue(size_t capacity);
42*8975f5c5SAndroid Build Coastguard Worker     ~FixedQueue();
43*8975f5c5SAndroid Build Coastguard Worker 
44*8975f5c5SAndroid Build Coastguard Worker     size_type size() const;
45*8975f5c5SAndroid Build Coastguard Worker     bool empty() const;
46*8975f5c5SAndroid Build Coastguard Worker     bool full() const;
47*8975f5c5SAndroid Build Coastguard Worker 
48*8975f5c5SAndroid Build Coastguard Worker     size_type capacity() const;
49*8975f5c5SAndroid Build Coastguard Worker     // Caller must ensure no one is accessing the data while update storage. This should happen
50*8975f5c5SAndroid Build Coastguard Worker     // infrequently since all data will be copied between old storage and new storage.
51*8975f5c5SAndroid Build Coastguard Worker     void updateCapacity(size_t newCapacity);
52*8975f5c5SAndroid Build Coastguard Worker 
53*8975f5c5SAndroid Build Coastguard Worker     reference front();
54*8975f5c5SAndroid Build Coastguard Worker     const_reference front() const;
55*8975f5c5SAndroid Build Coastguard Worker 
56*8975f5c5SAndroid Build Coastguard Worker     void push(const value_type &value);
57*8975f5c5SAndroid Build Coastguard Worker     void push(value_type &&value);
58*8975f5c5SAndroid Build Coastguard Worker 
59*8975f5c5SAndroid Build Coastguard Worker     reference back();
60*8975f5c5SAndroid Build Coastguard Worker     const_reference back() const;
61*8975f5c5SAndroid Build Coastguard Worker 
62*8975f5c5SAndroid Build Coastguard Worker     void pop();
63*8975f5c5SAndroid Build Coastguard Worker     void clear();
64*8975f5c5SAndroid Build Coastguard Worker 
65*8975f5c5SAndroid Build Coastguard Worker   private:
66*8975f5c5SAndroid Build Coastguard Worker     Storage mData;
67*8975f5c5SAndroid Build Coastguard Worker     // The front and back indices are virtual indices (think about queue size is infinite). They
68*8975f5c5SAndroid Build Coastguard Worker     // will never wrap around when hit N. The wrap around occur when element is referenced. Virtual
69*8975f5c5SAndroid Build Coastguard Worker     // index for current head
70*8975f5c5SAndroid Build Coastguard Worker     size_type mFrontIndex;
71*8975f5c5SAndroid Build Coastguard Worker     // Virtual index for next write.
72*8975f5c5SAndroid Build Coastguard Worker     size_type mEndIndex;
73*8975f5c5SAndroid Build Coastguard Worker     // Atomic so that we can support concurrent push and pop.
74*8975f5c5SAndroid Build Coastguard Worker     std::atomic<size_type> mSize;
75*8975f5c5SAndroid Build Coastguard Worker     size_type mMaxSize;
76*8975f5c5SAndroid Build Coastguard Worker };
77*8975f5c5SAndroid Build Coastguard Worker 
78*8975f5c5SAndroid Build Coastguard Worker template <class T>
FixedQueue(size_t capacity)79*8975f5c5SAndroid Build Coastguard Worker FixedQueue<T>::FixedQueue(size_t capacity)
80*8975f5c5SAndroid Build Coastguard Worker     : mFrontIndex(0), mEndIndex(0), mSize(0), mMaxSize(capacity)
81*8975f5c5SAndroid Build Coastguard Worker {
82*8975f5c5SAndroid Build Coastguard Worker     mData.resize(mMaxSize);
83*8975f5c5SAndroid Build Coastguard Worker }
84*8975f5c5SAndroid Build Coastguard Worker 
85*8975f5c5SAndroid Build Coastguard Worker template <class T>
~FixedQueue()86*8975f5c5SAndroid Build Coastguard Worker FixedQueue<T>::~FixedQueue()
87*8975f5c5SAndroid Build Coastguard Worker {
88*8975f5c5SAndroid Build Coastguard Worker     mData.clear();
89*8975f5c5SAndroid Build Coastguard Worker }
90*8975f5c5SAndroid Build Coastguard Worker 
91*8975f5c5SAndroid Build Coastguard Worker template <class T>
size()92*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE typename FixedQueue<T>::size_type FixedQueue<T>::size() const
93*8975f5c5SAndroid Build Coastguard Worker {
94*8975f5c5SAndroid Build Coastguard Worker     ASSERT(mSize <= mMaxSize);
95*8975f5c5SAndroid Build Coastguard Worker     return mSize;
96*8975f5c5SAndroid Build Coastguard Worker }
97*8975f5c5SAndroid Build Coastguard Worker 
98*8975f5c5SAndroid Build Coastguard Worker template <class T>
empty()99*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE bool FixedQueue<T>::empty() const
100*8975f5c5SAndroid Build Coastguard Worker {
101*8975f5c5SAndroid Build Coastguard Worker     return size() == 0;
102*8975f5c5SAndroid Build Coastguard Worker }
103*8975f5c5SAndroid Build Coastguard Worker 
104*8975f5c5SAndroid Build Coastguard Worker template <class T>
full()105*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE bool FixedQueue<T>::full() const
106*8975f5c5SAndroid Build Coastguard Worker {
107*8975f5c5SAndroid Build Coastguard Worker     return size() == mMaxSize;
108*8975f5c5SAndroid Build Coastguard Worker }
109*8975f5c5SAndroid Build Coastguard Worker 
110*8975f5c5SAndroid Build Coastguard Worker template <class T>
capacity()111*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE typename FixedQueue<T>::size_type FixedQueue<T>::capacity() const
112*8975f5c5SAndroid Build Coastguard Worker {
113*8975f5c5SAndroid Build Coastguard Worker     return mMaxSize;
114*8975f5c5SAndroid Build Coastguard Worker }
115*8975f5c5SAndroid Build Coastguard Worker 
116*8975f5c5SAndroid Build Coastguard Worker template <class T>
updateCapacity(size_t newCapacity)117*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE void FixedQueue<T>::updateCapacity(size_t newCapacity)
118*8975f5c5SAndroid Build Coastguard Worker {
119*8975f5c5SAndroid Build Coastguard Worker     ASSERT(newCapacity >= size());
120*8975f5c5SAndroid Build Coastguard Worker     Storage newData(newCapacity);
121*8975f5c5SAndroid Build Coastguard Worker     for (size_type i = mFrontIndex; i < mEndIndex; i++)
122*8975f5c5SAndroid Build Coastguard Worker     {
123*8975f5c5SAndroid Build Coastguard Worker         newData[i % newCapacity] = std::move(mData[i % mMaxSize]);
124*8975f5c5SAndroid Build Coastguard Worker     }
125*8975f5c5SAndroid Build Coastguard Worker     mData.clear();
126*8975f5c5SAndroid Build Coastguard Worker     std::swap(newData, mData);
127*8975f5c5SAndroid Build Coastguard Worker     mMaxSize = newCapacity;
128*8975f5c5SAndroid Build Coastguard Worker     ASSERT(mData.size() == mMaxSize);
129*8975f5c5SAndroid Build Coastguard Worker }
130*8975f5c5SAndroid Build Coastguard Worker 
131*8975f5c5SAndroid Build Coastguard Worker template <class T>
front()132*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE typename FixedQueue<T>::reference FixedQueue<T>::front()
133*8975f5c5SAndroid Build Coastguard Worker {
134*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!empty());
135*8975f5c5SAndroid Build Coastguard Worker     return mData[mFrontIndex % mMaxSize];
136*8975f5c5SAndroid Build Coastguard Worker }
137*8975f5c5SAndroid Build Coastguard Worker 
138*8975f5c5SAndroid Build Coastguard Worker template <class T>
front()139*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE typename FixedQueue<T>::const_reference FixedQueue<T>::front() const
140*8975f5c5SAndroid Build Coastguard Worker {
141*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!empty());
142*8975f5c5SAndroid Build Coastguard Worker     return mData[mFrontIndex % mMaxSize];
143*8975f5c5SAndroid Build Coastguard Worker }
144*8975f5c5SAndroid Build Coastguard Worker 
145*8975f5c5SAndroid Build Coastguard Worker template <class T>
push(const value_type & value)146*8975f5c5SAndroid Build Coastguard Worker void FixedQueue<T>::push(const value_type &value)
147*8975f5c5SAndroid Build Coastguard Worker {
148*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!full());
149*8975f5c5SAndroid Build Coastguard Worker     mData[mEndIndex % mMaxSize] = value;
150*8975f5c5SAndroid Build Coastguard Worker     mEndIndex++;
151*8975f5c5SAndroid Build Coastguard Worker     // We must increment size last, after we wrote data. That way if another thread is doing
152*8975f5c5SAndroid Build Coastguard Worker     // `if(!dq.empty()){ s = dq.front(); }`, it will only see not empty until element is fully
153*8975f5c5SAndroid Build Coastguard Worker     // pushed.
154*8975f5c5SAndroid Build Coastguard Worker     mSize++;
155*8975f5c5SAndroid Build Coastguard Worker }
156*8975f5c5SAndroid Build Coastguard Worker 
157*8975f5c5SAndroid Build Coastguard Worker template <class T>
push(value_type && value)158*8975f5c5SAndroid Build Coastguard Worker void FixedQueue<T>::push(value_type &&value)
159*8975f5c5SAndroid Build Coastguard Worker {
160*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!full());
161*8975f5c5SAndroid Build Coastguard Worker     mData[mEndIndex % mMaxSize] = std::move(value);
162*8975f5c5SAndroid Build Coastguard Worker     mEndIndex++;
163*8975f5c5SAndroid Build Coastguard Worker     // We must increment size last, after we wrote data. That way if another thread is doing
164*8975f5c5SAndroid Build Coastguard Worker     // `if(!dq.empty()){ s = dq.front(); }`, it will only see not empty until element is fully
165*8975f5c5SAndroid Build Coastguard Worker     // pushed.
166*8975f5c5SAndroid Build Coastguard Worker     mSize++;
167*8975f5c5SAndroid Build Coastguard Worker }
168*8975f5c5SAndroid Build Coastguard Worker 
169*8975f5c5SAndroid Build Coastguard Worker template <class T>
back()170*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE typename FixedQueue<T>::reference FixedQueue<T>::back()
171*8975f5c5SAndroid Build Coastguard Worker {
172*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!empty());
173*8975f5c5SAndroid Build Coastguard Worker     return mData[(mEndIndex + (mMaxSize - 1)) % mMaxSize];
174*8975f5c5SAndroid Build Coastguard Worker }
175*8975f5c5SAndroid Build Coastguard Worker 
176*8975f5c5SAndroid Build Coastguard Worker template <class T>
back()177*8975f5c5SAndroid Build Coastguard Worker ANGLE_INLINE typename FixedQueue<T>::const_reference FixedQueue<T>::back() const
178*8975f5c5SAndroid Build Coastguard Worker {
179*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!empty());
180*8975f5c5SAndroid Build Coastguard Worker     return mData[(mEndIndex + (mMaxSize - 1)) % mMaxSize];
181*8975f5c5SAndroid Build Coastguard Worker }
182*8975f5c5SAndroid Build Coastguard Worker 
183*8975f5c5SAndroid Build Coastguard Worker template <class T>
pop()184*8975f5c5SAndroid Build Coastguard Worker void FixedQueue<T>::pop()
185*8975f5c5SAndroid Build Coastguard Worker {
186*8975f5c5SAndroid Build Coastguard Worker     ASSERT(!empty());
187*8975f5c5SAndroid Build Coastguard Worker     mData[mFrontIndex % mMaxSize] = value_type();
188*8975f5c5SAndroid Build Coastguard Worker     mFrontIndex++;
189*8975f5c5SAndroid Build Coastguard Worker     // We must decrement size last, after we wrote data. That way if another thread is doing
190*8975f5c5SAndroid Build Coastguard Worker     // `if(!dq.full()){ dq.push; }`, it will only see not full until element is fully popped.
191*8975f5c5SAndroid Build Coastguard Worker     mSize--;
192*8975f5c5SAndroid Build Coastguard Worker }
193*8975f5c5SAndroid Build Coastguard Worker 
194*8975f5c5SAndroid Build Coastguard Worker template <class T>
clear()195*8975f5c5SAndroid Build Coastguard Worker void FixedQueue<T>::clear()
196*8975f5c5SAndroid Build Coastguard Worker {
197*8975f5c5SAndroid Build Coastguard Worker     // Size will change in the "pop()" and also by "push()" calls from other thread.
198*8975f5c5SAndroid Build Coastguard Worker     const size_type localSize = size();
199*8975f5c5SAndroid Build Coastguard Worker     for (size_type i = 0; i < localSize; i++)
200*8975f5c5SAndroid Build Coastguard Worker     {
201*8975f5c5SAndroid Build Coastguard Worker         pop();
202*8975f5c5SAndroid Build Coastguard Worker     }
203*8975f5c5SAndroid Build Coastguard Worker }
204*8975f5c5SAndroid Build Coastguard Worker }  // namespace angle
205*8975f5c5SAndroid Build Coastguard Worker 
206*8975f5c5SAndroid Build Coastguard Worker #endif  // COMMON_FIXEDQUEUE_H_
207