WIP1
This commit is contained in:
parent
af9db5a1b0
commit
5af059f4c6
@ -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)
|
||||
|
||||
@ -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
9
Tests/coro.cpp
Normal 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
14
coro/CMakeLists.txt
Normal 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})
|
||||
2
coro/coro-config.cmake.in
Normal file
2
coro/coro-config.cmake.in
Normal file
@ -0,0 +1,2 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-config-version.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-targets.cmake)
|
||||
142
coro/inc/skullc/coro/scheduler.hpp
Normal file
142
coro/inc/skullc/coro/scheduler.hpp
Normal 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...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
160
coro/inc/skullc/coro/task.hpp
Normal file
160
coro/inc/skullc/coro/task.hpp
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user