WIP1
Some checks failed
CI & Unit Tests / Unit-Tests (push) Failing after 21s
CI & Unit Tests / Docs (push) Successful in 14s

This commit is contained in:
Erki 2025-01-30 17:22:37 +02:00
parent af9db5a1b0
commit 5af059f4c6
7 changed files with 335 additions and 1 deletions

View File

@ -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)

View File

@ -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)

9
Tests/coro.cpp Normal file
View File

@ -0,0 +1,9 @@
//
// Created by erki on 30/01/25.
//
#include <catch2/catch.hpp>
#include "skullc/coro/task.hpp"
#include "skullc/coro/scheduler.hpp"

14
coro/CMakeLists.txt Normal file
View File

@ -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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc>
$<INSTALL_INTERFACE:include>
)
## INSTALL
skullc_install_packages(skullc coro ${SKULLC_VERSION})

View File

@ -0,0 +1,2 @@
include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-config-version.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-targets.cmake)

View File

@ -0,0 +1,142 @@
//
// Created by erki on 30/01/25.
//
#pragma once
#include <algorithm>
#include <array>
#include <coroutine>
#include <cstdint>
#include <utility>
#include <vector>
#include "skullc/coro/task.hpp"
namespace skullc::coro
{
template <typename T>
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 <Task... T>
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<std::pair<uint64_t, std::coroutine_handle<>>> scheduled;
std::vector<TaskPoller*> pollers;
template <typename T0, typename... R>
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...);
}
}
};
}

View File

@ -0,0 +1,160 @@
//
// Created by erki on 30/01/25.
//
#pragma once
#include <coroutine>
#include <memory>
#include <optional>
namespace skullc::coro
{
template<typename T = void, bool lazy = true>
struct [[nodiscard]] task
{
using value_type = T;
struct task_promise;
using promise_type = task_promise;
task() = delete;
explicit task(std::coroutine_handle<promise_type> 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<T, void>)
{
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<T, void>)
return;
else
return std::move(coro_handle.promise().data.value());
}
std::coroutine_handle<promise_type> coro_handle;
};
return awaiter{my_handle};
}
private:
std::coroutine_handle<promise_type> my_handle;
};
template<typename T>
struct promise_setter
{
template<typename U>
void return_value(U&& u)
{
data.emplace(std::forward<U>(u));
}
std::optional<T> data;
};
template<>
struct promise_setter<void>
{
void return_void() {}
};
template<typename T, bool lazy>
struct task<T, lazy>::task_promise : public promise_setter<T>
{
task get_return_object() noexcept
{
return task{std::coroutine_handle<task_promise>::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<task::task_promise> h) noexcept
{
if constexpr (std::is_same_v<T, void>)
{
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