WIP2
This commit is contained in:
parent
5af059f4c6
commit
4d897ad5c6
@ -11,15 +11,21 @@
|
|||||||
namespace Peripherals::Hal
|
namespace Peripherals::Hal
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum class Edge
|
||||||
|
{
|
||||||
|
Rising,
|
||||||
|
Falling
|
||||||
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept Gpio = requires (T t, const T ct) {
|
concept Gpio = requires(T t, const T ct) {
|
||||||
t.set(true);
|
t.set(true);
|
||||||
t.toggle();
|
t.toggle();
|
||||||
{ ct.read() } -> std::same_as<bool>;
|
{ ct.read() } -> std::same_as<bool>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
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(data, len) } -> std::same_as<bool>;
|
||||||
{ t.transmit(std::array<std::uint8_t, 10>{}) } -> std::same_as<bool>;
|
{ t.transmit(std::array<std::uint8_t, 10>{}) } -> std::same_as<bool>;
|
||||||
{ t.receive(data, len) } -> 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>
|
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.writerRegister(reg, std::uint8_t{});
|
||||||
t.writeRegisterMultibyte(reg, data, len);
|
t.writeRegisterMultibyte(reg, data, len);
|
||||||
{ t.readRegister(reg, std::uint32_t{}) } -> std::same_as<std::uint8_t>;
|
{ t.readRegister(reg, std::uint32_t{}) } -> std::same_as<std::uint8_t>;
|
||||||
t.readRegisterMultibyte(reg, data, len, std::uint32_t{});
|
t.readRegisterMultibyte(reg, data, len, std::uint32_t{});
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}// namespace Peripherals::Hal
|
||||||
|
|||||||
@ -20,8 +20,8 @@ struct Gpio
|
|||||||
|
|
||||||
Gpio() = delete;
|
Gpio() = delete;
|
||||||
explicit Gpio(gpio_num_t gpio, const bool inverted)
|
explicit Gpio(gpio_num_t gpio, const bool inverted)
|
||||||
: gpio(gpio), inverted(inverted)
|
: gpio(gpio), inverted(inverted)
|
||||||
{ }
|
{}
|
||||||
|
|
||||||
void set(const bool& state)
|
void set(const bool& state)
|
||||||
{
|
{
|
||||||
@ -39,13 +39,13 @@ struct Gpio
|
|||||||
static_assert(Peripherals::Hal::Gpio<Gpio>);
|
static_assert(Peripherals::Hal::Gpio<Gpio>);
|
||||||
|
|
||||||
#define CREATE_GPIO(name) \
|
#define CREATE_GPIO(name) \
|
||||||
Peripherals::Hal::Esp::Gpio{name, false}
|
Peripherals::Hal::Esp::Gpio { name, false }
|
||||||
|
|
||||||
#define CREATE_INV_GPIO(name) \
|
#define CREATE_INV_GPIO(name) \
|
||||||
Peripherals::Hal::Esp::Gpio{name, true}
|
Peripherals::Hal::Esp::Gpio { name, true }
|
||||||
|
|
||||||
}
|
}// namespace Peripherals::Hal::Esp
|
||||||
|
|
||||||
#else
|
#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 */
|
#endif /* SKULLC_USE_HAL_ESP */
|
||||||
|
|||||||
@ -61,8 +61,7 @@ struct StaticHal
|
|||||||
const std::uint32_t tick_start = DWT->CYCCNT;
|
const std::uint32_t tick_start = DWT->CYCCNT;
|
||||||
const std::uint32_t ticks_delay = micros * (SystemCoreClock / 1'000'000);
|
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
|
#else
|
||||||
(void) micros;
|
(void) micros;
|
||||||
#endif
|
#endif
|
||||||
@ -346,7 +345,7 @@ struct ItmSerialInterface
|
|||||||
}// namespace Peripherals
|
}// namespace Peripherals
|
||||||
|
|
||||||
#else
|
#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_USE_HAL_ST */
|
||||||
|
|
||||||
#endif /* SKULLC_PERIPHERALS_HAL_ST_HPP_ */
|
#endif /* SKULLC_PERIPHERALS_HAL_ST_HPP_ */
|
||||||
|
|||||||
@ -32,6 +32,7 @@ add_executable(tests
|
|||||||
bytes.cpp
|
bytes.cpp
|
||||||
filters.cpp
|
filters.cpp
|
||||||
coro.cpp
|
coro.cpp
|
||||||
|
this_coro.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(tests
|
target_link_libraries(tests
|
||||||
|
|||||||
226
Tests/coro.cpp
226
Tests/coro.cpp
@ -6,4 +6,230 @@
|
|||||||
|
|
||||||
#include "skullc/coro/task.hpp"
|
#include "skullc/coro/task.hpp"
|
||||||
#include "skullc/coro/scheduler.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
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
|
||||||
#include <coroutine>
|
#include <coroutine>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "skullc/coro/task.hpp"
|
#include "skullc/coro/task.hpp"
|
||||||
|
#include "skullc/coro/scheduler_base.hpp"
|
||||||
|
|
||||||
namespace skullc::coro
|
namespace skullc::coro
|
||||||
{
|
{
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
concept Task = requires(T task)
|
concept TaskType = requires(T task) {
|
||||||
{
|
{
|
||||||
{
|
task.get_handle().resume()
|
||||||
task.get_handle().resume()
|
};
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TaskPoller {
|
template<template<typename X> typename C>
|
||||||
virtual ~TaskPoller() = default;
|
class Scheduler final : public SchedulerBase
|
||||||
|
|
||||||
virtual bool isReady() = 0;
|
|
||||||
std::coroutine_handle<> stored_coro;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Scheduler
|
|
||||||
{
|
{
|
||||||
constexpr static auto cmp = [](const auto &a, const auto &b) {
|
constexpr static auto cmp = [](const auto& a, const auto& b) {
|
||||||
return a.first > b.first;
|
return a.first > b.first;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Scheduler()
|
using Container = C<std::pair<uint32_t, std::coroutine_handle<>>>;
|
||||||
: Scheduler(16)
|
using PollerContainer = C<TaskPoller*>;
|
||||||
{ }
|
|
||||||
|
|
||||||
Scheduler(const Scheduler&) = delete;
|
Scheduler() = default;
|
||||||
Scheduler(Scheduler&&) = delete;
|
|
||||||
Scheduler &operator=(const Scheduler&) = delete;
|
|
||||||
Scheduler &operator=(Scheduler&&) = delete;
|
|
||||||
|
|
||||||
explicit Scheduler(uint num_tasks)
|
Scheduler(const Scheduler&) = delete;
|
||||||
{
|
Scheduler(Scheduler&&) = delete;
|
||||||
scheduled.reserve(num_tasks);
|
Scheduler& operator=(const Scheduler&) = delete;
|
||||||
}
|
Scheduler& operator=(Scheduler&&) = delete;
|
||||||
|
|
||||||
/** \brief Start a new task. The task can terminate by calling co_return; . */
|
template<TaskType... T>
|
||||||
void start_task(task<> task, uint64_t when = 0)
|
void start_tasks(T... tasks)
|
||||||
{
|
|
||||||
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);
|
start(tasks...);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool remove(std::coroutine_handle<> handle)
|
void schedule(std::coroutine_handle<> handle, uint32_t when) override
|
||||||
{
|
{
|
||||||
for (auto &p : scheduled)
|
scheduled_.push_back({when, handle});
|
||||||
if (p.second == 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());
|
std::pop_heap(scheduled_.begin(), scheduled_.end(), cmp);
|
||||||
scheduled.pop_back();
|
scheduled_.pop_back();
|
||||||
std::make_heap(scheduled.begin(), scheduled.end(), cmp);
|
if (min_time.second)
|
||||||
return true;
|
min_time.second.resume();
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
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:
|
private:
|
||||||
/// \brief Heap of scheduled coroutines. Top of the heap is earliest time
|
Container scheduled_;
|
||||||
std::vector<std::pair<uint64_t, std::coroutine_handle<>>> scheduled;
|
PollerContainer pollers_;
|
||||||
std::vector<TaskPoller*> pollers;
|
|
||||||
|
|
||||||
template <typename T0, typename... R>
|
template<typename T0, typename... R>
|
||||||
void start(T0 &t0, R &...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();
|
||||||
t0.get_handle().resume();
|
if constexpr (sizeof...(r) > 0)
|
||||||
if constexpr (sizeof...(r) > 0) {
|
{
|
||||||
start(r...);
|
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>
|
template<typename T = void, bool lazy = true>
|
||||||
struct [[nodiscard]] task
|
struct [[nodiscard]] Task
|
||||||
{
|
{
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
struct task_promise;
|
struct task_promise;
|
||||||
using promise_type = 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)
|
: 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(const Task&) = delete;
|
||||||
task& operator=(const task&) = delete;
|
Task& operator=(const Task&) = delete;
|
||||||
|
|
||||||
~task()
|
~Task()
|
||||||
{
|
{
|
||||||
if constexpr (!std::is_same_v<T, void>)
|
if constexpr (!std::is_same_v<T, void>)
|
||||||
{
|
{
|
||||||
@ -42,7 +42,7 @@ struct [[nodiscard]] task
|
|||||||
auto get_handle() const { return my_handle; }
|
auto get_handle() const { return my_handle; }
|
||||||
void detach() { my_handle = nullptr; }
|
void detach() { my_handle = nullptr; }
|
||||||
|
|
||||||
task& operator=(task&& other) noexcept
|
Task& operator=(Task&& other) noexcept
|
||||||
{
|
{
|
||||||
if (std::addressof(other) != this)
|
if (std::addressof(other) != this)
|
||||||
{
|
{
|
||||||
@ -109,12 +109,12 @@ struct promise_setter<void>
|
|||||||
|
|
||||||
|
|
||||||
template<typename T, bool lazy>
|
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
|
auto initial_suspend() const noexcept
|
||||||
@ -134,7 +134,7 @@ struct task<T, lazy>::task_promise : public promise_setter<T>
|
|||||||
void await_resume() const noexcept {}
|
void await_resume() const noexcept {}
|
||||||
|
|
||||||
std::coroutine_handle<>
|
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>)
|
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