diff --git a/examples/bitpattern/bitpattern.ino b/examples/bitpattern/bitpattern.ino index 23708f5..9184358 100644 --- a/examples/bitpattern/bitpattern.ino +++ b/examples/bitpattern/bitpattern.ino @@ -41,7 +41,7 @@ void setup() { logSer.println(PSTR("\nOne Wire Half Duplex Bitpattern and Datarate Test")); swSer.begin(TESTBPS, EspSoftwareSerial::SWSERIAL_8N1, -1, D5); swSer.enableIntTx(true); - logSer.printf(PSTR("Tx from swSer to hwSer at %u\n"), swSer.baudRate()); + logSer.printf(PSTR("Tx from swSer to hwSer at %lu\n"), swSer.baudRate()); } uint8_t val = 0xff; diff --git a/examples/circular_queue_mp_test/mp_queue_test.cpp b/examples/circular_queue_mp_test/mp_queue_test.cpp deleted file mode 100644 index 6b2712a..0000000 --- a/examples/circular_queue_mp_test/mp_queue_test.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// circular_mp_test.cpp : This file contains the 'main' function. Program execution begins and ends there. -// - -#include -#include -#include -#include -#include "circular_queue/circular_queue_mp.h" - -struct qitem -{ - // producer id - int id; - // monotonic increasing value - int val = 0; -}; - -constexpr int TOTALMESSAGESTARGET = 60000000; -// reserve one thread as consumer -const auto THREADS = std::thread::hardware_concurrency() / 2 - 1; -const int MESSAGES = TOTALMESSAGESTARGET / THREADS; -circular_queue threads(THREADS); -circular_queue_mp queue(threads.capacity()* MESSAGES / 10); -std::vector checks(threads.capacity()); - -int main() -{ - using namespace std::chrono_literals; - std::cerr << "Utilizing " << THREADS << " producer threads" << std::endl; - for (int i = 0; i < threads.capacity(); ++i) - { - threads.push(std::thread([i]() { - for (int c = 0; c < MESSAGES;) - { - // simulate some load - auto start = std::chrono::system_clock::now(); - while (std::chrono::system_clock::now() - start < 1us); - if (queue.push({ i, c })) - { - ++c; - } - else - { - //std::cerr << "queue full" << std::endl; - //std::this_thread::sleep_for(10us); - } - //if (0 == c % 10000) std::this_thread::sleep_for(10us); - } - })); - } - for (int o = 0; o < threads.available() * MESSAGES; ++o) - { - auto now = std::chrono::system_clock::now(); - while (!queue.available()) - { - auto starvedFor = std::chrono::system_clock::now() - now; - if (starvedFor > 20s) std::cerr << "queue starved for > 20s" << std::endl; - //std::this_thread::sleep_for(20ms); - } - auto item = queue.pop(); - if (checks[item.id] != item.val) - { - std::cerr << "item mismatch" << std::endl; - } - checks[item.id] = item.val + 1; - if (0 == item.val % 1000) std::this_thread::sleep_for(100us); - } - while (threads.available()) - { - auto thread = threads.pop(); - thread.join(); - } - return 0; -} diff --git a/examples/coro/coro.ino b/examples/coro/coro.ino deleted file mode 100644 index 1a429b5..0000000 --- a/examples/coro/coro.ino +++ /dev/null @@ -1,371 +0,0 @@ -#include -#include -#include -#include - -#ifdef ESP8266 -#include -#include -#include -#else -#include -#endif - -#ifdef ARDUINO -#define PRINTF Serial.printf -#define PRINTLN Serial.println -#elif defined(__ZEPHYR__) -#include -#include -#include -#define PRINTF printk -#define PRINTLN(s) printk("%s\n", (s)) -#else -#define PRINTF printf -#define PRINTLN puts -#endif - -#ifdef __ZEPHYR__ -#include -namespace { - long unsigned micros() { return static_cast(k_uptime_get() * 1000UL); } - long unsigned millis() { return static_cast(k_uptime_get()); } -} -#elif !defined(ARDUINO) // __ZEPHYR__ -#include -#include -namespace { - long unsigned micros() - { - static auto start = std::chrono::steady_clock::now(); - return static_cast(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count()); - } - long unsigned millis() - { - static auto start = std::chrono::steady_clock::now(); - return static_cast(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count()); - } -} -#endif // __ZEPHYR__ - -//#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -constexpr unsigned DELAY = 10000; - -struct schedule : std::suspend_always -{ - ghostl::cancellation_token ct; - schedule(ghostl::cancellation_token _ct = {}) : ct(_ct) {} - - void await_suspend(std::coroutine_handle<> handle) - { - schedule_function([handle]() { - if (handle && !handle.done()) handle.resume(); - }); - } -}; - -struct co_delay : std::suspend_always -{ - co_delay(decltype(micros()) _delay_us, ghostl::cancellation_token _ct = {}) : delay_us(_delay_us), ct(_ct) {} - - decltype(micros()) delay_us; - ghostl::cancellation_token ct; - //Ticker ticker; - //std::coroutine_handle<> coroutine; - - void await_suspend(std::coroutine_handle<> handle) - { - // Using many Tickers at once appears to crash the ESP32... - //coroutine = handle; - //struct local { - // static auto schedule(co_delay* self) -> void { - // schedule_function([self]() { self->coroutine.resume(); }); - // } - //}; - //ticker.once_ms(delay_us / 1000, local::schedule, this); - schedule_recurrent_function_us([handle]() { - handle.resume(); return false; }, delay_us, - [this]() { return ct.is_cancellation_requested(); }); - } -}; - -ghostl::generator fibonacci_sequence(unsigned n) -{ - if (n == 0) - co_return; - - if (n > 94) - { - // throw std::runtime_error("Too big Fibonacci sequence. Elements would overflow."); - } - - co_yield 0; - - if (n == 1) - co_return; - - co_yield 1; - - if (n == 2) - co_return; - - std::uint64_t a = 0; - std::uint64_t b = 1; - - for (unsigned i = 2; i <= n; i++) - { - std::uint64_t s = a + b; - co_yield s; - a = b; - b = s; - } -} - -int main_gen() -{ - // max 93 before uint64_t overflows - auto fib = fibonacci_sequence(93); - for (int j = 1; fib; ++j) - { - auto v = fib(); - // PRINTF("fib(%u)=%llu\n", j, v); - } - - return 0; -} - -auto eventAsync(int id, ghostl::cancellation_token ct = {}) -{ - ghostl::task_completion_source tcs; - if (id % 2) { - std::stringstream hello; - hello << "Hello async (" << id << ") = " << micros() / 1000 << std::ends; - tcs.set_value(hello.str()); - } - else { - struct local { - static auto task(int id, ghostl::task_completion_source tcs, ghostl::cancellation_token ct = {}) -> ghostl::task<> { - co_await co_delay(id * DELAY, ct); - std::stringstream hello; - if (ct.is_cancellation_requested()) { - hello << "Hello async (" << id << ") cancelled" << std::ends; - auto str = hello.str(); - tcs.set_value(str); - // PRINTF("eventAsync: %s\n", str.c_str()); - co_return; - } - hello << "Hello async (" << id << ") = " << micros() / 1000 << std::ends; - tcs.set_value(hello.str()); - } - }; - auto task = local::task(id, tcs, ct); - auto runner = ghostl::run_task(std::move(task)); - runner.resume(); - } - return tcs.token(); -} - -void main_tcs(ghostl::cancellation_token ct = {}); - -auto run_tcs(ghostl::cancellation_token ct = {}) -> ghostl::task<> -{ - if (ct.is_cancellation_requested()) { PRINTLN("run_tcs(): cancelled"); co_return; } - auto preset_tcs = ghostl::task_completion_source<>(); - auto preset_tok = preset_tcs.token(); - preset_tcs.set_value(); - if (ct.is_cancellation_requested()) { PRINTLN("run_tcs(): cancelled"); co_return; } - co_await preset_tok; - auto posted_tcs = ghostl::task_completion_source<>(); - schedule_recurrent_function_us( - [posted_tcs]() { posted_tcs.set_value(); return false; }, DELAY / 2, - [ct]() { return ct.is_cancellation_requested(); }); - auto posted_tok = posted_tcs.token(); - co_await posted_tok; - if (ct.is_cancellation_requested()) { PRINTLN("run_tcs(): cancelled"); co_return; } - co_await co_delay(DELAY / 2, ct); -} - -void main_tcs(ghostl::cancellation_token ct) -{ - auto task = run_tcs(ct); - auto runner = ghostl::run_task(std::move(task)); - runner.continue_with([ct]() { schedule_function([ct]() { if (!ct.is_cancellation_requested()) main_tcs(ct); }); }); - runner.resume(); -} - -auto make_task(int id, ghostl::cancellation_token ct = {}) -> ghostl::task -{ - std::string result = co_await eventAsync(id, ct); - if (id % 2) co_await co_delay(id * DELAY, ct); - if (ct.is_cancellation_requested()) { PRINTF("make_task(%u): cancelled\n", id); } - co_return result; -} - -void main_when_all(ghostl::cancellation_token ct = {}); - -ghostl::task<> make_when_all_tasks(ghostl::cancellation_token outer_ct = {}) -{ - auto fwd_ct = ghostl::run_task(outer_ct.cancellation_request()); - ghostl::cancellation_token_source cts; - auto ct = cts.token(); - fwd_ct.continue_with([cts](bool cancelled) { if (cancelled) cts.cancel(); }); - fwd_ct.resume(); - - std::vector> main_tasks; - constexpr unsigned TASKCNT = 100; - for (unsigned id = 0; id < TASKCNT; ++id) - { - main_tasks.emplace(main_tasks.begin(), make_task(id, ct)); - } - auto wall = ghostl::when_all(std::exchange(main_tasks, {})); - auto results = co_await wall(); - cts.cancel(); - // PRINTLN("make_when_all_tasks results begin"); - // for (auto r : results) PRINTLN(r.c_str()); - // PRINTLN("make_when_all_tasks results end"); - co_await co_delay(DELAY, ct); -} - -void main_when_all(ghostl::cancellation_token ct) -{ - auto task = make_when_all_tasks(ct); - auto runner = ghostl::run_task(std::move(task)); - runner.continue_with([ct]() { schedule_function([ct]() { if(!ct.is_cancellation_requested()) main_when_all(ct); }); }); - runner.resume(); -} - -void main_when_any(ghostl::cancellation_token ct = {}); - -ghostl::task<> make_when_any_tasks(ghostl::cancellation_token outer_ct = {}) -{ - auto fwd_ct = ghostl::run_task(outer_ct.cancellation_request()); - ghostl::cancellation_token_source cts; - auto ct = cts.token(); - fwd_ct.continue_with([cts](bool cancelled) { if (cancelled) cts.cancel(); }); - fwd_ct.resume(); - - std::vector> main_tasks; - constexpr unsigned TASKCNT = 100; - for (unsigned id = 0; id < TASKCNT; ++id) - { - main_tasks.emplace(main_tasks.begin(), make_task(id, ct)); - } - auto wany = ghostl::when_any(std::exchange(main_tasks, {})); - auto result = co_await wany(); - cts.cancel(); - // PRINTF("make_when_any_tasks result = %s\n", result.c_str()); - co_await co_delay(DELAY, ct); -} - -void main_when_any(ghostl::cancellation_token ct) -{ - auto task = make_when_any_tasks(ct); - auto runner = ghostl::run_task(std::move(task)); - runner.continue_with([ct]() { schedule_function([ct]() { if (!ct.is_cancellation_requested()) main_when_any(ct); }); }); - runner.resume(); -} - -ghostl::cancellation_token_source cts; -auto start = micros(); -bool pending_restart = false; - -void setup() -{ -#if defined(ESP32) || defined(ESP8266) - Serial.begin(115200); - while (!Serial) delay(1); -#endif - - auto ct = cts.token(); - - main_gen(); - main_tcs(ct); - main_when_all(ct); - main_when_any(ct); -} - -void loop() -{ - if (!pending_restart && micros() - start > 1000000) - { - PRINTLN("Global cancel and restart after 10 ms"); - pending_restart = true; - cts.cancel(); - schedule_recurrent_function_us([]() { - cts = ghostl::cancellation_token_source{}; - start = micros(); - auto ct = cts.token(); - pending_restart = false; - main_gen(); - main_tcs(ct); - main_when_all(ct); - main_when_any(ct); - return false; - }, 10000); - } - -#ifndef ESP8266 - auto schedDelay_us = get_scheduled_recurrent_delay_us(); -#else - auto schedDelay_us = std::min(get_scheduled_delay_us(), get_scheduled_recurrent_delay_us()); -#endif - if (schedDelay_us >= 10) - { - PRINTF("Delaying scheduler: %lu us (now = %lu s)\n", schedDelay_us, millis() / 1000); - } - -#if defined(ARDUINO) - delay(schedDelay_us / 1000UL); -#elif defined(__ZEPHYR__) -#ifdef CONFIG_BOARD_NATIVE_POSIX - // The native POSIX board emulation does not advance system timeout during code execution, - // which is incompatible with the FastScheduler comparing system time to target times. - k_sleep(K_USEC(schedDelay_us ? schedDelay_us : 1)); -#else // CONFIG_BOARD_NATIVE_POSIX - k_sleep(K_USEC(schedDelay_us)); -#endif // CONFIG_BOARD_NATIVE_POSIX -#else //__ZEPHYR__ - std::this_thread::sleep_for(schedDelay_us < 4000000 ? std::chrono::microseconds(schedDelay_us) : std::chrono::microseconds(4000000)); -#endif //__ZEPHYR__ - -#ifndef ESP8266 - run_scheduled_functions(); -#endif -} - -#ifdef __ZEPHYR__ -void cpp_test_fn(void *arg1, void *arg2, void *arg3) -{ - (void)arg1; - (void)arg2; - (void)arg3; - setup(); - while (1) { - loop(); - } -} -K_THREAD_DEFINE(cpp_test, 3072, cpp_test_fn, NULL, NULL, NULL, 10, 0, 0); -#elif !defined(ARDUINO) -int main(int argc, char* argv[]) -{ - (void)argc; - (void)argv; - setup(); - for (;;) loop(); - return 0; -} -#endif - diff --git a/library.json b/library.json index ef549eb..b2d7702 100644 --- a/library.json +++ b/library.json @@ -22,5 +22,9 @@ "frameworks": "arduino", "platforms": [ "espressif8266", "espressif32" - ] + ], + "dependencies": + { + "dok-net/ghostl": "^1.0.0" + } } diff --git a/library.properties b/library.properties index 66ac389..4f14f80 100644 --- a/library.properties +++ b/library.properties @@ -7,3 +7,4 @@ paragraph= category=Signal Input/Output url=https://github.com/plerup/espsoftwareserial/ architectures=esp8266,esp32 +depends=ghostl(>=1.0.0) diff --git a/src/SoftwareSerial.h b/src/SoftwareSerial.h index 4c05920..65c5d5c 100644 --- a/src/SoftwareSerial.h +++ b/src/SoftwareSerial.h @@ -21,7 +21,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #ifndef __SoftwareSerial_h #define __SoftwareSerial_h -#include "circular_queue/circular_queue.h" +#include #include // Define lets bittiming calculation be based on cpu cycles instead diff --git a/src/circular_queue/Delegate.h b/src/circular_queue/Delegate.h deleted file mode 100644 index b65ca6a..0000000 --- a/src/circular_queue/Delegate.h +++ /dev/null @@ -1,2210 +0,0 @@ -#pragma once -/* -Delegate.h - An efficient interchangeable C function ptr and C++ std::function delegate -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 __Delegate_h -#define __Delegate_h - -#if defined(ESP8266) -#include -#elif defined(ESP32) -#include -#else -#define IRAM_ATTR -#endif - -#if defined(__GNUC__) -#undef ALWAYS_INLINE_ATTR -#define ALWAYS_INLINE_ATTR __attribute__((always_inline)) -#else -#define ALWAYS_INLINE_ATTR -#endif - -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) -#include -#include -#else -#include "ghostl.h" -#endif - -namespace -{ - - template - ALWAYS_INLINE_ATTR inline R IRAM_ATTR vPtrToFunPtrExec(void* fn, P... args) - { - using target_type = R(P...); - return reinterpret_cast(fn)(std::forward(args...)); - } - -} - -namespace delegate -{ - namespace detail - { - -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - template - class DelegatePImpl { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A, P...); - using FunVPPtr = R(*)(void*, P...); - using FunctionType = std::function; - public: - DelegatePImpl() - { - kind = FP; - fn = nullptr; - } - - DelegatePImpl(std::nullptr_t) - { - kind = FP; - fn = nullptr; - } - - ~DelegatePImpl() - { - if (FUNC == kind) - functional.~FunctionType(); - else if (FPA == kind) - obj.~A(); - } - - DelegatePImpl(const DelegatePImpl& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(del.functional); - } - else if (FPA == del.kind) - { - fnA = del.fnA; - new (&obj) A(del.obj); - } - else - { - fn = del.fn; - } - } - - DelegatePImpl(DelegatePImpl&& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(std::move(del.functional)); - } - else if (FPA == del.kind) - { - fnA = del.fnA; - new (&obj) A(std::move(del.obj)); - } - else - { - fn = del.fn; - } - } - - DelegatePImpl(FunAPtr fnA, const A& obj) - { - kind = FPA; - DelegatePImpl::fnA = fnA; - new (&this->obj) A(obj); - } - - DelegatePImpl(FunAPtr fnA, A&& obj) - { - kind = FPA; - DelegatePImpl::fnA = fnA; - new (&this->obj) A(std::move(obj)); - } - - DelegatePImpl(FunPtr fn) - { - kind = FP; - DelegatePImpl::fn = fn; - } - - template DelegatePImpl(F functional) - { - kind = FUNC; - new (&this->functional) FunctionType(std::forward(functional)); - } - - DelegatePImpl& operator=(const DelegatePImpl& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - if (FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - else if (FPA == del.kind) - { - new (&obj) A; - } - kind = del.kind; - } - if (FUNC == del.kind) - { - functional = del.functional; - } - else if (FPA == del.kind) - { - fnA = del.fnA; - obj = del.obj; - } - else - { - fn = del.fn; - } - return *this; - } - - DelegatePImpl& operator=(DelegatePImpl&& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - if (FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - else if (FPA == del.kind) - { - new (&obj) A; - } - kind = del.kind; - } - if (FUNC == del.kind) - { - functional = std::move(del.functional); - } - else if (FPA == del.kind) - { - fnA = del.fnA; - obj = std::move(del.obj); - } - else - { - fn = del.fn; - } - return *this; - } - - DelegatePImpl& operator=(FunPtr fn) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - kind = FP; - this->fn = fn; - return *this; - } - - DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - kind = FP; - fn = nullptr; - return *this; - } - - IRAM_ATTR operator bool() const - { - if (FP == kind) - { - return fn; - } - else if (FPA == kind) - { - return fnA; - } - else - { - return functional ? true : false; - } - } - - static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) ALWAYS_INLINE_ATTR - { - return static_cast(self)->fnA( - static_cast(self)->obj, - std::forward(args...)); - }; - - operator FunVPPtr() const - { - if (FP == kind) - { - return vPtrToFunPtrExec; - } - else if (FPA == kind) - { - return vPtrToFunAPtrExec; - } - else - { - return [](void* self, P... args) -> R - { - return static_cast(self)->functional(std::forward(args...)); - }; - } - } - - void* arg() const - { - if (FP == kind) - { - return reinterpret_cast(fn); - } - else - { - return const_cast(this); - } - } - - operator FunctionType() const - { - if (FP == kind) - { - return fn; - } - else if (FPA == kind) - { - return [this](P... args) { return fnA(obj, std::forward(args...)); }; - } - else - { - return functional; - } - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - R IRAM_ATTR operator()(P... args) const - { - if (FP == kind) - { - if (fn) return fn(std::forward(args...)); - } - else if (FPA == kind) - { - if (fnA) return fnA(obj, std::forward(args...)); - } - else - { - if (functional) return functional(std::forward(args...)); - } - return R(); - } - - protected: - union { - FunctionType functional; - FunPtr fn; - struct { - FunAPtr fnA; - A obj; - }; - }; - enum { FUNC, FP, FPA } kind; - }; -#else - template - class DelegatePImpl { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A, P...); - using FunVPPtr = R(*)(void*, P...); - public: - DelegatePImpl() - { - kind = FP; - fn = nullptr; - } - - DelegatePImpl(std::nullptr_t) - { - kind = FP; - fn = nullptr; - } - - DelegatePImpl(const DelegatePImpl& del) - { - kind = del.kind; - if (FPA == del.kind) - { - fnA = del.fnA; - obj = del.obj; - } - else - { - fn = del.fn; - } - } - - DelegatePImpl(DelegatePImpl&& del) - { - kind = del.kind; - if (FPA == del.kind) - { - fnA = del.fnA; - obj = std::move(del.obj); - } - else - { - fn = del.fn; - } - } - - DelegatePImpl(FunAPtr fnA, const A& obj) - { - kind = FPA; - DelegatePImpl::fnA = fnA; - this->obj = obj; - } - - DelegatePImpl(FunAPtr fnA, A&& obj) - { - kind = FPA; - DelegatePImpl::fnA = fnA; - this->obj = std::move(obj); - } - - DelegatePImpl(FunPtr fn) - { - kind = FP; - DelegatePImpl::fn = fn; - } - - template DelegatePImpl(F functional) - { - kind = FP; - fn = std::forward(functional); - } - - DelegatePImpl& operator=(const DelegatePImpl& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FPA == kind) - { - obj = {}; - } - kind = del.kind; - } - if (FPA == del.kind) - { - fnA = del.fnA; - obj = del.obj; - } - else - { - fn = del.fn; - } - return *this; - } - - DelegatePImpl& operator=(DelegatePImpl&& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FPA == kind) - { - obj = {}; - } - kind = del.kind; - } - if (FPA == del.kind) - { - fnA = del.fnA; - obj = std::move(del.obj); - } - else - { - fn = del.fn; - } - return *this; - } - - DelegatePImpl& operator=(FunPtr fn) - { - if (FPA == kind) - { - obj = {}; - } - kind = FP; - this->fn = fn; - return *this; - } - - DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) - { - if (FPA == kind) - { - obj = {}; - } - kind = FP; - fn = nullptr; - return *this; - } - - IRAM_ATTR operator bool() const - { - if (FP == kind) - { - return fn; - } - else - { - return fnA; - } - } - - static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self, P... args) ALWAYS_INLINE_ATTR - { - return static_cast(self)->fnA( - static_cast(self)->obj, - std::forward(args...)); - }; - - operator FunVPPtr() const - { - if (FP == kind) - { - return vPtrToFunPtrExec; - } - else - { - return vPtrToFunAPtrExec; - } - } - - void* arg() const - { - if (FP == kind) - { - return reinterpret_cast(fn); - } - else - { - return const_cast(this); - } - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - R IRAM_ATTR operator()(P... args) const - { - if (FP == kind) - { - if (fn) return fn(std::forward(args...)); - } - else - { - if (fnA) return fnA(obj, std::forward(args...)); - } - return R(); - } - - protected: - union { - FunPtr fn; - FunAPtr fnA; - }; - A obj; - enum { FP, FPA } kind; - }; -#endif - -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - template - class DelegatePImpl { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; - using FunctionType = std::function; - using FunVPPtr = R(*)(void*, P...); - public: - DelegatePImpl() - { - kind = FP; - fn = nullptr; - } - - DelegatePImpl(std::nullptr_t) - { - kind = FP; - fn = nullptr; - } - - ~DelegatePImpl() - { - if (FUNC == kind) - functional.~FunctionType(); - } - - DelegatePImpl(const DelegatePImpl& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(del.functional); - } - else - { - fn = del.fn; - } - } - - DelegatePImpl(DelegatePImpl&& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(std::move(del.functional)); - } - else - { - fn = del.fn; - } - } - - DelegatePImpl(FunPtr fn) - { - kind = FP; - DelegatePImpl::fn = fn; - } - - template DelegatePImpl(F functional) - { - kind = FUNC; - new (&this->functional) FunctionType(std::forward(functional)); - } - - DelegatePImpl& operator=(const DelegatePImpl& del) - { - if (this == &del) return *this; - if (FUNC == kind && FUNC != del.kind) - { - functional.~FunctionType(); - } - else if (FUNC != kind && FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - kind = del.kind; - if (FUNC == del.kind) - { - functional = del.functional; - } - else - { - fn = del.fn; - } - return *this; - } - - DelegatePImpl& operator=(DelegatePImpl&& del) - { - if (this == &del) return *this; - if (FUNC == kind && FUNC != del.kind) - { - functional.~FunctionType(); - } - else if (FUNC != kind && FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - kind = del.kind; - if (FUNC == del.kind) - { - functional = std::move(del.functional); - } - else - { - fn = del.fn; - } - return *this; - } - - DelegatePImpl& operator=(FunPtr fn) - { - if (FUNC == kind) - { - functional.~FunctionType(); - kind = FP; - } - DelegatePImpl::fn = fn; - return *this; - } - - DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - kind = FP; - fn = nullptr; - return *this; - } - - IRAM_ATTR operator bool() const - { - if (FP == kind) - { - return fn; - } - else - { - return functional ? true : false; - } - } - - operator FunVPPtr() const - { - if (FP == kind) - { - return vPtrToFunPtrExec; - } - else - { - return [](void* self, P... args) -> R - { - return static_cast(self)->functional(std::forward(args...)); - }; - } - } - - void* arg() const - { - if (FP == kind) - { - return reinterpret_cast(fn); - } - else - { - return const_cast(this); - } - } - - operator FunctionType() const - { - if (FP == kind) - { - return fn; - } - else - { - return functional; - } - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - R IRAM_ATTR operator()(P... args) const - { - if (FP == kind) - { - if (fn) return fn(std::forward(args...)); - } - else - { - if (functional) return functional(std::forward(args...)); - } - return R(); - } - - protected: - union { - FunctionType functional; - FunPtr fn; - }; - enum { FUNC, FP } kind; - }; -#else - template - class DelegatePImpl { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; - using FunVPPtr = R(*)(void*, P...); - public: - DelegatePImpl() - { - fn = nullptr; - } - - DelegatePImpl(std::nullptr_t) - { - fn = nullptr; - } - - DelegatePImpl(const DelegatePImpl& del) - { - fn = del.fn; - } - - DelegatePImpl(DelegatePImpl&& del) - { - fn = std::move(del.fn); - } - - DelegatePImpl(FunPtr fn) - { - DelegatePImpl::fn = fn; - } - - template DelegatePImpl(F fn) - { - DelegatePImpl::fn = std::forward(fn); - } - - DelegatePImpl& operator=(const DelegatePImpl& del) - { - if (this == &del) return *this; - fn = del.fn; - return *this; - } - - DelegatePImpl& operator=(DelegatePImpl&& del) - { - if (this == &del) return *this; - fn = std::move(del.fn); - return *this; - } - - DelegatePImpl& operator=(FunPtr fn) - { - DelegatePImpl::fn = fn; - return *this; - } - - inline DelegatePImpl& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR - { - fn = nullptr; - return *this; - } - - inline IRAM_ATTR operator bool() const ALWAYS_INLINE_ATTR - { - return fn; - } - - operator FunVPPtr() const - { - return vPtrToFunPtrExec; - } - - void* arg() const - { - return reinterpret_cast(fn); - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - inline R IRAM_ATTR operator()(P... args) const ALWAYS_INLINE_ATTR - { - if (fn) return fn(std::forward(args...)); - return R(); - } - - protected: - FunPtr fn; - }; -#endif - -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - template - class DelegateImpl { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A); - using FunctionType = std::function; - using FunVPPtr = R(*)(void*); - public: - DelegateImpl() - { - kind = FP; - fn = nullptr; - } - - DelegateImpl(std::nullptr_t) - { - kind = FP; - fn = nullptr; - } - - ~DelegateImpl() - { - if (FUNC == kind) - functional.~FunctionType(); - else if (FPA == kind) - obj.~A(); - } - - DelegateImpl(const DelegateImpl& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(del.functional); - } - else if (FPA == del.kind) - { - fnA = del.fnA; - new (&obj) A(del.obj); - } - else - { - fn = del.fn; - } - } - - DelegateImpl(DelegateImpl&& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(std::move(del.functional)); - } - else if (FPA == del.kind) - { - fnA = del.fnA; - new (&obj) A(std::move(del.obj)); - } - else - { - fn = del.fn; - } - } - - DelegateImpl(FunAPtr fnA, const A& obj) - { - kind = FPA; - DelegateImpl::fnA = fnA; - new (&this->obj) A(obj); - } - - DelegateImpl(FunAPtr fnA, A&& obj) - { - kind = FPA; - DelegateImpl::fnA = fnA; - new (&this->obj) A(std::move(obj)); - } - - DelegateImpl(FunPtr fn) - { - kind = FP; - DelegateImpl::fn = fn; - } - - template DelegateImpl(F functional) - { - kind = FUNC; - new (&this->functional) FunctionType(std::forward(functional)); - } - - DelegateImpl& operator=(const DelegateImpl& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - if (FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - else if (FPA == del.kind) - { - new (&obj) A; - } - kind = del.kind; - } - if (FUNC == del.kind) - { - functional = del.functional; - } - else if (FPA == del.kind) - { - fnA = del.fnA; - obj = del.obj; - } - else - { - fn = del.fn; - } - return *this; - } - - DelegateImpl& operator=(DelegateImpl&& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - if (FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - else if (FPA == del.kind) - { - new (&obj) A; - } - kind = del.kind; - } - if (FUNC == del.kind) - { - functional = std::move(del.functional); - } - else if (FPA == del.kind) - { - fnA = del.fnA; - obj = std::move(del.obj); - } - else - { - fn = del.fn; - } - return *this; - } - - DelegateImpl& operator=(FunPtr fn) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - kind = FP; - this->fn = fn; - return *this; - } - - DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - else if (FPA == kind) - { - obj.~A(); - } - kind = FP; - fn = nullptr; - return *this; - } - - IRAM_ATTR operator bool() const - { - if (FP == kind) - { - return fn; - } - else if (FPA == kind) - { - return fnA; - } - else - { - return functional ? true : false; - } - } - - static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self) ALWAYS_INLINE_ATTR - { - return static_cast(self)->fnA( - static_cast(self)->obj); - }; - - operator FunVPPtr() const - { - if (FP == kind) - { - return reinterpret_cast(fn); - } - else if (FPA == kind) - { - return vPtrToFunAPtrExec; - } - else - { - return [](void* self) -> R - { - return static_cast(self)->functional(); - }; - } - } - - void* arg() const - { - if (FP == kind) - { - return nullptr; - } - else - { - return const_cast(this); - } - } - - operator FunctionType() const - { - if (FP == kind) - { - return fn; - } - else if (FPA == kind) - { - return [this]() { return fnA(obj); }; - } - else - { - return functional; - } - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - R IRAM_ATTR operator()() const - { - if (FP == kind) - { - if (fn) return fn(); - } - else if (FPA == kind) - { - if (fnA) return fnA(obj); - } - else - { - if (functional) return functional(); - } - return R(); - } - - protected: - union { - FunctionType functional; - FunPtr fn; - struct { - FunAPtr fnA; - A obj; - }; - }; - enum { FUNC, FP, FPA } kind; - }; -#else - template - class DelegateImpl { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A); - using FunVPPtr = R(*)(void*); - public: - DelegateImpl() - { - kind = FP; - fn = nullptr; - } - - DelegateImpl(std::nullptr_t) - { - kind = FP; - fn = nullptr; - } - - DelegateImpl(const DelegateImpl& del) - { - kind = del.kind; - if (FPA == del.kind) - { - fnA = del.fnA; - obj = del.obj; - } - else - { - fn = del.fn; - } - } - - DelegateImpl(DelegateImpl&& del) - { - kind = del.kind; - if (FPA == del.kind) - { - fnA = del.fnA; - obj = std::move(del.obj); - } - else - { - fn = del.fn; - } - } - - DelegateImpl(FunAPtr fnA, const A& obj) - { - kind = FPA; - DelegateImpl::fnA = fnA; - this->obj = obj; - } - - DelegateImpl(FunAPtr fnA, A&& obj) - { - kind = FPA; - DelegateImpl::fnA = fnA; - this->obj = std::move(obj); - } - - DelegateImpl(FunPtr fn) - { - kind = FP; - DelegateImpl::fn = fn; - } - - template DelegateImpl(F fn) - { - kind = FP; - DelegateImpl::fn = std::forward(fn); - } - - DelegateImpl& operator=(const DelegateImpl& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FPA == kind) - { - obj = {}; - } - kind = del.kind; - } - if (FPA == del.kind) - { - fnA = del.fnA; - obj = del.obj; - } - else - { - fn = del.fn; - } - return *this; - } - - DelegateImpl& operator=(DelegateImpl&& del) - { - if (this == &del) return *this; - if (kind != del.kind) - { - if (FPA == kind) - { - obj = {}; - } - kind = del.kind; - } - if (FPA == del.kind) - { - fnA = del.fnA; - obj = std::move(del.obj); - } - else - { - fn = del.fn; - } - return *this; - } - - DelegateImpl& operator=(FunPtr fn) - { - if (FPA == kind) - { - obj = {}; - } - kind = FP; - this->fn = fn; - return *this; - } - - DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) - { - if (FPA == kind) - { - obj = {}; - } - kind = FP; - fn = nullptr; - return *this; - } - - IRAM_ATTR operator bool() const - { - if (FP == kind) - { - return fn; - } - else - { - return fnA; - } - } - - static inline R IRAM_ATTR vPtrToFunAPtrExec(void* self) ALWAYS_INLINE_ATTR - { - return static_cast(self)->fnA( - static_cast(self)->obj); - }; - - operator FunVPPtr() const - { - if (FP == kind) - { - return reinterpret_cast(fn); - } - else - { - return vPtrToFunAPtrExec; - } - } - - void* arg() const - { - if (FP == kind) - { - return nullptr; - } - else - { - return const_cast(this); - } - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - R IRAM_ATTR operator()() const - { - if (FP == kind) - { - if (fn) return fn(); - } - else - { - if (fnA) return fnA(obj); - } - return R(); - } - - protected: - union { - FunPtr fn; - FunAPtr fnA; - }; - A obj; - enum { FP, FPA } kind; - }; -#endif - -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - template - class DelegateImpl { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; - using FunctionType = std::function; - using FunVPPtr = R(*)(void*); - public: - DelegateImpl() - { - kind = FP; - fn = nullptr; - } - - DelegateImpl(std::nullptr_t) - { - kind = FP; - fn = nullptr; - } - - ~DelegateImpl() - { - if (FUNC == kind) - functional.~FunctionType(); - } - - DelegateImpl(const DelegateImpl& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(del.functional); - } - else - { - fn = del.fn; - } - } - - DelegateImpl(DelegateImpl&& del) - { - kind = del.kind; - if (FUNC == del.kind) - { - new (&functional) FunctionType(std::move(del.functional)); - } - else - { - fn = del.fn; - } - } - - DelegateImpl(FunPtr fn) - { - kind = FP; - DelegateImpl::fn = fn; - } - - template DelegateImpl(F functional) - { - kind = FUNC; - new (&this->functional) FunctionType(std::forward(functional)); - } - - DelegateImpl& operator=(const DelegateImpl& del) - { - if (this == &del) return *this; - if (FUNC == kind && FUNC != del.kind) - { - functional.~FunctionType(); - } - else if (FUNC != kind && FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - kind = del.kind; - if (FUNC == del.kind) - { - functional = del.functional; - } - else - { - fn = del.fn; - } - return *this; - } - - DelegateImpl& operator=(DelegateImpl&& del) - { - if (this == &del) return *this; - if (FUNC == kind && FUNC != del.kind) - { - functional.~FunctionType(); - } - else if (FUNC != kind && FUNC == del.kind) - { - new (&this->functional) FunctionType(); - } - kind = del.kind; - if (FUNC == del.kind) - { - functional = std::move(del.functional); - } - else - { - fn = del.fn; - } - return *this; - } - - DelegateImpl& operator=(FunPtr fn) - { - if (FUNC == kind) - { - functional.~FunctionType(); - kind = FP; - } - DelegateImpl::fn = fn; - return *this; - } - - DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) - { - if (FUNC == kind) - { - functional.~FunctionType(); - } - kind = FP; - fn = nullptr; - return *this; - } - - IRAM_ATTR operator bool() const - { - if (FP == kind) - { - return fn; - } - else - { - return functional ? true : false; - } - } - - operator FunVPPtr() const - { - if (FP == kind) - { - return reinterpret_cast(fn); - } - else - { - return [](void* self) -> R - { - return static_cast(self)->functional(); - }; - } - } - - void* arg() const - { - if (FP == kind) - { - return nullptr; - } - else - { - return const_cast(this); - } - } - - operator FunctionType() const - { - if (FP == kind) - { - return fn; - } - else - { - return functional; - } - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - R IRAM_ATTR operator()() const - { - if (FP == kind) - { - if (fn) return fn(); - } - else - { - if (functional) return functional(); - } - return R(); - } - - protected: - union { - FunctionType functional; - FunPtr fn; - }; - enum { FUNC, FP } kind; - }; -#else - template - class DelegateImpl { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; - using FunVPPtr = R(*)(void*); - public: - DelegateImpl() - { - fn = nullptr; - } - - DelegateImpl(std::nullptr_t) - { - fn = nullptr; - } - - DelegateImpl(const DelegateImpl& del) - { - fn = del.fn; - } - - DelegateImpl(DelegateImpl&& del) - { - fn = std::move(del.fn); - } - - DelegateImpl(FunPtr fn) - { - DelegateImpl::fn = fn; - } - - template DelegateImpl(F fn) - { - DelegateImpl::fn = std::forward(fn); - } - - DelegateImpl& operator=(const DelegateImpl& del) - { - if (this == &del) return *this; - fn = del.fn; - return *this; - } - - DelegateImpl& operator=(DelegateImpl&& del) - { - if (this == &del) return *this; - fn = std::move(del.fn); - return *this; - } - - DelegateImpl& operator=(FunPtr fn) - { - DelegateImpl::fn = fn; - return *this; - } - - inline DelegateImpl& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR - { - fn = nullptr; - return *this; - } - - inline IRAM_ATTR operator bool() const ALWAYS_INLINE_ATTR - { - return fn; - } - - operator FunVPPtr() const - { - return reinterpret_cast(fn); - } - - void* arg() const - { - return nullptr; - } - - /// Calling is safe without checking for nullptr. - /// If non-void, returns the default value. - /// In ISR context, where faults and exceptions must not - /// occurs, this saves the extra check for nullptr, - /// and allows the compiler to optimize out checks - /// in std::function which may not be ISR-safe or - /// cause linker errors, like l32r relocation errors - /// on the Xtensa ISA. - inline R IRAM_ATTR operator()() const ALWAYS_INLINE_ATTR - { - if (fn) return fn(); - return R(); - } - - protected: - FunPtr fn; - }; -#endif - - template - class Delegate : private detail::DelegatePImpl - { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A, P...); - using FunVPPtr = R(*)(void*, P...); -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - using FunctionType = std::function; -#endif - public: - using detail::DelegatePImpl::operator bool; - using detail::DelegatePImpl::arg; - using detail::DelegatePImpl::operator(); - - operator FunVPPtr() { return detail::DelegatePImpl::operator FunVPPtr(); } -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } -#endif - - Delegate() : detail::DelegatePImpl::DelegatePImpl() {} - - Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} - - Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( - std::move(static_cast&>(del))) {} - - Delegate(FunAPtr fnA, const A& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} - - Delegate(FunAPtr fnA, A&& obj) : detail::DelegatePImpl::DelegatePImpl(fnA, std::move(obj)) {} - - Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} - - template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - detail::DelegatePImpl::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - detail::DelegatePImpl::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(FunPtr fn) { - detail::DelegatePImpl::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - detail::DelegatePImpl::operator=(nullptr); - return *this; - } - }; - - template - class Delegate : private detail::DelegatePImpl - { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A*, P...); - using FunVPPtr = R(*)(void*, P...); -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - using FunctionType = std::function; -#endif - public: - using detail::DelegatePImpl::operator bool; - using detail::DelegatePImpl::operator(); - - operator FunVPPtr() const - { - if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) - { - return reinterpret_cast(detail::DelegatePImpl::fnA); - } - else - { - return detail::DelegatePImpl::operator FunVPPtr(); - } - } -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } -#endif - void* arg() const - { - if (detail::DelegatePImpl::FPA == detail::DelegatePImpl::kind) - { - return detail::DelegatePImpl::obj; - } - else - { - return detail::DelegatePImpl::arg(); - } - } - - Delegate() : detail::DelegatePImpl::DelegatePImpl() {} - - Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} - - Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( - std::move(static_cast&>(del))) {} - - Delegate(FunAPtr fnA, A* obj) : detail::DelegatePImpl::DelegatePImpl(fnA, obj) {} - - Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} - - template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - detail::DelegatePImpl::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - detail::DelegatePImpl::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(FunPtr fn) { - detail::DelegatePImpl::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - detail::DelegatePImpl::operator=(nullptr); - return *this; - } - }; - - template - class Delegate : private detail::DelegatePImpl - { - public: - using target_type = R(P...); - protected: - using FunPtr = target_type*; -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - using FunctionType = std::function; -#endif - using FunVPPtr = R(*)(void*, P...); - public: - using detail::DelegatePImpl::operator bool; - using detail::DelegatePImpl::arg; - using detail::DelegatePImpl::operator(); - - operator FunVPPtr() const { return detail::DelegatePImpl::operator FunVPPtr(); } -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - operator FunctionType() { return detail::DelegatePImpl::operator FunctionType(); } -#endif - - Delegate() : detail::DelegatePImpl::DelegatePImpl() {} - - Delegate(std::nullptr_t) : detail::DelegatePImpl::DelegatePImpl(nullptr) {} - - Delegate(const Delegate& del) : detail::DelegatePImpl::DelegatePImpl( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : detail::DelegatePImpl::DelegatePImpl( - std::move(static_cast&>(del))) {} - - Delegate(FunPtr fn) : detail::DelegatePImpl::DelegatePImpl(fn) {} - - template Delegate(F functional) : detail::DelegatePImpl::DelegatePImpl(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - detail::DelegatePImpl::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - detail::DelegatePImpl::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(FunPtr fn) { - detail::DelegatePImpl::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - detail::DelegatePImpl::operator=(nullptr); - return *this; - } - }; - - template - class Delegate : private detail::DelegateImpl - { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A); - using FunVPPtr = R(*)(void*); -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - using FunctionType = std::function; -#endif - public: - using detail::DelegateImpl::operator bool; - using detail::DelegateImpl::arg; - using detail::DelegateImpl::operator(); - - operator FunVPPtr() { return detail::DelegateImpl::operator FunVPPtr(); } -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } -#endif - - Delegate() : detail::DelegateImpl::DelegateImpl() {} - - Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} - - Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( - std::move(static_cast&>(del))) {} - - Delegate(FunAPtr fnA, const A& obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} - - Delegate(FunAPtr fnA, A&& obj) : detail::DelegateImpl::DelegateImpl(fnA, std::move(obj)) {} - - Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} - - template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - detail::DelegateImpl::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - detail::DelegateImpl::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(FunPtr fn) { - detail::DelegateImpl::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - detail::DelegateImpl::operator=(nullptr); - return *this; - } - }; - - template - class Delegate : private detail::DelegateImpl - { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; - using FunAPtr = R(*)(A*); - using FunVPPtr = R(*)(void*); -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - using FunctionType = std::function; -#endif - public: - using detail::DelegateImpl::operator bool; - using detail::DelegateImpl::operator(); - - operator FunVPPtr() const - { - if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) - { - return reinterpret_cast(detail::DelegateImpl::fnA); - } - else - { - return detail::DelegateImpl::operator FunVPPtr(); - } - } -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } -#endif - void* arg() const - { - if (detail::DelegateImpl::FPA == detail::DelegateImpl::kind) - { - return detail::DelegateImpl::obj; - } - else - { - return detail::DelegateImpl::arg(); - } - } - - Delegate() : detail::DelegateImpl::DelegateImpl() {} - - Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} - - Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( - std::move(static_cast&>(del))) {} - - Delegate(FunAPtr fnA, A* obj) : detail::DelegateImpl::DelegateImpl(fnA, obj) {} - - Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} - - template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - detail::DelegateImpl::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - detail::DelegateImpl::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(FunPtr fn) { - detail::DelegateImpl::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - detail::DelegateImpl::operator=(nullptr); - return *this; - } - }; - - template - class Delegate : private detail::DelegateImpl - { - public: - using target_type = R(); - protected: - using FunPtr = target_type*; -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - using FunctionType = std::function; -#endif - using FunVPPtr = R(*)(void*); - public: - using detail::DelegateImpl::operator bool; - using detail::DelegateImpl::arg; - using detail::DelegateImpl::operator(); - - operator FunVPPtr() const { return detail::DelegateImpl::operator FunVPPtr(); } -#if !defined(ARDUINO) || defined(ESP8266) || defined(ESP32) - operator FunctionType() { return detail::DelegateImpl::operator FunctionType(); } -#endif - - Delegate() : detail::DelegateImpl::DelegateImpl() {} - - Delegate(std::nullptr_t) : detail::DelegateImpl::DelegateImpl(nullptr) {} - - Delegate(const Delegate& del) : detail::DelegateImpl::DelegateImpl( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : detail::DelegateImpl::DelegateImpl( - std::move(static_cast&>(del))) {} - - Delegate(FunPtr fn) : detail::DelegateImpl::DelegateImpl(fn) {} - - template Delegate(F functional) : detail::DelegateImpl::DelegateImpl(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - detail::DelegateImpl::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - detail::DelegateImpl::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(FunPtr fn) { - detail::DelegateImpl::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - detail::DelegateImpl::operator=(nullptr); - return *this; - } - }; - } -} - -template class Delegate; -template class Delegate : public delegate::detail::Delegate -{ -public: - Delegate() : delegate::detail::Delegate::Delegate() {} - - Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} - - Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( - std::move(static_cast&>(del))) {} - - Delegate(typename delegate::detail::Delegate::FunAPtr fnA, const A& obj) : delegate::detail::Delegate::Delegate(fnA, obj) {} - - Delegate(typename delegate::detail::Delegate::FunAPtr fnA, A&& obj) : delegate::detail::Delegate::Delegate(fnA, std::move(obj)) {} - - Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} - - template Delegate(F functional) : delegate::detail::Delegate::Delegate(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - delegate::detail::Delegate::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - delegate::detail::Delegate::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { - delegate::detail::Delegate::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - delegate::detail::Delegate::operator=(nullptr); - return *this; - } -}; - -template class Delegate : public delegate::detail::Delegate -{ -public: - Delegate() : delegate::detail::Delegate::Delegate() {} - - Delegate(std::nullptr_t) : delegate::detail::Delegate::Delegate(nullptr) {} - - Delegate(const Delegate& del) : delegate::detail::Delegate::Delegate( - static_cast&>(del)) {} - - Delegate(Delegate&& del) : delegate::detail::Delegate::Delegate( - std::move(static_cast&>(del))) {} - - Delegate(typename delegate::detail::Delegate::FunPtr fn) : delegate::detail::Delegate::Delegate(fn) {} - - template Delegate(F functional) : delegate::detail::Delegate::Delegate(std::forward(functional)) {} - - Delegate& operator=(const Delegate& del) { - delegate::detail::Delegate::operator=(del); - return *this; - } - - Delegate& operator=(Delegate&& del) { - delegate::detail::Delegate::operator=(std::move(del)); - return *this; - } - - Delegate& operator=(typename delegate::detail::Delegate::FunPtr fn) { - delegate::detail::Delegate::operator=(fn); - return *this; - } - - inline Delegate& IRAM_ATTR operator=(std::nullptr_t) ALWAYS_INLINE_ATTR { - delegate::detail::Delegate::operator=(nullptr); - return *this; - } -}; - -#endif // __Delegate_h diff --git a/src/circular_queue/MultiDelegate.h b/src/circular_queue/MultiDelegate.h deleted file mode 100644 index dc7f5a8..0000000 --- a/src/circular_queue/MultiDelegate.h +++ /dev/null @@ -1,568 +0,0 @@ -#pragma once -/* -MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate -class -Copyright (c) 2019-2020 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 __MULTIDELEGATE_H -#define __MULTIDELEGATE_H - -#include -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) -#include -#else -#include "ghostl.h" -#endif - -#if defined(ESP8266) -#include -using esp8266::InterruptLock; -#elif defined(ARDUINO) -class InterruptLock { -public: - InterruptLock() { - noInterrupts(); - } - ~InterruptLock() { - interrupts(); - } -}; -#else -#include -#endif - -namespace -{ - - template< typename Delegate, typename R, bool ISQUEUE = false, typename... P> - struct CallP - { - static R execute(Delegate& del, P... args) - { - return del(std::forward(args...)); - } - }; - - template< typename Delegate, bool ISQUEUE, typename... P> - struct CallP - { - static bool execute(Delegate& del, P... args) - { - del(std::forward(args...)); - return true; - } - }; - - template< typename Delegate, typename R, bool ISQUEUE = false> - struct Call - { - static R execute(Delegate& del) - { - return del(); - } - }; - - template< typename Delegate, bool ISQUEUE> - struct Call - { - static bool execute(Delegate& del) - { - del(); - return true; - } - }; - -} - -namespace delegate -{ - namespace detail - { - - template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P> - class MultiDelegatePImpl - { - public: - MultiDelegatePImpl() = default; - ~MultiDelegatePImpl() - { - *this = nullptr; - } - - MultiDelegatePImpl(const MultiDelegatePImpl&) = delete; - MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete; - - MultiDelegatePImpl(MultiDelegatePImpl&& md) - { - first = md.first; - last = md.last; - unused = md.unused; - nodeCount = md.nodeCount; - md.first = nullptr; - md.last = nullptr; - md.unused = nullptr; - md.nodeCount = 0; - } - - MultiDelegatePImpl(const Delegate& del) - { - add(del); - } - - MultiDelegatePImpl(Delegate&& del) - { - add(std::move(del)); - } - - MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md) - { - first = md.first; - last = md.last; - unused = md.unused; - nodeCount = md.nodeCount; - md.first = nullptr; - md.last = nullptr; - md.unused = nullptr; - md.nodeCount = 0; - return *this; - } - - MultiDelegatePImpl& operator=(std::nullptr_t) - { - if (last) - last->mNext = unused; - if (first) - unused = first; - while (unused) - { - auto to_delete = unused; - unused = unused->mNext; - delete(to_delete); - } - return *this; - } - - MultiDelegatePImpl& operator+=(const Delegate& del) - { - add(del); - return *this; - } - - MultiDelegatePImpl& operator+=(Delegate&& del) - { - add(std::move(del)); - return *this; - } - - protected: - struct Node_t - { - ~Node_t() - { - mDelegate = nullptr; // special overload in Delegate - } - Node_t* mNext = nullptr; - Delegate mDelegate; - }; - - Node_t* first = nullptr; - Node_t* last = nullptr; - Node_t* unused = nullptr; - size_t nodeCount = 0; - - // Returns a pointer to an unused Node_t, - // or if none are available allocates a new one, - // or nullptr if limit is reached - Node_t* IRAM_ATTR get_node_unsafe() - { - Node_t* result = nullptr; - // try to get an item from unused items list - if (unused) - { - result = unused; - unused = unused->mNext; - } - // if no unused items, and count not too high, allocate a new one - else if (nodeCount < QUEUE_CAPACITY) - { -#if defined(ESP8266) || defined(ESP32) - result = new (std::nothrow) Node_t; -#else - result = new Node_t; -#endif - if (result) - ++nodeCount; - } - return result; - } - - void recycle_node_unsafe(Node_t* node) - { - node->mDelegate = nullptr; // special overload in Delegate - node->mNext = unused; - unused = node; - } - -#ifndef ARDUINO - std::mutex mutex_unused; -#endif - public: - class iterator : public std::iterator - { - public: - Node_t* current = nullptr; - Node_t* prev = nullptr; - const Node_t* stop = nullptr; - - iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {} - iterator() = default; - iterator(const iterator&) = default; - iterator& operator=(const iterator&) = default; - iterator& operator=(iterator&&) = default; - operator bool() const - { - return current && stop; - } - bool operator==(const iterator& rhs) const - { - return current == rhs.current; - } - bool operator!=(const iterator& rhs) const - { - return !operator==(rhs); - } - Delegate& operator*() const - { - return current->mDelegate; - } - Delegate* operator->() const - { - return ¤t->mDelegate; - } - iterator& operator++() // prefix - { - if (current && stop != current) - { - prev = current; - current = current->mNext; - } - else - current = nullptr; // end - return *this; - } - iterator& operator++(int) // postfix - { - iterator tmp(*this); - operator++(); - return tmp; - } - }; - - iterator begin() - { - return iterator(*this); - } - iterator end() const - { - return iterator(); - } - - const Delegate* add(const Delegate& del) - { - return add(Delegate(del)); - } - - const Delegate* add(Delegate&& del) - { - if (!del) - return nullptr; - -#ifdef ARDUINO - InterruptLock lockAllInterruptsInThisScope; -#else - std::lock_guard lock(mutex_unused); -#endif - - Node_t* item = ISQUEUE ? get_node_unsafe() : -#if defined(ESP8266) || defined(ESP32) - new (std::nothrow) Node_t; -#else - new Node_t; -#endif - if (!item) - return nullptr; - - item->mDelegate = std::move(del); - item->mNext = nullptr; - - if (last) - last->mNext = item; - else - first = item; - last = item; - - return &item->mDelegate; - } - - iterator erase(iterator it) - { - if (!it) - return end(); -#ifdef ARDUINO - InterruptLock lockAllInterruptsInThisScope; -#else - std::lock_guard lock(mutex_unused); -#endif - auto to_recycle = it.current; - - if (last == it.current) - last = it.prev; - it.current = it.current->mNext; - if (it.prev) - { - it.prev->mNext = it.current; - } - else - { - first = it.current; - } - if (ISQUEUE) - recycle_node_unsafe(to_recycle); - else - delete to_recycle; - return it; - } - - bool erase(const Delegate* const del) - { - auto it = begin(); - while (it) - { - if (del == &(*it)) - { - erase(it); - return true; - } - ++it; - } - return false; - } - - operator bool() const - { - return first; - } - - R operator()(P... args) - { - auto it = begin(); - if (!it) - return {}; - - static std::atomic fence(false); - // prevent recursive calls -#if defined(ARDUINO) && !defined(ESP32) - if (fence.load()) return {}; - fence.store(true); -#else - if (fence.exchange(true)) return {}; -#endif - - R result; - do - { - result = CallP::execute(*it, args...); - if (result && ISQUEUE) - it = erase(it); - else - ++it; -#if defined(ESP8266) || defined(ESP32) - // running callbacks might last too long for watchdog etc. - optimistic_yield(10000); -#endif - } while (it); - - fence.store(false); - return result; - } - }; - - template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> - class MultiDelegateImpl : public MultiDelegatePImpl - { - public: - using MultiDelegatePImpl::MultiDelegatePImpl; - - R operator()() - { - auto it = this->begin(); - if (!it) - return {}; - - static std::atomic fence(false); - // prevent recursive calls -#if defined(ARDUINO) && !defined(ESP32) - if (fence.load()) return {}; - fence.store(true); -#else - if (fence.exchange(true)) return {}; -#endif - - R result; - do - { - result = Call::execute(*it); - if (result && ISQUEUE) - it = this->erase(it); - else - ++it; -#if defined(ESP8266) || defined(ESP32) - // running callbacks might last too long for watchdog etc. - optimistic_yield(10000); -#endif - } while (it); - - fence.store(false); - return result; - } - }; - - template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate; - - template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> - class MultiDelegate : public MultiDelegatePImpl - { - public: - using MultiDelegatePImpl::MultiDelegatePImpl; - }; - - template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY> - class MultiDelegate : public MultiDelegateImpl - { - public: - using MultiDelegateImpl::MultiDelegateImpl; - }; - - template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> - class MultiDelegate : public MultiDelegatePImpl - { - public: - using MultiDelegatePImpl::MultiDelegatePImpl; - - void operator()(P... args) - { - auto it = this->begin(); - if (!it) - return; - - static std::atomic fence(false); - // prevent recursive calls -#if defined(ARDUINO) && !defined(ESP32) - if (fence.load()) return; - fence.store(true); -#else - if (fence.exchange(true)) return; -#endif - - do - { - CallP::execute(*it, args...); - if (ISQUEUE) - it = this->erase(it); - else - ++it; -#if defined(ESP8266) || defined(ESP32) - // running callbacks might last too long for watchdog etc. - optimistic_yield(10000); -#endif - } while (it); - - fence.store(false); - } - }; - - template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY> - class MultiDelegate : public MultiDelegateImpl - { - public: - using MultiDelegateImpl::MultiDelegateImpl; - - void operator()() - { - auto it = this->begin(); - if (!it) - return; - - static std::atomic fence(false); - // prevent recursive calls -#if defined(ARDUINO) && !defined(ESP32) - if (fence.load()) return; - fence.store(true); -#else - if (fence.exchange(true)) return; -#endif - - do - { - Call::execute(*it); - if (ISQUEUE) - it = this->erase(it); - else - ++it; -#if defined(ESP8266) || defined(ESP32) - // running callbacks might last too long for watchdog etc. - optimistic_yield(10000); -#endif - } while (it); - - fence.store(false); - } - }; - - } - -} - -/** -The MultiDelegate class template can be specialized to either a queue or an event multiplexer. -It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function. -@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on. -@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true), - the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain. - This is exploited to minimize the use of new and delete by reusing already allocated items, thus - reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are - used for allocation of the event handler items. - If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue - removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until - explicitly removed. - If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue - the type-conversion to bool of that result determines if the item is immediately removed or kept - after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event - handlers until they are explicitly removed. -@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically - allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate - instance during its own lifetime for efficiency. -*/ -template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32> -class MultiDelegate : public delegate::detail::MultiDelegate -{ -public: - using delegate::detail::MultiDelegate::MultiDelegate; -}; - -#endif // __MULTIDELEGATE_H diff --git a/src/circular_queue/async_queue.h b/src/circular_queue/async_queue.h deleted file mode 100644 index 83105a8..0000000 --- a/src/circular_queue/async_queue.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once -/* -async_queue.h - Implementation of a C++20 async awaitable queue. -Copyright (c) 2023 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 -*/ - -#include "circular_queue/task_completion_source.h" -#include "circular_queue/task.h" -#include "circular_queue/lfllist.h" - -namespace ghostl -{ - template - struct async_queue : private lfllist - { - using lfllist_type = lfllist; - - async_queue() - { - cur_tcs.store(tcs_queue.emplace_front(task_completion_source<>())); - } - async_queue(const async_queue&) = delete; - async_queue(async_queue&&) = delete; - auto operator =(const async_queue&)->async_queue & = delete; - auto operator =(async_queue&&)->async_queue & = delete; - - [[nodiscard]] auto push(T&& val) -> bool - { - if (auto node = lfllist_type::emplace_front(std::move(val)); node != nullptr) - { - if (auto _tcs_node = tcs_queue.emplace_front(task_completion_source<>()); _tcs_node != nullptr) - { - if (auto _cur_tcs = cur_tcs.exchange(_tcs_node); _cur_tcs != nullptr) - { - task_completion_source<> tcs = _cur_tcs->item; - tcs.set_value(); - return true; - } - // keep new cur_tcs, this fixes previous failure to set it (OOM) - } - lfllist_type::erase(node); - } - return false; - } - inline auto push(const T& val) -> bool ALWAYS_INLINE_ATTR - { - T v(val); - return push(std::move(v)); - } - auto flush() -> void - { - while (tcs_queue.back()) - { - if (task_completion_source<> item; tcs_queue.try_pop(item)) {} - } - cur_tcs.store(tcs_queue.emplace_front(task_completion_source<>())); - while (lfllist_type::back()) - { - if (T item; lfllist_type::try_pop(item)) {} - } - } - auto pop() -> ghostl::task - { - auto tcs = tcs_queue.back(); - auto token = tcs->item.token(); - co_await token; - tcs_queue.erase(tcs); - decltype(lfllist_type::back()) node = lfllist_type::back(); - T item = std::move(node->item); - lfllist_type::erase(node); - co_return item; - } - - private: - ghostl::lfllist> tcs_queue; - std::atomic cur_tcs; - }; -} diff --git a/src/circular_queue/cancellation_token.h b/src/circular_queue/cancellation_token.h deleted file mode 100644 index d8894a7..0000000 --- a/src/circular_queue/cancellation_token.h +++ /dev/null @@ -1,178 +0,0 @@ -#pragma once -/* -cancellation_token.h - Implementation of a C++20 async coroutines cancellation token. -Copyright (c) 2023 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 -*/ - -#include "task_completion_source.h" -#include "task.h" -#include "lfllist.h" - -#include -#include - -namespace ghostl -{ - struct cancellation_token_source; - - struct cancellation_state_type final - { - explicit cancellation_state_type() = default; - cancellation_state_type(const cancellation_state_type&) = delete; - cancellation_state_type(cancellation_state_type&& other) noexcept = delete; - auto operator=(const cancellation_state_type&)->cancellation_state_type & = delete; - auto operator=(cancellation_state_type&& other) noexcept -> cancellation_state_type & = delete; - ~cancellation_state_type() - { - if (!cancelled.load()) list.for_each([](task_completion_source&& tcs) { auto _tcs = std::move(tcs); _tcs.set_value(false); }); - } - void cancel() - { - if (!cancelled.exchange(true)) - { - list.for_each([](task_completion_source&& tcs) { auto _tcs = std::move(tcs); _tcs.set_value(true); }); - } - } - [[nodiscard]] auto is_cancellation_requested() const - { - return cancelled.load(); - } - [[nodiscard]] auto make_tcs_list_node() - { - return list.emplace_front(task_completion_source()); - } - auto erase_tcs_list_node(lfllist>::node_type* tcs_list_node) -> void - { - list.erase(tcs_list_node); - } - private: - std::atomic cancelled{ false }; - lfllist> list; - }; - - /// - /// The cancellation_token class exists to check if a cancellation request - /// has been made for its associated cancellation_token_source object. - /// The cancellation_token provides a co_await'able cancellation_request() - /// member function, that completes if the cancellation_token's associated - /// cancellation_token_source is cancelled. - /// - struct cancellation_token - { - private: - friend cancellation_token_source; - std::shared_ptr state; - decltype(ghostl::cancellation_state_type().make_tcs_list_node()) tcs_node{nullptr}; - cancellation_token(decltype(state) _state) : state(std::move(_state)) { } - public: - /// - /// create a default cancellation token that can never be cancelled - /// - cancellation_token() = default; - cancellation_token(const cancellation_token& other) : state(other.state) { } - cancellation_token(cancellation_token&& other) noexcept - { - state = std::exchange(other.state, nullptr); - } - ~cancellation_token() - { - if (tcs_node) state->erase_tcs_list_node(tcs_node); - } - auto operator=(const cancellation_token& other) -> cancellation_token& - { - if (std::addressof(other) != this) - { - state = other.state; - } - return *this; - } - auto operator=(cancellation_token&& other) noexcept -> cancellation_token& - { - if (std::addressof(other) != this) - { - state = std::exchange(other.state, nullptr); - } - return *this; - } - [[nodiscard]] auto is_cancellation_requested() const - { - return !state || state->is_cancellation_requested(); - } - /// - /// Get an awaitable that completes if the cancellation_token's associated - /// cancellation_token_source is cancelled. Never call this more than once - /// on the same cancellation_token, but get another cancellation token. - /// - /// A bool task, that has a value of true if cancellation occurred, - /// or false if the cancellation source etc was deleted without cancellation. - [[nodiscard]] auto cancellation_request() -> ghostl::task - { - if (!state || state->is_cancellation_requested()) co_return true; - tcs_node = state->make_tcs_list_node(); - // if concurrently cancelled, re-cancel to force processing of the new token. - if (state->is_cancellation_requested()) state->cancel(); - auto token = tcs_node->item.token(); - tcs_node = nullptr; - co_return co_await token; - } - }; - - /// - /// The cancellation_token_source class can issue a cancellation request, - /// similar to what std::stop_token_source does for std::jthread cancellation. - /// A cancellation request made for one cancellation_token_source object - /// is visible to all cancellation_token_sources and cancellation_tokens - /// of the same associated cancellation state. - /// - struct cancellation_token_source - { - explicit cancellation_token_source() = default; - cancellation_token_source(const cancellation_token_source& other) : state(other.state) { } - cancellation_token_source(cancellation_token_source&& other) noexcept - { - state = std::exchange(other.state, nullptr); - } - auto operator=(const cancellation_token_source& other) -> cancellation_token_source& - { - if (std::addressof(other) != this) - { - state = other.state; - } - return *this; - } - auto operator=(cancellation_token_source&& other) noexcept -> cancellation_token_source& - { - if (std::addressof(other) != this) - { - state = std::exchange(other.state, nullptr); - } - return *this; - } - void cancel() const - { - std::shared_ptr _state(state); - if (_state) _state->cancel(); - } - [[nodiscard]] auto is_cancellation_requested() const - { - return state ? state->is_cancellation_requested() : true; - } - [[nodiscard]] auto token() const { return cancellation_token(state); } - private: - std::shared_ptr state{ std::make_shared() }; - }; -} // namespace ghostl diff --git a/src/circular_queue/circular_queue.h b/src/circular_queue/circular_queue.h deleted file mode 100644 index e235562..0000000 --- a/src/circular_queue/circular_queue.h +++ /dev/null @@ -1,385 +0,0 @@ -#pragma once -/* -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 -#endif - -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) -#include -#include -#include -#include "Delegate.h" -using std::min; -#else -#include "ghostl.h" -#endif - -#if !defined(ESP32) && !defined(ESP8266) -#define IRAM_ATTR -#endif - -#if defined(__GNUC__) -#undef ALWAYS_INLINE_ATTR -#define ALWAYS_INLINE_ATTR __attribute__((always_inline)) -#else -#define ALWAYS_INLINE_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 number 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 IRAM_ATTR available() const - { - int avail = static_cast(m_inPos.load() - m_outPos.load()); - if (avail < 0) avail += static_cast(m_bufSize); - return avail; - } - - /*! - @brief Get the remaining free elementes for pushing. - */ - size_t IRAM_ATTR available_for_push() const - { - int avail = static_cast(m_outPos.load() - m_inPos.load()) - 1; - if (avail < 0) avail += static_cast(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() - { - const auto inPos = m_inPos.load(std::memory_order_acquire); - const size_t next = (inPos + 1) % m_bufSize; - if (next == m_outPos.load(std::memory_order_relaxed)) { - return false; - } - std::atomic_thread_fence(std::memory_order_release); - m_inPos.store(next, std::memory_order_release); - return true; - } - - /*! - @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) - { - const auto inPos = m_inPos.load(std::memory_order_acquire); - const size_t next = (inPos + 1) % m_bufSize; - if (next == m_outPos.load(std::memory_order_relaxed)) { - return false; - } - m_buffer[inPos] = std::move(val); - std::atomic_thread_fence(std::memory_order_release); - m_inPos.store(next, std::memory_order_release); - return true; - } - - /*! - @brief Push a copy of the parameter into the queue. - @return true if the queue accepted the value, false if the queue - was full. - */ - inline bool IRAM_ATTR push(const T& val) ALWAYS_INLINE_ATTR - { - T v(val); - return push(std::move(v)); - } - -#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& fun); -#else - void for_each(Delegate 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& fun); -#else - bool for_each_rev_requeue(Delegate fun); -#endif - -protected: - size_t m_bufSize; -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) - std::unique_ptr m_buffer; -#else - std::unique_ptr m_buffer; -#endif - std::atomic m_inPos; - std::atomic m_outPos; -}; - -template< typename T, typename ForEachArg > -bool circular_queue::capacity(const size_t cap) -{ - if (cap + 1 == m_bufSize) return true; - else if (available() > cap) return false; - std::unique_ptr buffer(new T[cap + 1]); - const auto available = pop_n(buffer, cap); - m_buffer.reset(buffer); - m_bufSize = cap + 1; - m_inPos.store(available, std::memory_order_relaxed); - m_outPos.store(0, std::memory_order_relaxed); - return true; -} - -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) -template< typename T, typename ForEachArg > -size_t circular_queue::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; - - auto dest = m_buffer.get() + inPos; - std::copy_n(std::make_move_iterator(buffer), blockSize, dest); - size = min(size - blockSize, outPos > 1 ? static_cast(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::pop() -{ - const auto outPos = m_outPos.load(std::memory_order_acquire); - if (m_inPos.load(std::memory_order_relaxed) == outPos) return {}; - - std::atomic_thread_fence(std::memory_order_acquire); - - auto val = std::move(m_buffer[outPos]); - - 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::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(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); - } - - 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::for_each(const Delegate& fun) -#else -void circular_queue::for_each(Delegate 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])); - 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::for_each_rev_requeue(const Delegate& fun) -#else -bool circular_queue::for_each_rev_requeue(Delegate fun) -#endif -{ - auto inPos0 = circular_queue::m_inPos.load(std::memory_order_acquire); - auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); - if (outPos == inPos0) return false; - auto pos = inPos0; - auto outPos1 = inPos0; - const auto posDecr = circular_queue::m_bufSize - 1; - std::atomic_thread_fence(std::memory_order_acquire); - do { - pos = (pos + posDecr) % circular_queue::m_bufSize; - T&& val = std::move(circular_queue::m_buffer[pos]); - if (fun(val)) - { - outPos1 = (outPos1 + posDecr) % circular_queue::m_bufSize; - if (outPos1 != pos) circular_queue::m_buffer[outPos1] = std::move(val); - } - } while (pos != outPos); - std::atomic_thread_fence(std::memory_order_release); - circular_queue::m_outPos.store(outPos1, std::memory_order_release); - return true; -} - -#endif // __circular_queue_h diff --git a/src/circular_queue/circular_queue_mp.h b/src/circular_queue/circular_queue_mp.h deleted file mode 100644 index 2993146..0000000 --- a/src/circular_queue/circular_queue_mp.h +++ /dev/null @@ -1,312 +0,0 @@ -#pragma once -/* -circular_queue_mp.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_mp_h -#define __circular_queue_mp_h - -#include "circular_queue.h" - -#if defined(ESP8266) -#include -using esp8266::InterruptLock; -#endif - -/*! - @brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO). - This implementation is lock-free between producers and consumer for the available(), peek(), - pop(), and push() type functions. -*/ -template< typename T, typename ForEachArg = void > -class circular_queue_mp : protected circular_queue -{ -public: - circular_queue_mp() : circular_queue() - { - m_inPos_mp.store(0); - m_concurrent_mp.store(0); - } - circular_queue_mp(const size_t capacity) : circular_queue(capacity) - { - m_inPos_mp.store(0); - m_concurrent_mp.store(0); - } - circular_queue_mp(circular_queue_mp&& cq) : circular_queue(std::move(cq)) - { - m_inPos_mp.store(cq.m_inPos_mp.load()); - m_concurrent_mp.store(cq.m_concurrent_mp.load()); - } - circular_queue_mp& operator=(circular_queue_mp&& cq) - { - circular_queue::operator=(std::move(cq)); - m_inPos_mp.store(cq.m_inPos_mp.load()); - m_concurrent_mp.store(cq.m_concurrent_mp.load()); - } - circular_queue_mp& operator=(const circular_queue_mp&) = delete; - - using circular_queue::capacity; - using circular_queue::flush; - using circular_queue::peek; - using circular_queue::pop; - using circular_queue::pop_n; - using circular_queue::for_each; - using circular_queue::for_each_rev_requeue; - - T& pushpeek() = delete; - bool push() = delete; - - inline size_t IRAM_ATTR available() const ALWAYS_INLINE_ATTR - { - return circular_queue::available(); - } - inline size_t IRAM_ATTR available_for_push() const ALWAYS_INLINE_ATTR - { - return circular_queue::available_for_push(); - } - - /*! - @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 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 push(T&& val); - - /*! - @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. - */ - inline bool IRAM_ATTR push(const T& val) ALWAYS_INLINE_ATTR - { - T v(val); - return push(std::move(v)); - } - - /*! - @brief Push copies of multiple elements from a buffer into the queue, - in order, beginning at buffer's head. This is safe for - multiple producers. - @return The number of elements actually copied into the queue, counted - from the buffer head. - */ -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) - size_t push_n(const T* buffer, size_t size); -#endif - -protected: - std::atomic m_inPos_mp; - std::atomic m_concurrent_mp; -}; - -template< typename T, typename ForEachArg > -bool circular_queue_mp::capacity(const size_t cap) -{ - if (cap + 1 == circular_queue::m_bufSize) return true; - else if (!circular_queue::capacity(cap)) return false; - m_inPos_mp.store(circular_queue::m_inPos.load(std::memory_order_relaxed), - std::memory_order_relaxed); - m_concurrent_mp.store(0, std::memory_order_relaxed); - return true; -} - -template< typename T, typename ForEachArg > -bool IRAM_ATTR circular_queue_mp::push(T&& val) -{ - size_t inPos_mp; - size_t next; -#if !defined(ESP32) && defined(ARDUINO) - class InterruptLock { - public: - InterruptLock() { - noInterrupts(); - } - ~InterruptLock() { - interrupts(); - } - }; - { - InterruptLock lock; -#else - ++m_concurrent_mp; - do - { -#endif - inPos_mp = m_inPos_mp.load(std::memory_order_relaxed); - next = (inPos_mp + 1) % circular_queue::m_bufSize; - if (next == circular_queue::m_outPos.load(std::memory_order_relaxed)) { -#if !defined(ESP32) && defined(ARDUINO) - return false; - } - m_inPos_mp.store(next, std::memory_order_relaxed); - m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) + 1, - std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - } -#else - int concurrent_mp; - do - { - inPos_mp = m_inPos_mp.load(); - concurrent_mp = m_concurrent_mp.load(); - if (1 == concurrent_mp) - { - circular_queue::m_inPos.store(inPos_mp, std::memory_order_release); - } - } - while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1)); - return false; - } - } - while (!m_inPos_mp.compare_exchange_weak(inPos_mp, next)); -#endif - - circular_queue::m_buffer[inPos_mp] = std::move(val); - - std::atomic_thread_fence(std::memory_order_release); - -#if !defined(ESP32) && defined(ARDUINO) - { - InterruptLock lock; - if (1 == m_concurrent_mp.load(std::memory_order_relaxed)) - { - inPos_mp = m_inPos_mp.load(std::memory_order_relaxed); - circular_queue::m_inPos.store(inPos_mp, std::memory_order_relaxed); - } - m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) - 1, - std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - } -#else - int concurrent_mp; - do - { - inPos_mp = m_inPos_mp.load(); - concurrent_mp = m_concurrent_mp.load(); - if (1 == concurrent_mp) - { - circular_queue::m_inPos.store(inPos_mp, std::memory_order_release); - } - } - while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1)); -#endif - - return true; -} - -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) -template< typename T, typename ForEachArg > -size_t circular_queue_mp::push_n(const T* buffer, size_t size) -{ - const auto outPos = circular_queue::m_outPos.load(std::memory_order_relaxed); - size_t inPos_mp; - size_t next; - size_t blockSize; -#if !defined(ESP32) && defined(ARDUINO) - { - InterruptLock lock; -#else - ++m_concurrent_mp; - do - { -#endif - inPos_mp = m_inPos_mp.load(std::memory_order_relaxed); - blockSize = (outPos > inPos_mp) ? outPos - 1 - inPos_mp : (outPos == 0) ? circular_queue::m_bufSize - 1 - inPos_mp : circular_queue::m_bufSize - inPos_mp; - blockSize = min(size, blockSize); - if (!blockSize) - { -#if !defined(ESP32) && defined(ARDUINO) - return 0; - } - next = (inPos_mp + blockSize) % circular_queue::m_bufSize; - m_inPos_mp.store(next, std::memory_order_relaxed); - m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) + 1, - std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - } -#else - int concurrent_mp = m_concurrent_mp.load(); - do - { - inPos_mp = m_inPos_mp.load(); - concurrent_mp = m_concurrent_mp.load(); - if (1 == concurrent_mp) - { - circular_queue::m_inPos.store(inPos_mp, std::memory_order_release); - } - } - while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1)); - return false; - } - next = (inPos_mp + blockSize) % circular_queue::m_bufSize; - } - while (!m_inPos_mp.compare_exchange_weak(inPos_mp, next)); -#endif - - auto dest = circular_queue::m_buffer.get() + inPos_mp; - std::copy_n(std::make_move_iterator(buffer), blockSize, dest); - size = min(size - blockSize, outPos > 1 ? static_cast(outPos - next - 1) : 0); - next += size; - dest = circular_queue::m_buffer.get(); - std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest); - - std::atomic_thread_fence(std::memory_order_release); - -#if !defined(ESP32) && defined(ARDUINO) - { - InterruptLock lock; - if (1 == m_concurrent_mp.load(std::memory_order_relaxed)) - { - inPos_mp = m_inPos_mp.load(std::memory_order_relaxed); - circular_queue::m_inPos.store(inPos_mp, std::memory_order_relaxed); - } - m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) - 1, - std::memory_order_relaxed); - std::atomic_thread_fence(std::memory_order_release); - } -#else - int concurrent_mp; - do - { - inPos_mp = m_inPos_mp.load(); - concurrent_mp = m_concurrent_mp.load(); - if (1 == concurrent_mp) - { - circular_queue::m_inPos.store(inPos_mp, std::memory_order_release); - } - } - while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1)); -#endif - - return blockSize + size; -} - -#endif - -#endif // __circular_queue_mp_h diff --git a/src/circular_queue/generator.h b/src/circular_queue/generator.h deleted file mode 100644 index 1f88667..0000000 --- a/src/circular_queue/generator.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once -/* -generator.h - Implementation of a C++20 generator. -Copyright (c) 2023 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 -*/ - -#include -#include -#include - -namespace ghostl -{ - template - struct generator - { - struct promise_type - { - generator get_return_object() noexcept - { - return generator(std::coroutine_handle::from_promise(*this)); - } - std::suspend_always initial_suspend() const { return {}; } - std::suspend_always final_suspend() const noexcept { return {}; } - void unhandled_exception() { exception = std::current_exception(); } - - template - std::suspend_always yield_value(C&& from) - { - value = std::move(from); // caching the result in promise - return {}; - } - void return_void() const { } - - T value{}; - std::exception_ptr exception; - }; - - std::coroutine_handle coroutine; - - generator() noexcept : coroutine(nullptr) {} - explicit generator(std::coroutine_handle h) : coroutine(h) { } - generator(const generator&) = delete; - generator(generator&& other) noexcept : coroutine(std::exchange(other.coroutine, nullptr)), full(other.full) {}; - ~generator() { if (coroutine) coroutine.destroy(); } - generator& operator=(const generator&) = delete; - generator& operator=(generator&& other) noexcept - { - if (std::addressof(other) != this) - { - if (coroutine) coroutine.destroy(); - coroutine = std::exchange(other.coroutine, nullptr); - full = other.full; - } - return *this; - } - explicit operator bool() - { - fill(); - return !coroutine.done(); - } - T operator()() - { - fill(); - full = false; - return std::move(coroutine.promise().value); - } - private: - bool full = false; - - void fill() - { - if (!full) - { - coroutine(); - // propagate coroutine exception in called context - if (coroutine.promise().exception) - std::rethrow_exception(coroutine.promise().exception); - full = true; - } - } - }; -} // namespace ghostl diff --git a/src/circular_queue/ghostl.h b/src/circular_queue/ghostl.h deleted file mode 100644 index e28ce22..0000000 --- a/src/circular_queue/ghostl.h +++ /dev/null @@ -1,198 +0,0 @@ -#pragma once -/* -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 - -#if defined(ARDUINO_ARCH_SAMD) -#include -#endif - -using size_t = decltype(sizeof(char)); - -namespace std -{ -#if !defined(ARDUINO_ARCH_SAMD) && !defined(ESP8266) - 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; } - - T exchange(T desired, std::memory_order = std::memory_order_seq_cst) const volatile noexcept { - noInterrupts(); - T orig = value; - value = desired; - interrupts(); - return orig; - } - - bool compare_exchange_strong(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept { - noInterrupts(); - const bool equal = value == expected; - if (equal) value = desired; - else expected = value; - interrupts(); - return equal; - } - - bool compare_exchange_weak(T& expected, T desired, std::memory_order order = std::memory_order_seq_cst) volatile noexcept { - return compare_exchange_strong(expected, desired, order); - }; - }; - - inline void atomic_thread_fence(std::memory_order order) noexcept {} - - template< typename T > T&& move(T& t) noexcept { return static_cast(t); } -#endif - -#ifndef ESP8266 - template< typename T, size_t long N > struct array - { - T _M_elems[N]; - decltype(sizeof(0)) size() const { return N; } - T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; } - const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; } - }; - - 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[](decltype(sizeof(0)) i) const { return ptr[i]; } - void reset(pointer p = pointer()) noexcept - { - delete ptr; - ptr = p; - } - T& operator*() const { return *ptr; } - private: - pointer ptr; - }; - - template< typename T > using function = T*; - using nullptr_t = decltype(nullptr); - - template - struct identity { - typedef T type; - }; - - template - inline T&& forward(typename identity::type& t) noexcept - { - return static_cast::type&&>(t); - } -#endif // ESP8266 - -#ifdef ESP8266 -#if defined (__cplusplus) - extern "C" { -#endif - bool __atomic_compare_exchange_4(uint32_t* ptr, uint32_t* expected, uint32_t desired, - bool weak, int success_memorder, int failure_memorder) - { - (void)weak; - (void)success_memorder; - (void)failure_memorder; - noInterrupts(); - const bool equal = *ptr == *expected; - if (equal) *ptr = desired; - else *expected = *ptr; - interrupts(); - return equal; - } - - bool __atomic_compare_exchange_1(uint8_t* ptr, uint8_t* expected, uint8_t desired, - bool weak, int success_memorder, int failure_memorder) - { - (void)weak; - (void)success_memorder; - (void)failure_memorder; - noInterrupts(); - const bool equal = *ptr == *expected; - if (equal) *ptr = desired; - else *expected = *ptr; - interrupts(); - return equal; - } - - uint32_t __atomic_exchange_4(uint32_t* ptr, uint32_t value, int memorder) - { - (void)memorder; - noInterrupts(); - uint32_t orig = *ptr; - *ptr = value; - interrupts(); - return orig; - } - - uint8_t __atomic_exchange_1(uint8_t* ptr, uint8_t value, int memorder) - { - (void)memorder; - noInterrupts(); - uint8_t orig = *ptr; - *ptr = value; - interrupts(); - return orig; - } - - uint32_t __atomic_fetch_add_4(uint32_t* ptr, uint32_t value, int memorder) - { - (void)memorder; - noInterrupts(); - uint32_t orig = *ptr; - *ptr += value; - interrupts(); - return orig; - } - - uint32_t __atomic_fetch_sub_4(uint32_t* ptr, uint32_t value, int memorder) - { - (void)memorder; - noInterrupts(); - uint32_t orig = *ptr; - *ptr -= value; - interrupts(); - return orig; - } -#if defined (__cplusplus) - } // extern "C" -#endif -#endif // ESP8266 -} -#endif // __ghostl_h diff --git a/src/circular_queue/lfllist.h b/src/circular_queue/lfllist.h deleted file mode 100644 index f0a0aef..0000000 --- a/src/circular_queue/lfllist.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - lfllist.h - A lock free double-linked list implementation. - Copyright (c) 2023 Dirk O. Kaar - - 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 -*/ - -#pragma once - -#ifndef __LFLLIST_H -#define __LFLLIST_H - -#include "Delegate.h" - -#include -#include - -namespace ghostl -{ - template - struct lfllist; - - namespace detail { - template - struct lfllist_node_type - { - T item; // must be first member to facilitate reinterpret_cast. - using node_type = lfllist_node_type; - lfllist_node_type() = default; - explicit lfllist_node_type(T&& _item) : item(std::move(_item)) {} - private: - template friend struct ghostl::lfllist; - std::atomic pred{ nullptr }; - std::atomic next{ nullptr }; - std::atomic remove_lock{ false }; - }; - }; - - template>, typename ForEachArg = void> - struct lfllist - { - using node_type = detail::lfllist_node_type; - - lfllist() = default; - lfllist(const lfllist&) = delete; - lfllist(lfllist&&) = delete; - ~lfllist() - { - node_type* node; - while (nullptr != (node = back())) erase(node); - } - auto operator =(const lfllist&)->lfllist & = delete; - auto operator =(lfllist&&)->lfllist & = delete; - - /// - /// Emplace an item at the list's front. Is safe for concurrency and reentrance. - /// - /// The item to emplace. - /// The pointer to new node, nullptr on failure. - [[nodiscard]] auto IRAM_ATTR emplace_front(T&& toInsert) -> node_type* - { - auto node = std::allocator_traits::allocate(alloc, 1); - if (!node) return nullptr; - std::allocator_traits::construct(alloc, node, std::move(toInsert)); - std::atomic_thread_fence(std::memory_order_release); - push(node); - return node; - }; - - /// - /// Push a node to the list's front. Is safe for concurrency and reentrance. - /// - /// A node pointer from a prior remove(). - /// , nullptr on failure. - auto IRAM_ATTR push(node_type* const node) -> void - { - auto next = first.exchange(node); - node->next.store(next); - std::atomic_thread_fence(std::memory_order_release); - next->pred.store(node); - std::atomic_thread_fence(std::memory_order_release); - }; - - /// - /// Remove, without destroying it, a member node from the list. - /// Using remove() or erase(), full concurrency safety for unique nodes. - /// Non-reentrant, non-concurrent with for_each(), and try_pop(). - /// Caveat: for_each() or try_pop() may erase any node pointer. - /// - /// A node (not nullptr) that must be a member of this list. - auto remove(node_type* const node) -> void - { - while (!try_remove(node)) {} - } - - /// - /// Try to remove, without destroying it, a member node from the list. - /// Using remove() or erase(), full concurrency safety for unique nodes. - /// Non-reentrant, non-concurrent with for_each(), and try_pop(). - /// Caveat: for_each() or try_pop() may erase any node pointer. - /// - /// A node (not nullptr) that must be a member of this list. - /// True on success, false if there is competition on locking node. - auto try_remove(node_type* const node) -> bool - { - auto _false = false; - if (!node->remove_lock.compare_exchange_strong(_false, true)) { return false; } - node_type* next = nullptr; - node_type* pred = nullptr; - for (;;) - { - next = node->next.load(); - if (next->remove_lock.compare_exchange_strong(_false, true)) - { - pred = node->pred.load(); - if (next == node->next.load()) break; - next->remove_lock.store(false); - } - else - { - _false = false; - } - } - for (;;) - { - next->pred.store(pred); - if (pred) pred->next.store(next); - auto _node = node; - if (!pred && !first.compare_exchange_strong(_node, next)) - { - while (node->pred.compare_exchange_strong(pred, pred)) {} - continue; - } - break; - } - next->remove_lock.store(false); - node->remove_lock.store(false); - node->pred.store(nullptr); - node->next.store(nullptr); - return true; - }; - - /// - /// Erase a previously emplaced node from the list. - /// Using erase(), full concurrency safety for unique nodes. - /// Non-reentrant, non-concurrent with for_each(), and try_pop(). - /// Caveat: for_each() or try_pop() may erase any node pointer. - /// - /// An item (not nullptr) that must be a member of this list. - auto erase(node_type* const to_erase) -> void - { - while (!try_erase(to_erase)) {} - } - - /// - /// Try to erase a previously emplaced node from the list. - /// Using try_erase(), full concurrency safety for unique nodes. - /// Non-reentrant, non-concurrent with for_each(), and try_pop(). - /// Caveat: for_each() or try_pop() may erase any node pointer. - /// - /// An item (not nullptr) that must be a member of this list. - /// True on success, false if there is competition on locking to_erase. - auto try_erase(node_type* const to_erase) -> bool - { - if (!try_remove(to_erase)) return false; - destroy(to_erase); - return true; - }; - - auto destroy(node_type* const node) -> void - { - std::allocator_traits::destroy(alloc, node); - std::allocator_traits::deallocate(alloc, node, 1); - } - - [[nodiscard]] auto back() -> node_type* - { - if (auto node = last_sentinel.pred.load(); node) return node; - return nullptr; - } - - /// - /// Try to atomically get the item of and erase a node at the back of the list. - /// Using try_pop(), full concurrency safety. - /// Non-reentrant, non-concurrent with erase(). - /// - /// An out argument that on success, receives the item at the back of this list. - /// True on success, false if the queue is empty or there is competition. - [[nodiscard]] auto try_pop(T& item) -> bool - { - node_type* node{}; - if (!try_pop(node)) return false; - item = std::move(node->item); - destroy(node); - return true; - }; - - /// - /// Try to atomically remove a node at the back of the list. - /// Using try_pop(), full concurrency safety. - /// Non-reentrant, non-concurrent with erase(). - /// - /// An out argument that on success, receives the item at the back of this list. - /// True on success, false if the queue is empty or there is competition. - [[nodiscard]] auto try_pop(node_type*& node) -> bool - { - auto _false = false; - if (!pop_guard.compare_exchange_strong(_false, true)) { return false; } - auto has_node = false; - if (nullptr != (node = back())) - { - remove(node); - has_node = true; - } - pop_guard.store(false); - return has_node; - }; - - /// - /// Traverse every node of this list in FIFO, then erase that node. - /// The ownership of the item contained in that node is passed to parameter function. - /// Non-reentrant, non-concurrent with erase(), try_pop(), and for_each(). - /// - /// A function this is invoked for each node of this list. -#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO) - void for_each(const Delegate& fn) -#else - void for_each(Delegate fn) -#endif - { - while (back()) - { - if (T item; try_pop(item)) fn(std::move(item)); - } - }; - - private: - Allocator alloc; - node_type last_sentinel; - std::atomic first = &last_sentinel; - std::atomic pop_guard{ false }; - }; -} - -#endif // __LFLLIST_H diff --git a/src/circular_queue/lfllist_allocator.h b/src/circular_queue/lfllist_allocator.h deleted file mode 100644 index 0417425..0000000 --- a/src/circular_queue/lfllist_allocator.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - lfllist_allocator.h - A lock free double-linked list based allocator. - Copyright (c) 2023 Dirk O. Kaar - - 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 -*/ - -#pragma once - -#ifndef __LFLLIST_ALLOCATOR_H -#define __LFLLIST_ALLOCATOR_H - -#include "lfllist.h" - -namespace ghostl -{ - template - struct lfllist_allocator : private lfllist - { - // type definitions - using lfllist = lfllist; - using node_type = lfllist::node_type; - using value_type = T; - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - - lfllist_allocator() - { - for (size_type i = 0; i < CAPACITY; ++i) - { - auto node = reinterpret_cast(&span[i * sizeof(node_type)]); - node = new (node) node_type(); - lfllist::push(node); - } - } - lfllist_allocator(const lfllist_allocator&) = delete; - lfllist_allocator(lfllist_allocator&&) = delete; - ~lfllist_allocator() - { - node_type* node; - while (nullptr != (node = lfllist::back())) - { - lfllist::remove(node); - } - } - auto operator =(const lfllist_allocator&)->lfllist_allocator & = delete; - auto operator =(lfllist_allocator&&)->lfllist_allocator & = delete; - [[nodiscard]] pointer allocate(size_type n, const void* = nullptr) - { - if (n != 1) - { - return nullptr; - } - node_type* node; - if (!lfllist::try_pop(node)) - { - return nullptr; - } - return reinterpret_cast(node); - } - constexpr void deallocate(pointer const p, size_t n) - { - auto node = reinterpret_cast(p); - lfllist::push(node); - } - private: - char span[CAPACITY * sizeof(node_type)] = { 0 }; - }; -} - -#endif // __LFLLIST_ALLOCATOR_H diff --git a/src/circular_queue/run_task.h b/src/circular_queue/run_task.h deleted file mode 100644 index 8be01b8..0000000 --- a/src/circular_queue/run_task.h +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once -/* -run_task.h - Implementation of a C++20 async coroutines task runner featuring continue_with. -Copyright (c) 2023 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 -*/ - -#include "task.h" - -#include -#include - -namespace ghostl -{ - namespace details - { - struct final_task final - { - struct promise_type final - { - final_task get_return_object() noexcept - { - return { std::coroutine_handle::from_promise(*this) }; - } - constexpr std::suspend_never initial_suspend() const noexcept { return {}; } - void unhandled_exception() const { std::rethrow_exception(std::current_exception()); } - constexpr void return_void() const noexcept {} - constexpr std::suspend_never final_suspend() const noexcept { return {}; } - }; - final_task() = default; - final_task(std::coroutine_handle&& handle) : coroutine(std::move(handle)) {} - std::coroutine_handle coroutine; - }; - } - - template - struct run_task - { - /// - /// Provide a non-coroutine continuation to run when the task completes. - /// - /// - /// The continuation function. Caveat: lambda captures can leak when the function is never invoked due to prior cancellation etc. of the task. - template void continue_with(F cont) - { - continuation = std::move(cont); - }; - run_task() = delete; - run_task(const ghostl::run_task& other) = delete; - run_task(ghostl::run_task&& other) noexcept : - task(std::exchange(other.task, {})), - continuation(std::exchange(other.continuation, nullptr)), - final_task(std::exchange(other.final_task, {})) { } - run_task(ghostl::task&& t) noexcept : task(std::move(t)) { } - - auto operator=(const ghostl::run_task& other) ->ghostl::run_task & = delete; - auto operator=(ghostl::run_task&& other) noexcept -> ghostl::run_task& - { - if (std::addressof(other) != this) - { - task = std::exchange(other.task, {}); - continuation = std::exchange(other.continuation, nullptr); - final_task = std::exchange(other.final_task, {}); - } - return *this; - } - - void resume() { - final_task = coroutine(); - } - private: - ghostl::details::final_task coroutine() { - auto t = std::exchange(task, {}); - auto cont = std::exchange(continuation, nullptr); - T res = co_await t; - if (cont) cont(res); - }; - ghostl::task task; - std::function continuation; - ghostl::details::final_task final_task; - }; - template<> - struct run_task - { - /// - /// Provide a non-coroutine continuation to run when the task completes. - /// - /// - /// The continuation function. Caveat: lambda captures can leak when the function is never invoked due to prior cancellation etc. of the task. - template void continue_with(F cont) - { - continuation = std::move(cont); - }; - run_task() = delete; - run_task(const ghostl::run_task<>& other) = delete; - run_task(ghostl::run_task<>&& other) noexcept : - task(std::exchange(other.task, {})), - continuation(std::exchange(other.continuation, nullptr)), - final_task(std::exchange(other.final_task, {})) { } - run_task(ghostl::task<>&& t) noexcept : task(std::move(t)) { } - - auto operator=(const ghostl::run_task<>& other) ->ghostl::run_task<> & = delete; - auto operator=(ghostl::run_task<>&& other) noexcept -> ghostl::run_task<>& - { - if (std::addressof(other) != this) - { - task = std::exchange(other.task, {}); - continuation = std::exchange(other.continuation, nullptr); - final_task = std::exchange(other.final_task, {}); - } - return *this; - } - - void resume() { - final_task = coroutine(); - } - void destroy() { - if (auto handle = std::exchange(final_task.coroutine, nullptr); handle && handle.done()) - { - handle.destroy(); - } - } - private: - ghostl::details::final_task coroutine() { - auto t = std::exchange(task, {}); - auto cont = std::exchange(continuation, nullptr); - co_await t; - if (cont) cont(); - }; - ghostl::task<> task; - std::function continuation; - ghostl::details::final_task final_task; - }; -} // namespace ghostl diff --git a/src/circular_queue/task.h b/src/circular_queue/task.h deleted file mode 100644 index cb781ea..0000000 --- a/src/circular_queue/task.h +++ /dev/null @@ -1,190 +0,0 @@ -#pragma once -/* -task.h - Implementation of a C++20 async coroutines task. -Copyright (c) 2023 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 -*/ - -#include -#include -#include - -namespace ghostl -{ - template - struct task - { - struct promise_type final - { - auto get_return_object() noexcept - { - return task(std::coroutine_handle::from_promise(*this)); - } - constexpr std::suspend_always initial_suspend() const { return {}; } - struct final_awaiter final - { - constexpr bool await_ready() const noexcept { return false; } - constexpr void await_resume() const noexcept {} - std::coroutine_handle<> - await_suspend(std::coroutine_handle h) const noexcept - { - // final_awaiter::await_suspend is called when the execution of the - // current coroutine (referred to by 'h') is about to finish. - // If the current coroutine was resumed by another coroutine via - // co_await get_task(), a handle to that coroutine has been stored - // as h.promise().previous. In that case, return the handle to resume - // the previous coroutine. - // Otherwise, return noop_coroutine(), whose resumption does nothing. - - if (auto previous = h.promise().previous; previous) - return previous; - else - return std::noop_coroutine(); - } - }; - final_awaiter final_suspend() const noexcept { return {}; } - void unhandled_exception() const { std::rethrow_exception(std::current_exception()); } - void return_value(T value) { result = std::move(value); } - - std::coroutine_handle<> previous; - T result{}; - }; - - task() noexcept : coroutine(nullptr) { } - explicit task(std::coroutine_handle h) : coroutine(h) { } - task(const task&) = delete; - task(task&& other) noexcept : coroutine(std::exchange(other.coroutine, nullptr)) { } - ~task() { if (coroutine) coroutine.destroy(); } - task& operator=(const task&) = delete; - task& operator=(task&& other) noexcept - { - if (std::addressof(other) != this) - { - if (coroutine) coroutine.destroy(); - coroutine = std::exchange(other.coroutine, nullptr); - } - return *this; - } - - struct awaiter final - { - awaiter() = delete; - explicit awaiter(std::coroutine_handle h) : coroutine(h) { } - constexpr bool await_ready() const { return false; } - T await_resume() { return std::move(coroutine.promise().result); } - auto await_suspend(std::coroutine_handle<> h) - { - coroutine.promise().previous = h; - return coroutine; - } - private: - std::coroutine_handle coroutine; - }; - awaiter operator co_await() { return awaiter{ coroutine }; } - T resume() - { - coroutine.resume(); - return std::move(coroutine.promise().result); - } - T operator()() - { - return resume(); - } - private: - std::coroutine_handle coroutine; - }; - - template<> - struct task - { - struct promise_type final - { - auto get_return_object() noexcept - { - return task(std::coroutine_handle::from_promise(*this)); - } - constexpr std::suspend_always initial_suspend() const { return {}; } - struct final_awaiter final - { - constexpr bool await_ready() const noexcept { return false; } - constexpr void await_resume() const noexcept {} - std::coroutine_handle<> - await_suspend(std::coroutine_handle h) const noexcept - { - // final_awaiter::await_suspend is called when the execution of the - // current coroutine (referred to by 'h') is about to finish. - // If the current coroutine was resumed by another coroutine via - // co_await get_task(), a handle to that coroutine has been stored - // as h.promise().previous. In that case, return the handle to resume - // the previous coroutine. - // Otherwise, return noop_coroutine(), whose resumption does nothing. - - if (auto previous = h.promise().previous; previous) - return previous; - else - return std::noop_coroutine(); - } - }; - final_awaiter final_suspend() const noexcept { return {}; } - void unhandled_exception() const { std::rethrow_exception(std::current_exception()); } - constexpr void return_void() const { } - - std::coroutine_handle<> previous; - }; - - task() noexcept : coroutine(nullptr) { } - explicit task(std::coroutine_handle h) : coroutine(h) { } - task(const task&) = delete; - task(task&& other) noexcept : coroutine(std::exchange(other.coroutine, nullptr)) { } - ~task() { if (coroutine) coroutine.destroy(); } - task& operator=(const task&) = delete; - task& operator=(task&& other) noexcept - { - if (std::addressof(other) != this) - { - if (coroutine) coroutine.destroy(); - coroutine = std::exchange(other.coroutine, nullptr); - } - return *this; - } - - struct awaiter final - { - awaiter() = delete; - explicit awaiter(std::coroutine_handle h) : coroutine(h) { } - constexpr bool await_ready() const { return false; } - constexpr void await_resume() const noexcept {} - auto await_suspend(std::coroutine_handle<> h) - { - coroutine.promise().previous = h; - return coroutine; - } - private: - std::coroutine_handle coroutine; - }; - awaiter operator co_await() { return awaiter{ coroutine }; } - void resume() - { - coroutine.resume(); - } - void operator()() - { - resume(); - } - private: - std::coroutine_handle coroutine; - }; -} // namespace ghostl diff --git a/src/circular_queue/task_completion_source.h b/src/circular_queue/task_completion_source.h deleted file mode 100644 index 6150e8d..0000000 --- a/src/circular_queue/task_completion_source.h +++ /dev/null @@ -1,177 +0,0 @@ -#pragma once -/* -task_completion_source.h - Implementation of a C++20 async coroutines task completion source. -Copyright (c) 2023 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 -*/ - -#include -#include -#include - -#if defined(__GNUC__) -#undef ALWAYS_INLINE_ATTR -#define ALWAYS_INLINE_ATTR __attribute__((always_inline)) -#else -#define ALWAYS_INLINE_ATTR -#endif - -namespace ghostl -{ -template -struct task_completion_source -{ - task_completion_source() = default; - task_completion_source(const task_completion_source& other) noexcept : state(other.state) { } - task_completion_source(task_completion_source&& other) noexcept - { - state = std::exchange(other.state, nullptr); - } - auto operator=(const task_completion_source& other) -> task_completion_source& - { - if (std::addressof(other) != this) - { - state = other.state; - } - return *this; - } - auto operator=(task_completion_source&& other) noexcept -> task_completion_source& - { - if (std::addressof(other) != this) - { - state = std::exchange(other.state, nullptr); - } - return *this; - } - auto set_value(T&& val) const -> void - { - for (bool expect{false}; !state->is_set.compare_exchange_strong(expect, true);) - return; - state->value = std::make_shared(std::move(val)); - std::atomic_thread_fence(std::memory_order_release); - for (bool expect{false}; !state->ready.compare_exchange_weak(expect, true); expect = false) {} - if (auto handle = state->coroutine.load(); handle && !handle.done()) { handle.resume(); } - } - auto set_value(const T& val) const -> void ALWAYS_INLINE_ATTR - { - T v(val); - set_value(std::move(v)); - } - [[nodiscard]] auto token() const { return awaiter(state); } -private: - struct state_type final - { - explicit state_type() {} - state_type(const state_type&) = delete; - state_type(state_type&& other) noexcept = delete; - auto operator=(const state_type&) -> state_type& = delete; - auto operator=(state_type&& other) noexcept -> state_type& = delete; - std::atomic is_set{false}; - std::atomic ready{false}; - std::atomic> coroutine{nullptr}; - std::shared_ptr value; - }; - struct awaiter final - { - awaiter() = delete; - explicit awaiter(std::shared_ptr _state) : state(std::move(_state)) {} - awaiter(const awaiter&) = default; - awaiter(awaiter&& other) noexcept = default; - auto operator=(const awaiter&) -> awaiter& = default; - auto operator=(awaiter&& other) noexcept -> awaiter& = default; - bool await_ready() const noexcept { return state->ready.exchange(true); } - void await_suspend(std::coroutine_handle<> handle) const noexcept - { - state->coroutine.store(handle); - state->ready.store(false); - } - T await_resume() const noexcept { return *state->value; } - private: - std::shared_ptr state; - }; - - std::shared_ptr state{std::make_shared()}; -}; - -template<> -struct task_completion_source -{ - task_completion_source() = default; - task_completion_source(const task_completion_source& other) noexcept : state(other.state) { } - task_completion_source(task_completion_source&& other) noexcept - { - state = std::exchange(other.state, nullptr); - } - auto operator=(const task_completion_source& other) -> task_completion_source& - { - if (std::addressof(other) != this) - { - state = other.state; - } - return *this; - } - auto operator=(task_completion_source&& other) noexcept -> task_completion_source& - { - if (std::addressof(other) != this) - { - state = std::exchange(other.state, nullptr); - } - return *this; - } - void set_value() const - { - for (bool expect{false}; !state->is_set.compare_exchange_strong(expect, true);) - return; - std::atomic_thread_fence(std::memory_order_release); - for (bool expect{false}; !state->ready.compare_exchange_weak(expect, true); expect = false) {} - if (auto handle = state->coroutine.load(); handle && !handle.done()) { handle.resume(); } - } - [[nodiscard]] auto token() const { return awaiter(state); } -private: - struct state_type final - { - explicit state_type() {} - state_type(const state_type&) = delete; - state_type(state_type&& other) noexcept = delete; - auto operator=(const state_type&) -> state_type& = delete; - auto operator=(state_type&& other) noexcept -> state_type& = delete; - std::atomic is_set{false}; - std::atomic ready{false}; - std::atomic> coroutine{nullptr}; - }; - struct awaiter final - { - awaiter() = delete; - explicit awaiter(std::shared_ptr _state) : state(std::move(_state)) {} - awaiter(const awaiter&) = default; - awaiter(awaiter&& other) noexcept = default; - auto operator=(const awaiter&) -> awaiter& = default; - auto operator=(awaiter&& other) noexcept -> awaiter& = default; - bool await_ready() const noexcept { return state->ready.exchange(true); } - void await_suspend(std::coroutine_handle<> handle) const noexcept - { - state->coroutine.store(handle); - state->ready.store(false); - } - constexpr void await_resume() const noexcept {} - private: - std::shared_ptr state; - }; - - std::shared_ptr state{std::make_shared()}; -}; - -} // namespace ghostl diff --git a/src/circular_queue/when_all.h b/src/circular_queue/when_all.h deleted file mode 100644 index a4ff94d..0000000 --- a/src/circular_queue/when_all.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once -/* -when_all.h - Implementation of a C++20 async coroutines when-all awaiter. -Copyright (c) 2023 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 -*/ - -#include "run_task.h" -#include "task_completion_source.h" - -#include - -namespace ghostl -{ - template - struct when_all final - { - static auto continuation(T result, size_t pos, - std::shared_ptr> results, - std::shared_ptr> remaining, - ghostl::task_completion_source> tcs) -> void - { - (*results)[pos] = std::move(result); - if (auto isremaining = -- * remaining; !isremaining) - { - tcs.set_value(std::move(*results)); - } - } - - when_all() = delete; - template explicit when_all(C&& _tasks) - { - C tasks = std::move(_tasks); - auto results = this->results; - auto remaining = this->remaining; - auto tcs = this->tcs; - for (ghostl::task& task : tasks) - { - auto pos = remaining->load(); - ++*remaining; - auto runner = ghostl::run_task(std::move(std::exchange(task, {}))); - runner.continue_with([pos, results, remaining, tcs](T result) { continuation(result, pos, results, remaining, tcs); }); - continuations->emplace_back(std::move(runner)); - } - results->resize(remaining->load()); - if (continuations->empty()) tcs.set_value(std::move(*results)); - else for (auto& runner : *continuations) - { - runner.resume(); - } - } - template explicit when_all(const C& tasks) = delete; - when_all(const when_all& other) = delete; - when_all(when_all&& other) = delete; - when_all& operator=(when_all& other) = delete; - when_all& operator=(when_all&& other) = delete; - auto operator ()() { - return tcs.token(); - } - private: - std::shared_ptr>> continuations{ - std::make_shared>>() }; - std::shared_ptr> results = std::make_shared>(); - std::shared_ptr> remaining{ - std::make_shared>(0) }; - ghostl::task_completion_source> tcs{}; - }; - template<> - struct when_all final - { - static auto continuation( - std::shared_ptr> remaining, - ghostl::task_completion_source<> tcs) -> void - { - if (auto isremaining = -- * remaining; !isremaining) - { - tcs.set_value(); - } - } - - when_all() = delete; - template explicit when_all(C&& _tasks) - { - C tasks = std::move(_tasks); - auto remaining = this->remaining; - auto tcs = this->tcs; - for (ghostl::task<>& task : tasks) - { - ++*remaining; - auto runner = ghostl::run_task<>(std::move(std::exchange(task, {}))); - runner.continue_with([remaining, tcs]() { continuation(remaining, tcs); }); - continuations->emplace_back(std::move(runner)); - } - if (continuations->empty()) tcs.set_value(); - else for (auto& runner : *continuations) - { - runner.resume(); - } - } - template explicit when_all(const C& tasks) = delete; - when_all(const when_all& other) = delete; - when_all(when_all&& other) = delete; - when_all& operator=(when_all& other) = delete; - when_all& operator=(when_all&& other) = delete; - auto operator ()() { - return tcs.token(); - } - private: - std::shared_ptr>> continuations{ - std::make_shared>>() }; - std::shared_ptr> remaining{ - std::make_shared>(0) }; - ghostl::task_completion_source<> tcs{}; - }; -} // namespace ghostl diff --git a/src/circular_queue/when_any.h b/src/circular_queue/when_any.h deleted file mode 100644 index c100186..0000000 --- a/src/circular_queue/when_any.h +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once -/* -when_any.h - Implementation of a C++20 async coroutines when-any awaiter. -Copyright (c) 2023 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 -*/ - -#include "run_task.h" -#include "task_completion_source.h" - -#include - -namespace ghostl -{ - template - struct when_any final - { - static auto continuation(T result, - std::shared_ptr> completed, - ghostl::task_completion_source tcs) -> void - { - if (auto isCompleted = completed->exchange(true); !isCompleted) - { - tcs.set_value(std::move(result)); - } - } - - when_any() = delete; - template explicit when_any(C&& _tasks) - { - C tasks = std::move(_tasks); - auto completed = this->completed; - auto tcs = this->tcs; - for (ghostl::task& task : tasks) - { - auto runner = ghostl::run_task(std::move(std::exchange(task, {}))); - runner.continue_with([completed, tcs](T result) { continuation(result, completed, tcs); }); - continuations->emplace_back(std::move(runner)); - } - if (continuations->empty()) tcs.set_value({}); - else for (auto& runner : *continuations) - { - runner.resume(); - } - } - template explicit when_any(const C& tasks) = delete; - when_any(const when_any& other) = delete; - when_any(when_any&& other) = delete; - ~when_any() { } - when_any& operator=(when_any& other) = delete; - when_any& operator=(when_any&& other) = delete; - auto operator ()() { - return tcs.token(); - } - private: - std::shared_ptr>> continuations{ - std::make_shared>>() }; - std::shared_ptr> completed{ - std::make_shared>(false) }; - ghostl::task_completion_source tcs{}; - }; - template<> - struct when_any final - { - static auto continuation( - std::shared_ptr> completed, - ghostl::task_completion_source<> tcs) -> void - { - if (auto isCompleted = completed->exchange(true); !isCompleted) - { - tcs.set_value(); - } - } - - when_any() = delete; - template explicit when_any(C&& _tasks) - { - C tasks = std::move(_tasks); - auto completed = this->completed; - auto tcs = this->tcs; - for (ghostl::task<>& task : tasks) - { - auto runner = ghostl::run_task<>(std::move(std::exchange(task, {}))); - runner.continue_with([completed, tcs]() { continuation(completed, tcs); }); - continuations->emplace_back(std::move(runner)); - } - if (continuations->empty()) tcs.set_value(); - else for (auto& runner : *continuations) - { - runner.resume(); - } - } - template explicit when_any(const C& tasks) = delete; - when_any(const when_any& other) = delete; - when_any(when_any&& other) = delete; - ~when_any() { } - when_any& operator=(when_any& other) = delete; - when_any& operator=(when_any&& other) = delete; - auto operator ()() { - return tcs.token(); - } - private: - std::shared_ptr>> continuations{ - std::make_shared>>() }; - std::shared_ptr> completed{ - std::make_shared>(false) }; - ghostl::task_completion_source<> tcs{}; - }; -} // namespace ghostl