diff --git a/CMakeLists.txt b/CMakeLists.txt index b47f745..baf3300 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -set(SKULLC_VERSION 0.1.0) +set(SKULLC_VERSION 0.2.0) project(skullc VERSION ${SKULLC_VERSION} @@ -19,6 +19,7 @@ include(skullc-install) add_subdirectory(Peripherals) add_subdirectory(Utility) +add_subdirectory(CppTick) add_subdirectory(Messaging) if(SKULLC_WITH_TESTS) diff --git a/CppTick/CMakeLists.txt b/CppTick/CMakeLists.txt new file mode 100644 index 0000000..49243ee --- /dev/null +++ b/CppTick/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) + +add_library(cpptick INTERFACE) +add_library(skullc::cpptick ALIAS cpptick) + +target_include_directories(cpptick + INTERFACE + $ + $ +) + +set_target_properties(cpptick + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED TRUE +) + +target_link_libraries(cpptick + INTERFACE + skullc::utility +) + +skullc_install_packages(skullc cpptick ${SKULLC_VERSION}) diff --git a/CppTick/Inc/cpptick/cpptick.hpp b/CppTick/Inc/cpptick/cpptick.hpp new file mode 100644 index 0000000..27a6bec --- /dev/null +++ b/CppTick/Inc/cpptick/cpptick.hpp @@ -0,0 +1,235 @@ +// +// Created by erki on 12/09/23. +// + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace cpptick +{ + +template +concept TrivialClass = std::is_trivial_v && std::is_trivially_destructible_v; + +template +concept CopyableClass = !TrivialClass && std::copy_constructible; + +template +concept SlotArgument = TrivialClass || CopyableClass; + +struct ArgStorage +{ + std::array buffer; + std::array pointer_buffer; + std::array::iterator pointer_buffer_head; + std::size_t space_remaining; + + ArgStorage() = default; + ArgStorage(const ArgStorage&) = default; + ArgStorage(ArgStorage&&) = default; + + void reset() + { + space_remaining = buffer.size(); + pointer_buffer_head = pointer_buffer.begin(); + *pointer_buffer_head = buffer.data(); + } + + template + void pushArg(T&& arg) + { + if (pointer_buffer_head == pointer_buffer.end() || space_remaining == 0) + std::exit(1); + + void* memory_location = *pointer_buffer_head; + + if (std::align(alignof(T), sizeof(T), memory_location, space_remaining) == nullptr) + std::exit(2); + + std::memcpy(memory_location, &arg, sizeof(T)); + pointer_buffer_head++; + if (pointer_buffer_head != pointer_buffer.end()) + { + *pointer_buffer_head = (char*)memory_location + sizeof(T); + space_remaining -= sizeof(T); + } + } + + template + void pushArg(T&& arg) + { + if (pointer_buffer_head == pointer_buffer.end() || space_remaining == 0) + std::exit(1); + + void* memory_location = *pointer_buffer_head; + + if (std::align(alignof(T), sizeof(T), memory_location, space_remaining) == nullptr) + std::exit(2); + + new (memory_location) T{arg}; + pointer_buffer_head++; + if (pointer_buffer_head != pointer_buffer.end()) + { + *pointer_buffer_head = (char*)memory_location + sizeof(T); + space_remaining -= sizeof(T); + } + } + + template + void cleanUp(const std::size_t) + { } + + template + void cleanUp(const std::size_t index) + { + T* item = reinterpret_cast(pointer_buffer[index]); + item->~T(); + } + + template + void cleanUp(std::index_sequence) + { + (cleanUp(Is), ...); + } + + template + void cleanUp() + { + cleanUp(std::make_index_sequence{}); + } + + template + T& at(const std::size_t idx) + { + return *((T*)pointer_buffer[idx]); + } + + template + const T& at(const std::size_t idx) const + { + return *((T*)pointer_buffer[idx]); + } +}; + +struct Scheduler +{ + using StoredCall = std::pair*, ArgStorage>; + Utility::Ringbuffer stored_calls; + + void tick() + { + if (!stored_calls.empty()) + { + auto* f = stored_calls.begin()->first; + auto& args = stored_calls.begin()->second; + (*f)(args); + stored_calls.pop_front(); + } + } + + ArgStorage& storeCall(Utility::IFunction* call) + { + // @todo: handle overflow... + + // still constructs double. + stored_calls.emplace_back(call, ArgStorage{}); + auto& storage = stored_calls.back().second; + storage.reset(); + return storage; + } +}; + +template +struct Slot; + +template +struct Slot +{ + std::array*, 12> signals; + Scheduler* scheduler; + + Utility::FunctionOwned, void (ArgStorage&)> invoke_ptr; + + Slot() = delete; + explicit Slot(Scheduler* sched) + : scheduler(sched) + , invoke_ptr(*this, &Slot::callUp) + { + signals.fill(nullptr); + } + + Slot(const Slot&) = delete; + Slot(Slot&&) = delete; + Slot& operator=(const Slot&) = delete; + Slot& operator=(Slot&&) = delete; + + void connect(Utility::IFunction* signal) + { + for (auto*& callable : signals) + { + if (callable == nullptr) + { + callable = signal; + break; + } + } + } + + Utility::IFunction* connect(R (*func)(Args...)) + { + auto* f = new Utility::Function(func); + + connect(f); + + return f; + } + + template + Utility::IFunction* connect(Source& src, R (Source::* func)(Args...)) + { + auto* f = new Utility::FunctionOwned(src, func); + + connect(f); + + return f; + } + + template + void callUpFunction(Utility::IFunction* func, ArgStorage& args, std::index_sequence) + { + (*func)(std::forward(args.at(Is))...); + } + + void callUpFunction(Utility::IFunction* func, ArgStorage& args) + { + callUpFunction(func, args, std::make_index_sequence{}); + } + + void callUp(ArgStorage& args) + { + for (auto* f : signals) + { + if (f) + callUpFunction(f, args); + else + break; + } + + args.cleanUp(); + } + + void invoke(Args... args) + { + ArgStorage& storage = scheduler->storeCall(&invoke_ptr); + (storage.pushArg(std::forward(args)), ...); + } +}; + +} diff --git a/CppTick/cpptick-config.cmake.in b/CppTick/cpptick-config.cmake.in new file mode 100644 index 0000000..21a6740 --- /dev/null +++ b/CppTick/cpptick-config.cmake.in @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_LIST_DIR}/skullc-cpptick-config-version.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/skullc-cpptick-targets.cmake) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 7d01952..69185ad 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(tests enum_helpers.cpp bytes.cpp filters.cpp + cpptick.cpp ) target_link_libraries(tests @@ -39,11 +40,12 @@ target_link_libraries(tests skullc::messaging skullc::peripherals Catch2::Catch2 + skullc::cpptick ) set_target_properties(tests PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 ) list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) diff --git a/Tests/cpptick.cpp b/Tests/cpptick.cpp new file mode 100644 index 0000000..2a45187 --- /dev/null +++ b/Tests/cpptick.cpp @@ -0,0 +1,98 @@ +// +// Created by erki on 12/09/23. +// + +#include + +#include + +namespace +{ + +int callback_count = 0; + +void callbackByOne() +{ + callback_count += 1; +} + +void callbackByN(int to_add) +{ + callback_count += to_add; +} + +} + +TEST_CASE("Slot calls function properly with void args.", "[cpptick]") +{ + cpptick::Scheduler scheduler; + + cpptick::Slot slot(&scheduler); + auto* f = slot.connect(callbackByOne); + + callback_count = 0; + REQUIRE(callback_count == 0); + + SECTION("invoke() calls the slot appropriately.") + { + slot.invoke(); + CHECK(callback_count == 0); + + scheduler.tick(); + CHECK(callback_count == 1); + + SECTION("Second tick doesn't invoke the slot again.") + { + scheduler.tick(); + CHECK(callback_count == 1); + } + + SECTION("Second invoke() will call the slot again.") + { + slot.invoke(); + scheduler.tick(); + CHECK(callback_count == 2); + } + } + + SECTION("Multiple calls queue properly.") + { + const int count = 3; + for (int i = 0; i < count; i++) + { + slot.invoke(); + } + + for (int i = 0; i < count; i++) + { + CHECK(callback_count == i); + scheduler.tick(); + CHECK(callback_count == i + 1); + } + } + + delete f; +} + +TEST_CASE("Slot calls function properly with args.", "[cpptick]") +{ + cpptick::Scheduler scheduler; + + cpptick::Slot slot(&scheduler); + auto* f = slot.connect(callbackByN); + + callback_count = 0; + REQUIRE(callback_count == 0); + + SECTION("invoke() passes argument appropriately.") + { + const int to_add = 5; + slot.invoke(to_add); + CHECK(callback_count == 0); + + scheduler.tick(); + CHECK(callback_count == to_add); + } + + delete f; +}