Skip to content

Commit

Permalink
Latest circular_queue from CoopTask work - ghostl.h C++ STL substitut…
Browse files Browse the repository at this point in the history
…e, building for AVR
  • Loading branch information
dok-net committed Sep 15, 2019
1 parent 35d9c14 commit 776d49b
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 15 deletions.
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "EspSoftwareSerial",
"version": "5.2.9",
"version": "5.3.0",
"keywords": [
"serial", "io", "softwareserial"
],
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=EspSoftwareSerial
version=5.2.9
version=5.3.0
author=Peter Lerup, Dirk Kaar
maintainer=Peter Lerup <[email protected]>
sentence=Implementation of the Arduino software serial for ESP8266/ESP32.
Expand Down
90 changes: 78 additions & 12 deletions src/circular_queue/circular_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#ifdef ARDUINO
#include <Arduino.h>
#endif

#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
#include <atomic>
#include <memory>
#include <algorithm>
#include <functional>
using std::min;
#else
#include "ghostl.h"
#endif

#if !defined(ESP32) && !defined(ESP8266)
#define ICACHE_RAM_ATTR
#define IRAM_ATTR
#endif

using std::min;

/*!
@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(),
Expand Down Expand Up @@ -123,19 +127,35 @@ class circular_queue
}

/*!
@brief Peek at the next element pop returns without removing it from the queue.
@return An rvalue copy of the next element that can be popped, or a default
value of type T if the queue is empty.
@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_acquire);
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);
if (inPos == outPos) return defaultValue;
else return m_buffer[outPos];
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
Expand All @@ -153,13 +173,15 @@ class circular_queue
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.
Expand All @@ -168,31 +190,46 @@ class circular_queue
*/
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 std::function<void(T&&)>& fun);
#else
void for_each(std::function<void(T&&)> 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 std::function<bool(T&)>& fun);
#else
bool for_each_rev_requeue(std::function<bool(T&)> fun);
#endif

protected:
const T defaultValue = {};
unsigned m_bufSize;
std::unique_ptr<T[] > m_buffer;
#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;
};
Expand All @@ -212,6 +249,21 @@ bool circular_queue<T>::capacity(const size_t cap)
return true;
}

template< typename T >
bool IRAM_ATTR circular_queue<T>::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 >
bool IRAM_ATTR circular_queue<T>::push(T&& val)
{
Expand All @@ -231,6 +283,7 @@ bool IRAM_ATTR circular_queue<T>::push(T&& val)
return true;
}

#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
template< typename T >
size_t circular_queue<T>::push_n(const T* buffer, size_t size)
{
Expand All @@ -256,6 +309,7 @@ size_t circular_queue<T>::push_n(const T* buffer, size_t size)
m_inPos.store(next, std::memory_order_release);
return blockSize + size;
}
#endif

template< typename T >
T circular_queue<T>::pop()
Expand All @@ -273,6 +327,7 @@ T circular_queue<T>::pop()
return val;
}

#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
template< typename T >
size_t circular_queue<T>::pop_n(T* buffer, size_t size) {
size_t avail = size = min(size, available());
Expand All @@ -282,18 +337,25 @@ size_t circular_queue<T>::pop_n(T* buffer, size_t size) {

std::atomic_thread_fence(std::memory_order_acquire);

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);
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 >
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
void circular_queue<T>::for_each(const std::function<void(T&&)>& fun)
#else
void circular_queue<T>::for_each(std::function<void(T&&)> fun)
#endif
{
auto outPos = m_outPos.load(std::memory_order_acquire);
const auto inPos = m_inPos.load(std::memory_order_relaxed);
Expand All @@ -308,7 +370,11 @@ void circular_queue<T>::for_each(const std::function<void(T&&)>& fun)
}

template< typename T >
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
bool circular_queue<T>::for_each_rev_requeue(const std::function<bool(T&)>& fun)
#else
bool circular_queue<T>::for_each_rev_requeue(std::function<bool(T&)> fun)
#endif
{
auto inPos0 = circular_queue<T>::m_inPos.load(std::memory_order_acquire);
auto outPos = circular_queue<T>::m_outPos.load(std::memory_order_relaxed);
Expand Down
10 changes: 9 additions & 1 deletion src/circular_queue/circular_queue_mp.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ class circular_queue_mp : protected circular_queue<T>
return circular_queue<T>::capacity(cap);
}

bool IRAM_ATTR push() = delete;

/*!
@brief Move the rvalue parameter into the queue, guarded
for multiple concurrent producers.
@return true if the queue accepted the value, false if the queue
was full.
*/
bool IRAM_ATTR push(T&& val)
{
#ifdef ESP8266
Expand All @@ -82,7 +90,7 @@ class circular_queue_mp : protected circular_queue<T>
}

/*!
@brief Move the rvalue parameter into the queue, guarded
@brief Push a copy of the parameter into the queue, guarded
for multiple concurrent producers.
@return true if the queue accepted the value, false if the queue
was full.
Expand Down
67 changes: 67 additions & 0 deletions src/circular_queue/ghostl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell
that allows building some Arduino ESP8266/ESP32
libraries on Aruduino AVR.
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 __ghostl_h
#define __ghostl_h

#include <util/atomic.h>

namespace std
{
template< typename T > class unique_ptr
{
public:
using pointer = T *;
unique_ptr() noexcept : ptr(nullptr) {}
unique_ptr(pointer p) : ptr(p) {}
pointer operator->() const noexcept { return ptr; }
T& operator[](size_t i) const { return ptr[i]; }
void reset(pointer p = pointer()) noexcept
{
delete ptr;
ptr = p;
}
T& operator*() const { return *ptr; }
private:
pointer ptr;
};

typedef enum memory_order {
memory_order_relaxed,
memory_order_acquire,
memory_order_release,
memory_order_seq_cst
} memory_order;
template< typename T > class atomic {
private:
T value;
public:
atomic() {}
atomic(T desired) { value = desired; }
void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; }
T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; }
};
inline void atomic_thread_fence(std::memory_order order) noexcept {}
template< typename T > T&& move(T& t) noexcept { return static_cast<T&&>(t); }
template< typename T > using function = T *;
}

#endif // __ghostl_h

0 comments on commit 776d49b

Please sign in to comment.