From 9e389e280fe1fd46f7db2cfdc2bd42b63ddfec89 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Wed, 4 Nov 2020 13:19:36 +0000 Subject: [PATCH] Add buffer descriptors --- include/etl/atomic/atomic_gcc_sync.h | 1 + include/etl/atomic/atomic_std.h | 1 + include/etl/buffer_descriptors.h | 219 ++++++++++++++++ test/CMakeLists.txt | 2 + test/test_atomic_std.cpp | 4 +- test/test_buffer_descriptors.cpp | 362 +++++++++++++++++++++++++++ test/test_string_view.cpp | 2 +- test/vs2019/etl.vcxproj | 10 +- test/vs2019/etl.vcxproj.filters | 6 + 9 files changed, 602 insertions(+), 5 deletions(-) create mode 100644 include/etl/buffer_descriptors.h create mode 100644 test/test_buffer_descriptors.cpp diff --git a/include/etl/atomic/atomic_gcc_sync.h b/include/etl/atomic/atomic_gcc_sync.h index c70a792d..190af045 100644 --- a/include/etl/atomic/atomic_gcc_sync.h +++ b/include/etl/atomic/atomic_gcc_sync.h @@ -749,6 +749,7 @@ namespace etl mutable volatile uintptr_t value; }; + typedef std::atomic atomic_bool; typedef etl::atomic atomic_char; typedef etl::atomic atomic_schar; typedef etl::atomic atomic_uchar; diff --git a/include/etl/atomic/atomic_std.h b/include/etl/atomic/atomic_std.h index 3cf82166..783352d6 100644 --- a/include/etl/atomic/atomic_std.h +++ b/include/etl/atomic/atomic_std.h @@ -556,6 +556,7 @@ namespace etl std::atomic value; }; + typedef std::atomic atomic_bool; typedef std::atomic atomic_char; typedef std::atomic atomic_schar; typedef std::atomic atomic_uchar; diff --git a/include/etl/buffer_descriptors.h b/include/etl/buffer_descriptors.h new file mode 100644 index 00000000..baf05b4c --- /dev/null +++ b/include/etl/buffer_descriptors.h @@ -0,0 +1,219 @@ +///\file + +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2020 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_BUFFER_DESCRIPTORS_INCLUDED +#define ETL_BUFFER_DESCRIPTORS_INCLUDED + +#include "platform.h" +#include "atomic.h" +#include "array.h" +#include "delegate.h" +#include "type_traits.h" +#include "static_assert.h" +#include "cyclic_value.h" + +#include + +#undef ETL_FILE +#define ETL_FILE "57" + +namespace etl +{ + //*************************************************************************** + /// buffer_descriptors + //*************************************************************************** + template + class buffer_descriptors + { + public: + + typedef TBuffer value_type; + typedef value_type* pointer; + typedef TSize size_type; + typedef TFlag flag_type; + + ETL_STATIC_ASSERT(etl::is_unsigned::value, "TSize must be unsigned"); + ETL_STATIC_ASSERT(etl::is_integral::value, "TSize must be integral"); + + static /*ETL_CONSTANT*/ const size_t N_BUFFERS = N_BUFFERS_; + static /*ETL_CONSTANT*/ const size_type BUFFER_SIZE = BUFFER_SIZE_; + + //********************************* + class descriptor + { + public: + + friend class buffer_descriptors; + + static /*ETL_CONSTANT*/ const size_type MAX_SIZE = buffer_descriptors::BUFFER_SIZE; + + //********************************* + descriptor() + : pbuffer(ETL_NULLPTR) + , in_use(false) + { + } + + //********************************* + ETL_CONSTEXPR pointer data() const + { + return pbuffer; + } + + //********************************* + ETL_CONSTEXPR size_type max_size() const + { + return BUFFER_SIZE; + } + + //********************************* + bool is_valid() const + { + return pbuffer != ETL_NULLPTR; + } + + //********************************* + bool is_allocated() const + { + return in_use.load(); + } + + //********************************* + bool is_released() const + { + return !in_use.load(); + } + + //********************************* + void release() + { + in_use.store(false); + } + + private: + + //********************************* + ETL_CONSTEXPR descriptor(TBuffer* pbuffer_) + : pbuffer(pbuffer_) + , in_use(false) + { + } + + //********************************* + void allocate() + { + in_use.store(true); + } + + //********************************* + descriptor(const descriptor&) ETL_DELETE; + descriptor& operator =(const descriptor&) ETL_DELETE; + + pointer pbuffer; + flag_type in_use; + }; + + typedef etl::delegate callback_type; + + //********************************* + buffer_descriptors(TBuffer* pbuffers_) + { + assert(descriptors[0].in_use.is_lock_free()); + + for (size_t i = 0U; i < N_BUFFERS; ++i) + { + descriptors[i].pbuffer = pbuffers_ + (i * BUFFER_SIZE); + } + } + + //********************************* + buffer_descriptors(TBuffer* pbuffers_, const callback_type& callback_) + : callback(callback_) + { + assert(descriptors[0].in_use.is_lock_free()); + + for (size_t i = 0U; i < N_BUFFERS; ++i) + { + descriptors[i].pbuffer = pbuffers_ + (i * BUFFER_SIZE); + } + } + + //********************************* + void set_callback(const callback_type& callback_) + { + callback = callback_; + } + + //********************************* + bool is_valid() const + { + return callback.is_valid(); + } + + //********************************* + void notify(descriptor& desc_, size_type count_) + { + // We have a valid callback? + if (callback.is_valid()) + { + // Set the relevant data. + callback(desc_, count_); + } + } + + //********************************* + descriptor& allocate() + { + if (descriptors[next].is_released()) + { + size_t index = next++; + + descriptors[index].allocate(); + + return descriptors[index]; + } + else + { + static descriptor null_descriptor; + return null_descriptor; + } + } + + private: + + callback_type callback; + etl::array descriptors; + etl::cyclic_value next; + }; +} + +#undef ETL_FILE + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 97fb199d..224038b6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,6 +35,7 @@ set(TEST_SOURCE_FILES test_bloom_filter.cpp test_bresenham_line.cpp test_bsd_checksum.cpp + test_buffer_descriptors.cpp test_callback_service.cpp test_callback_timer.cpp test_checksum.cpp @@ -54,6 +55,7 @@ set(TEST_SOURCE_FILES test_error_handler.cpp test_exception.cpp test_fixed_iterator.cpp + test_flags test_flat_map.cpp test_flat_multimap.cpp test_flat_multiset.cpp diff --git a/test/test_atomic_std.cpp b/test/test_atomic_std.cpp index ea98228b..3dbb24c6 100644 --- a/test/test_atomic_std.cpp +++ b/test/test_atomic_std.cpp @@ -487,8 +487,8 @@ namespace CHECK_EQUAL(compare_expected, test_expected); CHECK_EQUAL(compare.load(), test.load()); } - - //========================================================================= + + //************************************************************************* #if REALTIME_TEST #if defined(ETL_TARGET_OS_WINDOWS) // Only Windows priority is currently supported diff --git a/test/test_buffer_descriptors.cpp b/test/test_buffer_descriptors.cpp new file mode 100644 index 00000000..582c43e8 --- /dev/null +++ b/test/test_buffer_descriptors.cpp @@ -0,0 +1,362 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2020 jwellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "UnitTest++/UnitTest++.h" + +#include +#include +#include +#include +#include +#include +#include +#include "etl/queue_spsc_atomic.h" + +#include "etl/buffer_descriptors.h" + +#if ETL_HAS_ATOMIC + +#if defined(ETL_TARGET_OS_WINDOWS) + #include +#endif + +#define REALTIME_TEST 0 + +namespace +{ + constexpr size_t BUFFER_SIZE = 16U; + constexpr size_t N_BUFFERS = 4U; + constexpr size_t DATA_COUNT = BUFFER_SIZE / 2; + + using BD = etl::buffer_descriptors; + using Descriptor = BD::descriptor; + using NotificationCallback = BD::callback_type; + + //*********************************** + struct Receiver + { + void receive(Descriptor& desc_, BD::size_type count_) + { + pbuffer = desc_.data(); + count = count_; + } + + BD::pointer pbuffer; + BD::size_type count; + }; + + SUITE(test_buffer_descriptors) + { + //************************************************************************* + TEST(test_constructor_plus_buffer) + { + char buffers[N_BUFFERS][BUFFER_SIZE]; + + BD bd(&buffers[0][0]); + + CHECK_EQUAL(N_BUFFERS, bd.N_BUFFERS); + CHECK_EQUAL(BUFFER_SIZE, bd.BUFFER_SIZE); + CHECK(!bd.is_valid()); + } + + //************************************************************************* + TEST(test_constructor_plus_buffer_and_callback) + { + Receiver receiver; + + char buffers[N_BUFFERS][BUFFER_SIZE]; + NotificationCallback callback = NotificationCallback::create(receiver); + + BD bd(&buffers[0][0], callback); + + CHECK_EQUAL(N_BUFFERS, bd.N_BUFFERS); + CHECK_EQUAL(BUFFER_SIZE, bd.BUFFER_SIZE); + CHECK(bd.is_valid()); + } + + //************************************************************************* + TEST(test_constructor_plus_buffer_set_callback) + { + Receiver receiver; + + char buffers[N_BUFFERS][BUFFER_SIZE]; + NotificationCallback callback = NotificationCallback::create(receiver); + + BD bd(&buffers[0][0]); + bd.set_callback(callback); + + CHECK_EQUAL(N_BUFFERS, bd.N_BUFFERS); + CHECK_EQUAL(BUFFER_SIZE, bd.BUFFER_SIZE); + CHECK(bd.is_valid()); + } + + //************************************************************************* + TEST(test_buffers) + { + Receiver receiver; + + char buffers[N_BUFFERS][BUFFER_SIZE]; + NotificationCallback callback = NotificationCallback::create(receiver); + + BD bd(&buffers[0][0], callback); + + for (size_t i = 0U; i < N_BUFFERS; ++i) + { + Descriptor& desc = bd.allocate(); + + CHECK_EQUAL(BUFFER_SIZE, desc.max_size()); + CHECK_EQUAL(uintptr_t(&buffers[i][0]), uintptr_t(desc.data())); + } + } + + //************************************************************************* + TEST(test_notifications) + { + Receiver receiver; + + std::array test = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0 }; + + char buffers[N_BUFFERS][BUFFER_SIZE]; + std::fill(&buffers[0][0], &buffers[N_BUFFERS - 1][0] + BUFFER_SIZE , 0U); + + NotificationCallback callback = NotificationCallback::create(receiver); + + BD bd(&buffers[0][0], callback); + + for (size_t i = 0U; i < N_BUFFERS; ++i) + { + Descriptor& desc = bd.allocate(); + + CHECK(desc.is_valid()); + + std::copy(test.begin(), test.begin() + DATA_COUNT, desc.data()); + bd.notify(desc, DATA_COUNT); + desc.release(); + + CHECK_EQUAL(DATA_COUNT, receiver.count); + CHECK_EQUAL(uintptr_t(&buffers[i][0]), uintptr_t(receiver.pbuffer)); + CHECK_ARRAY_EQUAL(test.data(), desc.data(), BUFFER_SIZE); + } + } + + //************************************************************************* + TEST(test_allocate_overflow) + { + Receiver receiver; + + char buffers[N_BUFFERS][BUFFER_SIZE]; + + NotificationCallback callback = NotificationCallback::create(receiver); + + BD bd(&buffers[0][0], callback); + + // Use up all of the descriptors. + for (size_t i = 0U; i < N_BUFFERS; ++i) + { + Descriptor& desc = bd.allocate(); + CHECK(desc.is_valid()); + } + + Descriptor& desc = bd.allocate(); + CHECK(!desc.is_valid()); + } + + //************************************************************************* + TEST(test_allocate_release_rollover) + { + Receiver receiver; + std::queue desc_queue; + + char buffers[N_BUFFERS][BUFFER_SIZE]; + + NotificationCallback callback = NotificationCallback::create(receiver); + + BD bd(&buffers[0][0], callback); + + // Use up all of the descriptors, then release/allocate for the rest. + for (size_t i = 0U; i < (N_BUFFERS * 2); ++i) + { + Descriptor& desc = bd.allocate(); + desc_queue.push(&desc); + + CHECK(desc.is_valid()); + + if (i >= (N_BUFFERS - 1)) + { + desc_queue.front()->release(); + desc_queue.pop(); + } + } + } + + //************************************************************************* + TEST(test_descriptors) + { + char buffers[N_BUFFERS][BUFFER_SIZE]; + + BD bd(&buffers[0][0]); + + Descriptor& desc1 = bd.allocate(); + Descriptor& desc2 = bd.allocate(); + Descriptor& desc3 = bd.allocate(); + Descriptor& desc4 = bd.allocate(); + + CHECK(desc1.is_allocated()); + CHECK(desc2.is_allocated()); + CHECK(desc3.is_allocated()); + CHECK(desc4.is_allocated()); + + CHECK(desc1.data() == &buffers[0][0]); + CHECK(desc2.data() == &buffers[1][0]); + CHECK(desc3.data() == &buffers[2][0]); + CHECK(desc4.data() == &buffers[3][0]); + + CHECK(desc1.max_size() == BUFFER_SIZE); + CHECK(desc2.max_size() == BUFFER_SIZE); + CHECK(desc3.max_size() == BUFFER_SIZE); + CHECK(desc4.max_size() == BUFFER_SIZE); + + CHECK(desc1.MAX_SIZE == BUFFER_SIZE); + CHECK(desc2.MAX_SIZE == BUFFER_SIZE); + CHECK(desc3.MAX_SIZE == BUFFER_SIZE); + CHECK(desc4.MAX_SIZE == BUFFER_SIZE); + + CHECK(desc1.is_valid()); + CHECK(desc2.is_valid()); + CHECK(desc3.is_valid()); + CHECK(desc4.is_valid()); + + desc1.release(); + desc2.release(); + desc3.release(); + desc4.release(); + + CHECK(desc1.is_released()); + CHECK(desc2.is_released()); + CHECK(desc3.is_released()); + CHECK(desc4.is_released()); + } + + //************************************************************************* +#if REALTIME_TEST + +#if defined(ETL_TARGET_OS_WINDOWS) // Only Windows priority is currently supported +#define RAISE_THREAD_PRIORITY SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) +#define FIX_PROCESSOR_AFFINITY1 SetThreadAffinityMask(GetCurrentThread(), 1) +#define FIX_PROCESSOR_AFFINITY2 SetThreadAffinityMask(GetCurrentThread(), 2) +#else +#define RAISE_THREAD_PRIORITY +#define FIX_PROCESSOR_AFFINITY1 +#define FIX_PROCESSOR_AFFINITY2 +#endif + + etl::atomic_bool start = false; + + //********************************* + struct Notification + { + Descriptor* pdesc; + BD::size_type count; + }; + + etl::queue_spsc_atomic desc_queue; + + //********************************* + void Callback(Descriptor& desc_, BD::size_type count_) + { + desc_queue.push(Notification{ &desc_, count_ }); + } + + //********************************* + void Producer() + { + static char buffers[N_BUFFERS][BUFFER_SIZE]; + + BD bd(&buffers[0][0], NotificationCallback::create()); + + RAISE_THREAD_PRIORITY; + FIX_PROCESSOR_AFFINITY1; + + // Wait for the start flag. + while (!start.load()); + + for (int i = 0; i < 10000000; ++i) + { + Descriptor* pdesc; + + // Wait unitl we cam allocate a descriptor. + do + { + pdesc = &bd.allocate(); + } while (pdesc->is_valid() == false); + + // Send a notification to the callback function. + bd.notify(*pdesc, BUFFER_SIZE); + } + } + + //********************************* + void Consumer() + { + RAISE_THREAD_PRIORITY; + FIX_PROCESSOR_AFFINITY2; + + // Wait for the start flag. + while (!start.load()); + + for (int i = 0; i < 10000000;) + { + Notification n; + + // Try to get a notification from the queue. + if (desc_queue.pop(n)) + { + CHECK_EQUAL(BUFFER_SIZE, n.count); + CHECK(n.pdesc->is_allocated()); + n.pdesc->release(); + ++i; + } + } + } + + //********************************* + TEST(test_multi_thread) + { + std::thread t1(Producer); + std::thread t2(Consumer); + + start.store(true); + + t1.join(); + t2.join(); + } +#endif + }; +} + +#endif diff --git a/test/test_string_view.cpp b/test/test_string_view.cpp index c1445b6d..599e06f5 100644 --- a/test/test_string_view.cpp +++ b/test/test_string_view.cpp @@ -51,7 +51,7 @@ namespace std::string text_shorter = "Hello Worl"; std::string text_different = "Goodbye!!!!"; - char ctext[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' }; + char ctext[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0' }; char* pctext = ctext; std::ostream& operator << (std::ostream& os, const View& view) diff --git a/test/vs2019/etl.vcxproj b/test/vs2019/etl.vcxproj index cde64d34..d5e0a949 100644 --- a/test/vs2019/etl.vcxproj +++ b/test/vs2019/etl.vcxproj @@ -135,6 +135,7 @@ true v142 Unicode + true Application @@ -171,6 +172,7 @@ true v142 Unicode + true Application @@ -549,8 +551,9 @@ ../../../unittest-cpp/;../../include;../../test - false + true stdcpp17 + ProgramDatabase Console @@ -675,8 +678,9 @@ ./;../../../unittest-cpp/;../../include;../../test - false + true stdcpp17 + ProgramDatabase Console @@ -1247,6 +1251,7 @@ + @@ -1500,6 +1505,7 @@ + diff --git a/test/vs2019/etl.vcxproj.filters b/test/vs2019/etl.vcxproj.filters index 0bfc9539..7a04c4d7 100644 --- a/test/vs2019/etl.vcxproj.filters +++ b/test/vs2019/etl.vcxproj.filters @@ -891,6 +891,9 @@ ETL\Utilities + + ETL\Utilities + @@ -1415,6 +1418,9 @@ Source Files + + Source Files +