From 5af059f4c67a6d8fe44ff094dc5cb2dc59857628 Mon Sep 17 00:00:00 2001 From: Erki Date: Thu, 30 Jan 2025 17:22:37 +0200 Subject: [PATCH] WIP1 --- CMakeLists.txt | 5 + Tests/CMakeLists.txt | 4 +- Tests/coro.cpp | 9 ++ coro/CMakeLists.txt | 14 +++ coro/coro-config.cmake.in | 2 + coro/inc/skullc/coro/scheduler.hpp | 142 +++++++++++++++++++++++++ coro/inc/skullc/coro/task.hpp | 160 +++++++++++++++++++++++++++++ 7 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 Tests/coro.cpp create mode 100644 coro/CMakeLists.txt create mode 100644 coro/coro-config.cmake.in create mode 100644 coro/inc/skullc/coro/scheduler.hpp create mode 100644 coro/inc/skullc/coro/task.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a6f4bf5..3cd4e08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,10 +16,15 @@ option(SKULLC_WITH_TESTS "Enable unit testing." OFF) option(SKULLC_WITH_HAL "Enable the compiling and deployment of the HAL dependent sections." OFF) option(SKULLC_USE_HAL_ST "Enable the ST HAl when SKULLC_WITH_HAL is enabled." OFF) option(SKULLC_USE_HAL_ESP "Enable the ESP HAL when SKULLC_WITH_HAL is enabled." OFF) +option(SKULLC_WITH_CORO "Enable coroutine support library." OFF) option(SKULLC_WITH_DOCS "Enable documentation building." OFF) include(skullc-install) +if(SKULLC_WITH_CORO) + add_subdirectory(coro) +endif() + add_subdirectory(Peripherals) add_subdirectory(Utility) add_subdirectory(Messaging) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 7d01952..430d92f 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -31,19 +31,21 @@ add_executable(tests enum_helpers.cpp bytes.cpp filters.cpp + coro.cpp ) target_link_libraries(tests PUBLIC skullc::utility skullc::messaging + skullc::coro skullc::peripherals Catch2::Catch2 ) set_target_properties(tests PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 23 ) list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib) diff --git a/Tests/coro.cpp b/Tests/coro.cpp new file mode 100644 index 0000000..7a74ab1 --- /dev/null +++ b/Tests/coro.cpp @@ -0,0 +1,9 @@ +// +// Created by erki on 30/01/25. +// + +#include + +#include "skullc/coro/task.hpp" +#include "skullc/coro/scheduler.hpp" + diff --git a/coro/CMakeLists.txt b/coro/CMakeLists.txt new file mode 100644 index 0000000..636a865 --- /dev/null +++ b/coro/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) + +add_library(coro INTERFACE) + +add_library(skullc::coro ALIAS coro) + +target_include_directories(coro + INTERFACE + $ + $ +) + +## INSTALL +skullc_install_packages(skullc coro ${SKULLC_VERSION}) diff --git a/coro/coro-config.cmake.in b/coro/coro-config.cmake.in new file mode 100644 index 0000000..15c02e2 --- /dev/null +++ b/coro/coro-config.cmake.in @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-config-version.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-targets.cmake) diff --git a/coro/inc/skullc/coro/scheduler.hpp b/coro/inc/skullc/coro/scheduler.hpp new file mode 100644 index 0000000..4732332 --- /dev/null +++ b/coro/inc/skullc/coro/scheduler.hpp @@ -0,0 +1,142 @@ +// +// Created by erki on 30/01/25. +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "skullc/coro/task.hpp" + +namespace skullc::coro +{ + +template +concept Task = requires(T task) +{ + { + task.get_handle().resume() + }; +}; + +struct TaskPoller { + virtual ~TaskPoller() = default; + + virtual bool isReady() = 0; + std::coroutine_handle<> stored_coro; +}; + +class Scheduler +{ + constexpr static auto cmp = [](const auto &a, const auto &b) { + return a.first > b.first; + }; + +public: + Scheduler() + : Scheduler(16) + { } + + Scheduler(const Scheduler&) = delete; + Scheduler(Scheduler&&) = delete; + Scheduler &operator=(const Scheduler&) = delete; + Scheduler &operator=(Scheduler&&) = delete; + + explicit Scheduler(uint num_tasks) + { + scheduled.reserve(num_tasks); + } + + /** \brief Start a new task. The task can terminate by calling co_return; . */ + void start_task(task<> task, uint64_t when = 0) + { + schedule(task.get_handle(), when); + } + + template + void start_tasks(T... tasks) { + start(tasks...); + } + + /** \brief Schedule a coroutine for future restart. + * \details This routine should only be called on the same core and from + * regular execution code. Do not call this from an interrupt. + * @param handle + * @param when + */ + void schedule(std::coroutine_handle<> handle, uint64_t when) + { + scheduled.push_back({when, handle}); + std::push_heap(scheduled.begin(), scheduled.end(), cmp); + } + + void loop(const uint64_t& current_time) + { + if (!scheduled.empty()) + { + auto min_time = scheduled.front(); + if (min_time.first < current_time) + { + std::pop_heap(scheduled.begin(), scheduled.end(), cmp); + scheduled.pop_back(); + if (min_time.second) + min_time.second.resume(); + } + } + + bool pollers_need_updating = false; + for (auto* poller : pollers) + { + if (poller->isReady()) + { + schedule(poller->stored_coro, current_time); + poller->stored_coro = nullptr; + pollers_need_updating = true; + } + } + + if (pollers_need_updating) + pollers.erase(std::remove_if(pollers.begin(), pollers.end(), + [](const auto* poller) { return poller->stored_coro == nullptr; })); + } + + void add_poller(TaskPoller* poller) + { + pollers.push_back(poller); + } + + bool remove(std::coroutine_handle<> handle) + { + for (auto &p : scheduled) + if (p.second == handle) + { + std::swap(p, scheduled.back()); + scheduled.pop_back(); + std::make_heap(scheduled.begin(), scheduled.end(), cmp); + return true; + } + return false; + } + +private: + /// \brief Heap of scheduled coroutines. Top of the heap is earliest time + std::vector>> scheduled; + std::vector pollers; + + template + void start(T0 &t0, R &...r) + { + /// Start the first task. As soon as it co_await anything, it will re-queue itself. + t0.get_handle().resume(); + if constexpr (sizeof...(r) > 0) { + start(r...); + } + } +}; + +} \ No newline at end of file diff --git a/coro/inc/skullc/coro/task.hpp b/coro/inc/skullc/coro/task.hpp new file mode 100644 index 0000000..9c1d53a --- /dev/null +++ b/coro/inc/skullc/coro/task.hpp @@ -0,0 +1,160 @@ +// +// Created by erki on 30/01/25. +// + +#pragma once + +#include +#include +#include + +namespace skullc::coro +{ + +template +struct [[nodiscard]] task +{ + using value_type = T; + struct task_promise; + using promise_type = task_promise; + + task() = delete; + + explicit task(std::coroutine_handle handle) + : my_handle(handle) + { + } + + task(task&& t) noexcept : my_handle(t.my_handle) { t.my_handle = nullptr; } + + task(const task&) = delete; + task& operator=(const task&) = delete; + + ~task() + { + if constexpr (!std::is_same_v) + { + if (my_handle) + my_handle.destroy(); + } + } + + auto get_handle() const { return my_handle; } + void detach() { my_handle = nullptr; } + + task& operator=(task&& other) noexcept + { + if (std::addressof(other) != this) + { + if (my_handle) + { + my_handle.destroy(); + } + + my_handle = other.my_handle; + other.my_handle = nullptr; + } + + return *this; + } + + auto operator co_await() noexcept + { + struct awaiter + { + bool await_ready() const noexcept + { + return !coro_handle || coro_handle.done(); + } + + std::coroutine_handle<> + await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept + { + coro_handle.promise().set_continuation(awaiting_coroutine); + return coro_handle; + } + T await_resume() noexcept + { + if constexpr (std::is_same_v) + return; + else + return std::move(coro_handle.promise().data.value()); + } + + std::coroutine_handle coro_handle; + }; + return awaiter{my_handle}; + } + +private: + std::coroutine_handle my_handle; +}; + +template +struct promise_setter +{ + template + void return_value(U&& u) + { + data.emplace(std::forward(u)); + } + std::optional data; +}; + +template<> +struct promise_setter +{ + void return_void() {} +}; + + +template +struct task::task_promise : public promise_setter +{ + + task get_return_object() noexcept + { + return task{std::coroutine_handle::from_promise(*this)}; + }; + + auto initial_suspend() const noexcept + { + if constexpr (lazy) + return std::suspend_always{}; + else + return std::suspend_never{}; + } + + auto final_suspend() const noexcept + { + struct awaiter + { + bool await_ready() const noexcept { return false; } + + void await_resume() const noexcept {} + + std::coroutine_handle<> + await_suspend(std::coroutine_handle h) noexcept + { + if constexpr (std::is_same_v) + { + auto continuation = h.promise().continuation; + h.destroy(); + return continuation; + } + else + return h.promise().continuation; + } + }; + return awaiter{}; + } + + void unhandled_exception() noexcept { std::terminate(); } + + void set_continuation(std::coroutine_handle<> handle) { continuation = handle; } + +private: + std::coroutine_handle<> continuation = std::noop_coroutine(); +}; + +}// namespace skullc::coro