Delete circular_queue.h
This commit is contained in:
		
							parent
							
								
									3b3a8b58fe
								
							
						
					
					
						commit
						e4d645cf41
					
				| @ -1,399 +0,0 @@ | |||||||
| /*
 |  | ||||||
| circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial. |  | ||||||
| Copyright (c) 2019 Dirk O. Kaar. All rights reserved. |  | ||||||
| 
 |  | ||||||
| This library is free software; you can redistribute it and/or |  | ||||||
| modify it under the terms of the GNU Lesser General Public |  | ||||||
| License as published by the Free Software Foundation; either |  | ||||||
| version 2.1 of the License, or (at your option) any later version. |  | ||||||
| 
 |  | ||||||
| This library is distributed in the hope that it will be useful, |  | ||||||
| but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU |  | ||||||
| Lesser General Public License for more details. |  | ||||||
| 
 |  | ||||||
| You should have received a copy of the GNU Lesser General Public |  | ||||||
| License along with this library; if not, write to the Free Software |  | ||||||
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| #ifndef __circular_queue_h |  | ||||||
| #define __circular_queue_h |  | ||||||
| 
 |  | ||||||
| #ifdef ARDUINO |  | ||||||
| #include <Arduino.h> |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
| #include <atomic> |  | ||||||
| #include <memory> |  | ||||||
| #include <algorithm> |  | ||||||
| #include "Delegate.h" |  | ||||||
| using std::min; |  | ||||||
| #else |  | ||||||
| #include "ghostl.h" |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| #if !defined(ESP32) && !defined(ESP8266) |  | ||||||
| #define ICACHE_RAM_ATTR |  | ||||||
| #define IRAM_ATTR |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| /*!
 |  | ||||||
|     @brief	Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO). |  | ||||||
|             This implementation is lock-free between producer and consumer for the available(), peek(), |  | ||||||
|             pop(), and push() type functions. |  | ||||||
| */ |  | ||||||
| template< typename T, typename ForEachArg = void > |  | ||||||
| class circular_queue |  | ||||||
| { |  | ||||||
| public: |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Constructs a valid, but zero-capacity dummy queue. |  | ||||||
|     */ |  | ||||||
|     circular_queue() : m_bufSize(1) |  | ||||||
|     { |  | ||||||
|         m_inPos.store(0); |  | ||||||
|         m_outPos.store(0); |  | ||||||
|     } |  | ||||||
|     /*!
 |  | ||||||
|         @brief  Constructs a queue of the given maximum capacity. |  | ||||||
|     */ |  | ||||||
|     circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize]) |  | ||||||
|     { |  | ||||||
|         m_inPos.store(0); |  | ||||||
|         m_outPos.store(0); |  | ||||||
|     } |  | ||||||
|     circular_queue(circular_queue&& cq) : |  | ||||||
|         m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load()) |  | ||||||
|     {} |  | ||||||
|     ~circular_queue() |  | ||||||
|     { |  | ||||||
|         m_buffer.reset(); |  | ||||||
|     } |  | ||||||
|     circular_queue(const circular_queue&) = delete; |  | ||||||
|     circular_queue& operator=(circular_queue&& cq) |  | ||||||
|     { |  | ||||||
|         m_bufSize = cq.m_bufSize; |  | ||||||
|         m_buffer = cq.m_buffer; |  | ||||||
|         m_inPos.store(cq.m_inPos.load()); |  | ||||||
|         m_outPos.store(cq.m_outPos.load()); |  | ||||||
|     } |  | ||||||
|     circular_queue& operator=(const circular_queue&) = delete; |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Get the numer of elements the queue can hold at most. |  | ||||||
|     */ |  | ||||||
|     size_t capacity() const |  | ||||||
|     { |  | ||||||
|         return m_bufSize - 1; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Resize the queue. The available elements in the queue are preserved. |  | ||||||
|                 This is not lock-free and concurrent producer or consumer access |  | ||||||
|                 will lead to corruption. |  | ||||||
|         @return True if the new capacity could accommodate the present elements in |  | ||||||
|                 the queue, otherwise nothing is done and false is returned. |  | ||||||
|     */ |  | ||||||
|     bool capacity(const size_t cap); |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Discard all data in the queue. |  | ||||||
|     */ |  | ||||||
|     void flush() |  | ||||||
|     { |  | ||||||
|         m_outPos.store(m_inPos.load()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Get a snapshot number of elements that can be retrieved by pop. |  | ||||||
|     */ |  | ||||||
|     size_t available() const |  | ||||||
|     { |  | ||||||
|         int avail = static_cast<int>(m_inPos.load() - m_outPos.load()); |  | ||||||
|         if (avail < 0) avail += m_bufSize; |  | ||||||
|         return avail; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Get the remaining free elementes for pushing. |  | ||||||
|     */ |  | ||||||
|     size_t available_for_push() const |  | ||||||
|     { |  | ||||||
|         int avail = static_cast<int>(m_outPos.load() - m_inPos.load()) - 1; |  | ||||||
|         if (avail < 0) avail += m_bufSize; |  | ||||||
|         return avail; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Peek at the next element pop will return without removing it from the queue. |  | ||||||
|         @return An rvalue copy of the next element that can be popped. If the queue is empty, |  | ||||||
|                 return an rvalue copy of the element that is pending the next push. |  | ||||||
|     */ |  | ||||||
|     T peek() const |  | ||||||
|     { |  | ||||||
|         const auto outPos = m_outPos.load(std::memory_order_relaxed); |  | ||||||
|         std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
|         return m_buffer[outPos]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Peek at the next pending input value. |  | ||||||
|         @return A reference to the next element that can be pushed. |  | ||||||
|     */ |  | ||||||
|     T& IRAM_ATTR pushpeek() |  | ||||||
|     { |  | ||||||
|         const auto inPos = m_inPos.load(std::memory_order_relaxed); |  | ||||||
|         std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
|         return m_buffer[inPos]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Release the next pending input value, accessible by pushpeek(), into the queue. |  | ||||||
|         @return true if the queue accepted the value, false if the queue |  | ||||||
|                 was full. |  | ||||||
|     */ |  | ||||||
|     bool IRAM_ATTR push(); |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Move the rvalue parameter into the queue. |  | ||||||
|         @return true if the queue accepted the value, false if the queue |  | ||||||
|                 was full. |  | ||||||
|     */ |  | ||||||
|     bool IRAM_ATTR push(T&& val); |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Push a copy of the parameter into the queue. |  | ||||||
|         @return true if the queue accepted the value, false if the queue |  | ||||||
|                 was full. |  | ||||||
|     */ |  | ||||||
|     bool IRAM_ATTR push(const T& val) |  | ||||||
|     { |  | ||||||
|         return push(T(val)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Push copies of multiple elements from a buffer into the queue, |  | ||||||
|                 in order, beginning at buffer's head. |  | ||||||
|         @return The number of elements actually copied into the queue, counted |  | ||||||
|                 from the buffer head. |  | ||||||
|     */ |  | ||||||
|     size_t push_n(const T* buffer, size_t size); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Pop the next available element from the queue. |  | ||||||
|         @return An rvalue copy of the popped element, or a default |  | ||||||
|                 value of type T if the queue is empty. |  | ||||||
|     */ |  | ||||||
|     T pop(); |  | ||||||
| 
 |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Pop multiple elements in ordered sequence from the queue to a buffer. |  | ||||||
|                 If buffer is nullptr, simply discards up to size elements from the queue. |  | ||||||
|         @return The number of elements actually popped from the queue to |  | ||||||
|                 buffer. |  | ||||||
|     */ |  | ||||||
|     size_t pop_n(T* buffer, size_t size); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	Iterate over and remove each available element from queue, |  | ||||||
|                 calling back fun with an rvalue reference of every single element. |  | ||||||
|     */ |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
|     void for_each(const Delegate<void(T&&), ForEachArg>& fun); |  | ||||||
| #else |  | ||||||
|     void for_each(Delegate<void(T&&), ForEachArg> fun); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
|     /*!
 |  | ||||||
|         @brief	In reverse order, iterate over, pop and optionally requeue each available element from the queue, |  | ||||||
|                 calling back fun with a reference of every single element. |  | ||||||
|                 Requeuing is dependent on the return boolean of the callback function. If it |  | ||||||
|                 returns true, the requeue occurs. |  | ||||||
|     */ |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
|     bool for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun); |  | ||||||
| #else |  | ||||||
|     bool for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun); |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| protected: |  | ||||||
|     const T defaultValue = {}; |  | ||||||
|     unsigned m_bufSize; |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
|     std::unique_ptr<T[]> m_buffer; |  | ||||||
| #else |  | ||||||
|     std::unique_ptr<T> m_buffer; |  | ||||||
| #endif |  | ||||||
|     std::atomic<unsigned> m_inPos; |  | ||||||
|     std::atomic<unsigned> m_outPos; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| bool circular_queue<T, ForEachArg>::capacity(const size_t cap) |  | ||||||
| { |  | ||||||
|     if (cap + 1 == m_bufSize) return true; |  | ||||||
|     else if (available() > cap) return false; |  | ||||||
|     std::unique_ptr<T[] > buffer(new T[cap + 1]); |  | ||||||
|     const auto available = pop_n(buffer, cap); |  | ||||||
|     m_buffer.reset(buffer); |  | ||||||
|     m_bufSize = cap + 1; |  | ||||||
|     std::atomic_thread_fence(std::memory_order_release); |  | ||||||
|     m_inPos.store(available, std::memory_order_relaxed); |  | ||||||
|     m_outPos.store(0, std::memory_order_release); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| bool IRAM_ATTR circular_queue<T, ForEachArg>::push() |  | ||||||
| { |  | ||||||
|     const auto inPos = m_inPos.load(std::memory_order_acquire); |  | ||||||
|     const unsigned next = (inPos + 1) % m_bufSize; |  | ||||||
|     if (next == m_outPos.load(std::memory_order_relaxed)) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
| 
 |  | ||||||
|     m_inPos.store(next, std::memory_order_release); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| bool IRAM_ATTR circular_queue<T, ForEachArg>::push(T&& val) |  | ||||||
| { |  | ||||||
|     const auto inPos = m_inPos.load(std::memory_order_acquire); |  | ||||||
|     const unsigned next = (inPos + 1) % m_bufSize; |  | ||||||
|     if (next == m_outPos.load(std::memory_order_relaxed)) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
| 
 |  | ||||||
|     m_buffer[inPos] = std::move(val); |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_release); |  | ||||||
| 
 |  | ||||||
|     m_inPos.store(next, std::memory_order_release); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| size_t circular_queue<T, ForEachArg>::push_n(const T* buffer, size_t size) |  | ||||||
| { |  | ||||||
|     const auto inPos = m_inPos.load(std::memory_order_acquire); |  | ||||||
|     const auto outPos = m_outPos.load(std::memory_order_relaxed); |  | ||||||
| 
 |  | ||||||
|     size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos; |  | ||||||
|     blockSize = min(size, blockSize); |  | ||||||
|     if (!blockSize) return 0; |  | ||||||
|     int next = (inPos + blockSize) % m_bufSize; |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
| 
 |  | ||||||
|     auto dest = m_buffer.get() + inPos; |  | ||||||
|     std::copy_n(std::make_move_iterator(buffer), blockSize, dest); |  | ||||||
|     size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0); |  | ||||||
|     next += size; |  | ||||||
|     dest = m_buffer.get(); |  | ||||||
|     std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest); |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_release); |  | ||||||
| 
 |  | ||||||
|     m_inPos.store(next, std::memory_order_release); |  | ||||||
|     return blockSize + size; |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| T circular_queue<T, ForEachArg>::pop() |  | ||||||
| { |  | ||||||
|     const auto outPos = m_outPos.load(std::memory_order_acquire); |  | ||||||
|     if (m_inPos.load(std::memory_order_relaxed) == outPos) return defaultValue; |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
| 
 |  | ||||||
|     auto val = std::move(m_buffer[outPos]); |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_release); |  | ||||||
| 
 |  | ||||||
|     m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release); |  | ||||||
|     return val; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| size_t circular_queue<T, ForEachArg>::pop_n(T* buffer, size_t size) { |  | ||||||
|     size_t avail = size = min(size, available()); |  | ||||||
|     if (!avail) return 0; |  | ||||||
|     const auto outPos = m_outPos.load(std::memory_order_acquire); |  | ||||||
|     size_t n = min(avail, static_cast<size_t>(m_bufSize - outPos)); |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
| 
 |  | ||||||
|     if (buffer) { |  | ||||||
|         buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer); |  | ||||||
|         avail -= n; |  | ||||||
|         std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::atomic_thread_fence(std::memory_order_release); |  | ||||||
| 
 |  | ||||||
|     m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release); |  | ||||||
|     return size; |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
| 
 |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
| void circular_queue<T, ForEachArg>::for_each(const Delegate<void(T&&), ForEachArg>& fun) |  | ||||||
| #else |  | ||||||
| void circular_queue<T, ForEachArg>::for_each(Delegate<void(T&&), ForEachArg> fun) |  | ||||||
| #endif |  | ||||||
| { |  | ||||||
|     auto outPos = m_outPos.load(std::memory_order_acquire); |  | ||||||
|     const auto inPos = m_inPos.load(std::memory_order_relaxed); |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
|     while (outPos != inPos) |  | ||||||
|     { |  | ||||||
|         fun(std::move(m_buffer[outPos])); |  | ||||||
|         std::atomic_thread_fence(std::memory_order_release); |  | ||||||
|         outPos = (outPos + 1) % m_bufSize; |  | ||||||
|         m_outPos.store(outPos, std::memory_order_release); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| template< typename T, typename ForEachArg > |  | ||||||
| #if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) |  | ||||||
| bool circular_queue<T, ForEachArg>::for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun) |  | ||||||
| #else |  | ||||||
| bool circular_queue<T, ForEachArg>::for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun) |  | ||||||
| #endif |  | ||||||
| { |  | ||||||
|     auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire); |  | ||||||
|     auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed); |  | ||||||
|     std::atomic_thread_fence(std::memory_order_acquire); |  | ||||||
|     if (outPos == inPos0) return false; |  | ||||||
|     auto pos = inPos0; |  | ||||||
|     auto outPos1 = inPos0; |  | ||||||
|     const auto posDecr = circular_queue<T, ForEachArg>::m_bufSize - 1; |  | ||||||
|     do { |  | ||||||
|         pos = (pos + posDecr) % circular_queue<T, ForEachArg>::m_bufSize; |  | ||||||
|         T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[pos]); |  | ||||||
|         if (fun(val)) |  | ||||||
|         { |  | ||||||
|             outPos1 = (outPos1 + posDecr) % circular_queue<T, ForEachArg>::m_bufSize; |  | ||||||
|             if (outPos1 != pos) circular_queue<T, ForEachArg>::m_buffer[outPos1] = std::move(val); |  | ||||||
|         } |  | ||||||
|     } while (pos != outPos); |  | ||||||
|     circular_queue<T, ForEachArg>::m_outPos.store(outPos1, std::memory_order_release); |  | ||||||
|     return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #endif // __circular_queue_h
 |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user