WIP2
This commit is contained in:
parent
5af059f4c6
commit
4d897ad5c6
@ -11,15 +11,21 @@
|
||||
namespace Peripherals::Hal
|
||||
{
|
||||
|
||||
enum class Edge
|
||||
{
|
||||
Rising,
|
||||
Falling
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept Gpio = requires (T t, const T ct) {
|
||||
concept Gpio = requires(T t, const T ct) {
|
||||
t.set(true);
|
||||
t.toggle();
|
||||
{ ct.read() } -> std::same_as<bool>;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept SerialInterface = requires (T t, std::uint8_t* data, const std::uint32_t len) {
|
||||
concept SerialInterface = requires(T t, std::uint8_t* data, const std::uint32_t len) {
|
||||
{ t.transmit(data, len) } -> std::same_as<bool>;
|
||||
{ t.transmit(std::array<std::uint8_t, 10>{}) } -> std::same_as<bool>;
|
||||
{ t.receive(data, len) } -> std::same_as<bool>;
|
||||
@ -27,11 +33,11 @@ concept SerialInterface = requires (T t, std::uint8_t* data, const std::uint32_t
|
||||
};
|
||||
|
||||
template<typename T, typename RegName>
|
||||
concept RegisterInterface = requires (T t, RegName reg, std::uint8_t* data, const std::uint32_t len) {
|
||||
concept RegisterInterface = requires(T t, RegName reg, std::uint8_t* data, const std::uint32_t len) {
|
||||
t.writerRegister(reg, std::uint8_t{});
|
||||
t.writeRegisterMultibyte(reg, data, len);
|
||||
{ t.readRegister(reg, std::uint32_t{}) } -> std::same_as<std::uint8_t>;
|
||||
t.readRegisterMultibyte(reg, data, len, std::uint32_t{});
|
||||
};
|
||||
|
||||
}
|
||||
}// namespace Peripherals::Hal
|
||||
|
||||
@ -20,8 +20,8 @@ struct Gpio
|
||||
|
||||
Gpio() = delete;
|
||||
explicit Gpio(gpio_num_t gpio, const bool inverted)
|
||||
: gpio(gpio), inverted(inverted)
|
||||
{ }
|
||||
: gpio(gpio), inverted(inverted)
|
||||
{}
|
||||
|
||||
void set(const bool& state)
|
||||
{
|
||||
@ -39,13 +39,13 @@ struct Gpio
|
||||
static_assert(Peripherals::Hal::Gpio<Gpio>);
|
||||
|
||||
#define CREATE_GPIO(name) \
|
||||
Peripherals::Hal::Esp::Gpio{name, false}
|
||||
Peripherals::Hal::Esp::Gpio { name, false }
|
||||
|
||||
#define CREATE_INV_GPIO(name) \
|
||||
Peripherals::Hal::Esp::Gpio{name, true}
|
||||
Peripherals::Hal::Esp::Gpio { name, true }
|
||||
|
||||
}
|
||||
}// namespace Peripherals::Hal::Esp
|
||||
|
||||
#else
|
||||
# warning "ESP HAL included without SKULLC_USE_HAL_ESP being defined."
|
||||
#warning "ESP HAL included without SKULLC_USE_HAL_ESP being defined."
|
||||
#endif /* SKULLC_USE_HAL_ESP */
|
||||
|
||||
@ -61,8 +61,7 @@ struct StaticHal
|
||||
const std::uint32_t tick_start = DWT->CYCCNT;
|
||||
const std::uint32_t ticks_delay = micros * (SystemCoreClock / 1'000'000);
|
||||
|
||||
while (DWT->CYCCNT - tick_start < ticks_delay)
|
||||
;
|
||||
while (DWT->CYCCNT - tick_start < ticks_delay);
|
||||
#else
|
||||
(void) micros;
|
||||
#endif
|
||||
@ -346,7 +345,7 @@ struct ItmSerialInterface
|
||||
}// namespace Peripherals
|
||||
|
||||
#else
|
||||
# warning "ESP HAL included without SKULLC_USE_HAL_ESP being defined."
|
||||
#warning "ST HAL included without SKULLC_USE_HAL_ST being defined."
|
||||
#endif /* SKULLC_USE_HAL_ST */
|
||||
|
||||
#endif /* SKULLC_PERIPHERALS_HAL_ST_HPP_ */
|
||||
|
||||
@ -32,6 +32,7 @@ add_executable(tests
|
||||
bytes.cpp
|
||||
filters.cpp
|
||||
coro.cpp
|
||||
this_coro.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests
|
||||
|
||||
226
Tests/coro.cpp
226
Tests/coro.cpp
@ -6,4 +6,230 @@
|
||||
|
||||
#include "skullc/coro/task.hpp"
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
#include "skullc/coro/sleep.hpp"
|
||||
#include "skullc/coro/semaphore.hpp"
|
||||
|
||||
#include <semaphore>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int test_coro_called = 0;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
skullc::coro::Task<> test_coro(const int expected)
|
||||
{
|
||||
REQUIRE(expected == test_coro_called);
|
||||
test_coro_called++;
|
||||
co_return;
|
||||
}
|
||||
|
||||
skullc::coro::Task<> test_sleepy_coro(const int expected, const std::chrono::duration<uint32_t, std::milli>& duration = 10ms)
|
||||
{
|
||||
co_await skullc::coro::sleep(0ms, duration);
|
||||
REQUIRE(expected == test_coro_called);
|
||||
test_coro_called++;
|
||||
co_return;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
skullc::coro::Task<> test_semaphore_coro(T* semaphore, const int expected)
|
||||
{
|
||||
co_await skullc::coro::await_semaphore(*semaphore);
|
||||
REQUIRE(expected == test_coro_called);
|
||||
test_coro_called++;
|
||||
co_return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Scheduler runs coroutines.", "[coro]")
|
||||
{
|
||||
using namespace skullc::coro;
|
||||
|
||||
|
||||
Scheduler<std::vector> scheduler;
|
||||
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
||||
|
||||
test_coro_called = 0;
|
||||
|
||||
SECTION("With a single coroutine.")
|
||||
{
|
||||
scheduler.start_tasks(test_coro(0));
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
|
||||
|
||||
SECTION("With two coroutines.")
|
||||
{
|
||||
scheduler.start_tasks(test_coro(0), test_coro(1));
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 2);
|
||||
}
|
||||
|
||||
SECTION("With a single sleepy coroutine.")
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
scheduler.start_tasks(test_sleepy_coro(0));
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
const std::chrono::duration<uint32_t, std::milli> half_sleep = 5ms;
|
||||
scheduler.loop(half_sleep.count());
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
const std::chrono::duration<uint32_t, std::milli> full_sleep = 11ms;
|
||||
scheduler.loop(full_sleep.count());
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
|
||||
SECTION("With a semaphore awaiter.")
|
||||
{
|
||||
std::binary_semaphore semaphore{0};
|
||||
std::counting_semaphore<10> counting_semaphore{0};
|
||||
|
||||
scheduler.start_tasks(
|
||||
test_semaphore_coro(&semaphore, 0),
|
||||
test_semaphore_coro(&counting_semaphore, 1)
|
||||
);
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
semaphore.release(1);
|
||||
scheduler.loop(1);
|
||||
scheduler.loop(2);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
|
||||
counting_semaphore.release(1);
|
||||
scheduler.loop(3);
|
||||
scheduler.loop(4);
|
||||
|
||||
REQUIRE(test_coro_called == 2);
|
||||
}
|
||||
|
||||
SECTION("Coros scheduled for the same millisecond get executed with the same time.")
|
||||
{
|
||||
scheduler.start_tasks(
|
||||
test_sleepy_coro(0, std::chrono::milliseconds{2}),
|
||||
test_sleepy_coro(1, std::chrono::milliseconds{2})
|
||||
);
|
||||
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
scheduler.loop(2);
|
||||
scheduler.loop(2);
|
||||
|
||||
REQUIRE(test_coro_called == 2);
|
||||
}
|
||||
}
|
||||
|
||||
#include "skullc/coro/peripheral_awaiters.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TestGpio
|
||||
{
|
||||
bool is_set = false;
|
||||
|
||||
void set(const bool value)
|
||||
{
|
||||
is_set = value;
|
||||
}
|
||||
|
||||
void toggle()
|
||||
{
|
||||
is_set = !is_set;
|
||||
}
|
||||
|
||||
bool read() const
|
||||
{
|
||||
return is_set;
|
||||
}
|
||||
};
|
||||
|
||||
skullc::coro::Task<> await_pin_edge(TestGpio* pin, const Peripherals::Hal::Edge edge)
|
||||
{
|
||||
co_await skullc::coro::PinEdgeAwaiter(pin, edge);
|
||||
test_coro_called = 1;
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Peripheral awaiters work.", "[coro],[awaiters]")
|
||||
{
|
||||
using namespace skullc::coro;
|
||||
Scheduler<std::vector> scheduler;
|
||||
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
||||
|
||||
test_coro_called = 0;
|
||||
|
||||
SECTION("Pin edge awaiter works with rising edge.")
|
||||
{
|
||||
TestGpio gpio{ false };
|
||||
scheduler.start_tasks(await_pin_edge(&gpio, Peripherals::Hal::Edge::Rising));
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
SECTION("Keeping low generates no edge.")
|
||||
{
|
||||
scheduler.loop(1);
|
||||
scheduler.loop(2);
|
||||
scheduler.loop(3);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
}
|
||||
|
||||
SECTION("Raising generated a rising edge.")
|
||||
{
|
||||
gpio.is_set = true;
|
||||
scheduler.loop(1);
|
||||
scheduler.loop(2);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Pin edge awaiter works with falling edge.")
|
||||
{
|
||||
TestGpio gpio{ true };
|
||||
scheduler.start_tasks(await_pin_edge(&gpio, Peripherals::Hal::Edge::Falling));
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
SECTION("Keeping high generates no edge.")
|
||||
{
|
||||
scheduler.loop(1);
|
||||
scheduler.loop(2);
|
||||
scheduler.loop(3);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
}
|
||||
|
||||
SECTION("Falling generated a falling edge.")
|
||||
{
|
||||
gpio.is_set = false;
|
||||
scheduler.loop(1);
|
||||
scheduler.loop(2);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
Tests/this_coro.cpp
Normal file
16
Tests/this_coro.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// Created by erki on 30/01/25.
|
||||
//
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
|
||||
TEST_CASE("this_coro returns the correct scheduler.", "[coro],[this_coro]")
|
||||
{
|
||||
skullc::coro::Scheduler<std::vector> scheduler;
|
||||
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
||||
|
||||
REQUIRE(&skullc::this_coro::scheduler() == &scheduler);
|
||||
}
|
||||
86
coro/inc/skullc/coro/peripheral_awaiters.hpp
Normal file
86
coro/inc/skullc/coro/peripheral_awaiters.hpp
Normal file
@ -0,0 +1,86 @@
|
||||
//
|
||||
// Created by erki on 01/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
|
||||
#include "peripherals_hal_concepts.hpp"
|
||||
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
template<Peripherals::Hal::Gpio G>
|
||||
struct PinEdgeAwaiter : TaskPoller
|
||||
{
|
||||
PinEdgeAwaiter() = delete;
|
||||
explicit PinEdgeAwaiter(G* pin, const Peripherals::Hal::Edge& edge)
|
||||
: pin(pin)
|
||||
, awaited_edge(edge)
|
||||
{
|
||||
if (awaited_edge == Peripherals::Hal::Edge::Rising)
|
||||
previous_is_set = false;
|
||||
else
|
||||
previous_is_set = true;
|
||||
}
|
||||
|
||||
~PinEdgeAwaiter() override
|
||||
{
|
||||
if (continuation)
|
||||
{
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
bool await_ready() { return false; }
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
continuation = h;
|
||||
stored_coro = continuation;
|
||||
skullc::this_coro::scheduler().add_poller(this);
|
||||
}
|
||||
auto await_resume()
|
||||
{
|
||||
continuation = {};
|
||||
}
|
||||
|
||||
bool isReady(const std::chrono::duration<uint32_t, std::milli>&) override
|
||||
{
|
||||
const bool is_set = pin->read();
|
||||
const auto found_edge = calculateEdge(is_set);
|
||||
|
||||
if (found_edge && *found_edge == awaited_edge)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
previous_is_set = is_set;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
G* pin;
|
||||
Peripherals::Hal::Edge awaited_edge;
|
||||
std::coroutine_handle<> continuation;
|
||||
|
||||
private:
|
||||
bool previous_is_set;
|
||||
|
||||
std::optional<Peripherals::Hal::Edge> calculateEdge(const bool new_is_set) const
|
||||
{
|
||||
if (new_is_set && !previous_is_set)
|
||||
return Peripherals::Hal::Edge::Rising;
|
||||
else if (!new_is_set && previous_is_set)
|
||||
return Peripherals::Hal::Edge::Falling;
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@ -5,138 +5,119 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <coroutine>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "skullc/coro/task.hpp"
|
||||
#include "skullc/coro/scheduler_base.hpp"
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
concept Task = requires(T task)
|
||||
{
|
||||
{
|
||||
task.get_handle().resume()
|
||||
};
|
||||
template<typename T>
|
||||
concept TaskType = requires(T task) {
|
||||
{
|
||||
task.get_handle().resume()
|
||||
};
|
||||
};
|
||||
|
||||
struct TaskPoller {
|
||||
virtual ~TaskPoller() = default;
|
||||
|
||||
virtual bool isReady() = 0;
|
||||
std::coroutine_handle<> stored_coro;
|
||||
};
|
||||
|
||||
class Scheduler
|
||||
template<template<typename X> typename C>
|
||||
class Scheduler final : public SchedulerBase
|
||||
{
|
||||
constexpr static auto cmp = [](const auto &a, const auto &b) {
|
||||
return a.first > b.first;
|
||||
};
|
||||
constexpr static auto cmp = [](const auto& a, const auto& b) {
|
||||
return a.first > b.first;
|
||||
};
|
||||
|
||||
public:
|
||||
Scheduler()
|
||||
: Scheduler(16)
|
||||
{ }
|
||||
using Container = C<std::pair<uint32_t, std::coroutine_handle<>>>;
|
||||
using PollerContainer = C<TaskPoller*>;
|
||||
|
||||
Scheduler(const Scheduler&) = delete;
|
||||
Scheduler(Scheduler&&) = delete;
|
||||
Scheduler &operator=(const Scheduler&) = delete;
|
||||
Scheduler &operator=(Scheduler&&) = delete;
|
||||
Scheduler() = default;
|
||||
|
||||
explicit Scheduler(uint num_tasks)
|
||||
{
|
||||
scheduled.reserve(num_tasks);
|
||||
}
|
||||
Scheduler(const Scheduler&) = delete;
|
||||
Scheduler(Scheduler&&) = delete;
|
||||
Scheduler& operator=(const Scheduler&) = delete;
|
||||
Scheduler& operator=(Scheduler&&) = delete;
|
||||
|
||||
/** \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)
|
||||
template<TaskType... T>
|
||||
void start_tasks(T... tasks)
|
||||
{
|
||||
pollers.push_back(poller);
|
||||
}
|
||||
start(tasks...);
|
||||
}
|
||||
|
||||
bool remove(std::coroutine_handle<> handle)
|
||||
{
|
||||
for (auto &p : scheduled)
|
||||
if (p.second == handle)
|
||||
void schedule(std::coroutine_handle<> handle, uint32_t when) override
|
||||
{
|
||||
scheduled_.push_back({when, handle});
|
||||
std::push_heap(scheduled_.begin(), scheduled_.end(), cmp);
|
||||
}
|
||||
|
||||
void loop(const uint32_t& current_time_ms)
|
||||
{
|
||||
loop(std::chrono::duration<uint32_t, std::milli>(current_time_ms));
|
||||
}
|
||||
|
||||
void loop(const std::chrono::duration<uint32_t, std::milli>& current_time)
|
||||
{
|
||||
if (!scheduled_.empty())
|
||||
{
|
||||
auto min_time = scheduled_.front();
|
||||
if (min_time.first <= current_time.count())
|
||||
{
|
||||
std::swap(p, scheduled.back());
|
||||
scheduled.pop_back();
|
||||
std::make_heap(scheduled.begin(), scheduled.end(), cmp);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
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(current_time))
|
||||
{
|
||||
schedule(poller->stored_coro, current_time.count());
|
||||
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) override
|
||||
{
|
||||
pollers_.push_back(poller);
|
||||
}
|
||||
|
||||
bool remove(std::coroutine_handle<> handle) override
|
||||
{
|
||||
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;
|
||||
Container scheduled_;
|
||||
PollerContainer 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...);
|
||||
}
|
||||
}
|
||||
template<typename T0, typename... R>
|
||||
void start(T0& t0, R&... r)
|
||||
{
|
||||
t0.get_handle().resume();
|
||||
if constexpr (sizeof...(r) > 0)
|
||||
{
|
||||
start(r...);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}// namespace skullc::coro
|
||||
31
coro/inc/skullc/coro/scheduler_base.hpp
Normal file
31
coro/inc/skullc/coro/scheduler_base.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Created by erki on 02/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <coroutine>
|
||||
#include <cstdint>
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
struct TaskPoller
|
||||
{
|
||||
virtual ~TaskPoller() = default;
|
||||
|
||||
virtual bool isReady(const std::chrono::duration<uint32_t, std::milli>& current_time) = 0;
|
||||
std::coroutine_handle<> stored_coro;
|
||||
};
|
||||
|
||||
class SchedulerBase
|
||||
{
|
||||
public:
|
||||
virtual ~SchedulerBase() = default;
|
||||
|
||||
virtual void schedule(std::coroutine_handle<> handle, uint32_t when_ms) = 0;
|
||||
virtual bool remove(std::coroutine_handle<> handle) = 0;
|
||||
virtual void add_poller(TaskPoller* poller) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
59
coro/inc/skullc/coro/semaphore.hpp
Normal file
59
coro/inc/skullc/coro/semaphore.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// Created by erki on 01/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <coroutine>
|
||||
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
requires requires (T t) { { t.try_acquire() } -> std::same_as<bool>; }
|
||||
auto await_semaphore(T& semaphore)
|
||||
{
|
||||
struct Awaitable : TaskPoller
|
||||
{
|
||||
Awaitable() = delete;
|
||||
explicit Awaitable(T* semaphore)
|
||||
: semaphore{semaphore}
|
||||
{}
|
||||
|
||||
~Awaitable() override
|
||||
{
|
||||
if (continuation)
|
||||
{
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
bool await_ready() { return false; }
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
continuation = h;
|
||||
stored_coro = continuation;
|
||||
this_coro::scheduler().add_poller(this);
|
||||
}
|
||||
auto await_resume()
|
||||
{
|
||||
continuation = {};
|
||||
}
|
||||
|
||||
bool isReady(const std::chrono::duration<uint32_t, std::milli>&) override
|
||||
{
|
||||
return semaphore->try_acquire();
|
||||
}
|
||||
|
||||
T* semaphore;
|
||||
std::coroutine_handle<> continuation;
|
||||
};
|
||||
|
||||
return Awaitable{&semaphore};
|
||||
}
|
||||
|
||||
}
|
||||
51
coro/inc/skullc/coro/sleep.hpp
Normal file
51
coro/inc/skullc/coro/sleep.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// Created by erki on 01/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
struct TimeAwaitable
|
||||
{
|
||||
~TimeAwaitable()
|
||||
{
|
||||
if (continuation)
|
||||
{
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
bool await_ready() { return false; }
|
||||
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
continuation = h;
|
||||
this_coro::scheduler().schedule(h, resume_time);
|
||||
}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
continuation = {};
|
||||
return resume_time;
|
||||
}
|
||||
|
||||
uint32_t resume_time;
|
||||
std::coroutine_handle<> continuation{};
|
||||
};
|
||||
|
||||
}// namespace detail
|
||||
|
||||
inline auto sleep(const std::chrono::duration<uint32_t, std::milli>& time_now, const std::chrono::duration<uint32_t, std::milli>& duration)
|
||||
{
|
||||
return detail::TimeAwaitable{time_now.count() + duration.count()};
|
||||
}
|
||||
|
||||
}// namespace skullc::coro
|
||||
@ -12,25 +12,25 @@ namespace skullc::coro
|
||||
{
|
||||
|
||||
template<typename T = void, bool lazy = true>
|
||||
struct [[nodiscard]] task
|
||||
struct [[nodiscard]] Task
|
||||
{
|
||||
using value_type = T;
|
||||
struct task_promise;
|
||||
using promise_type = task_promise;
|
||||
|
||||
task() = delete;
|
||||
Task() = delete;
|
||||
|
||||
explicit task(std::coroutine_handle<promise_type> handle)
|
||||
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(Task&& t) noexcept : my_handle(t.my_handle) { t.my_handle = nullptr; }
|
||||
|
||||
task(const task&) = delete;
|
||||
task& operator=(const task&) = delete;
|
||||
Task(const Task&) = delete;
|
||||
Task& operator=(const Task&) = delete;
|
||||
|
||||
~task()
|
||||
~Task()
|
||||
{
|
||||
if constexpr (!std::is_same_v<T, void>)
|
||||
{
|
||||
@ -42,7 +42,7 @@ struct [[nodiscard]] task
|
||||
auto get_handle() const { return my_handle; }
|
||||
void detach() { my_handle = nullptr; }
|
||||
|
||||
task& operator=(task&& other) noexcept
|
||||
Task& operator=(Task&& other) noexcept
|
||||
{
|
||||
if (std::addressof(other) != this)
|
||||
{
|
||||
@ -109,12 +109,12 @@ struct promise_setter<void>
|
||||
|
||||
|
||||
template<typename T, bool lazy>
|
||||
struct task<T, lazy>::task_promise : public promise_setter<T>
|
||||
struct Task<T, lazy>::task_promise : public promise_setter<T>
|
||||
{
|
||||
|
||||
task get_return_object() noexcept
|
||||
Task get_return_object() noexcept
|
||||
{
|
||||
return task{std::coroutine_handle<task_promise>::from_promise(*this)};
|
||||
return Task{std::coroutine_handle<task_promise>::from_promise(*this)};
|
||||
};
|
||||
|
||||
auto initial_suspend() const noexcept
|
||||
@ -134,7 +134,7 @@ struct task<T, lazy>::task_promise : public promise_setter<T>
|
||||
void await_resume() const noexcept {}
|
||||
|
||||
std::coroutine_handle<>
|
||||
await_suspend(std::coroutine_handle<task::task_promise> h) noexcept
|
||||
await_suspend(std::coroutine_handle<Task::task_promise> h) noexcept
|
||||
{
|
||||
if constexpr (std::is_same_v<T, void>)
|
||||
{
|
||||
|
||||
30
coro/inc/skullc/coro/this_coro.hpp
Normal file
30
coro/inc/skullc/coro/this_coro.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// Created by erki on 30/01/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "skullc/coro/scheduler_base.hpp"
|
||||
|
||||
namespace skullc::this_coro
|
||||
{
|
||||
|
||||
struct SchedulerGetter
|
||||
{
|
||||
void register_scheduler(coro::SchedulerBase& scheduler)
|
||||
{
|
||||
scheduler_ = &scheduler;
|
||||
}
|
||||
|
||||
coro::SchedulerBase& operator()()
|
||||
{
|
||||
return *scheduler_;
|
||||
}
|
||||
|
||||
private:
|
||||
coro::SchedulerBase* scheduler_ = nullptr;
|
||||
};
|
||||
|
||||
inline static SchedulerGetter scheduler;
|
||||
|
||||
}// namespace skullc::this_coro
|
||||
Loading…
x
Reference in New Issue
Block a user