Compare commits
No commits in common. "feature/coros_actually_happening" and "master" have entirely different histories.
feature/co
...
master
@ -14,7 +14,6 @@ jobs:
|
||||
cmake . -B${{runner.workspace}}/build \
|
||||
-G"Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSKULLC_WITH_CORO=ON \
|
||||
-DSKULLC_WITH_TESTS=ON
|
||||
|
||||
- name: Build tests + lib
|
||||
@ -35,7 +34,6 @@ jobs:
|
||||
cmake . -B${{runner.workspace}}/build \
|
||||
-G"Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSKULLC_WITH_CORO=ON \
|
||||
-DSKULLC_WITH_DOCS=ON
|
||||
|
||||
- name: Build docs
|
||||
|
||||
@ -16,15 +16,10 @@ 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)
|
||||
|
||||
@ -10,20 +10,5 @@ target_include_directories(peripherals
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
if (DEFINED SKULLC_USE_HAL_ST)
|
||||
set(PERIPHERALS_DEFINITIONS SKULLC_USE_HAL_ST)
|
||||
endif ()
|
||||
if (DEFINED SKULLC_USE_HAL_ESP)
|
||||
set(PERIPHERALS_DEFINITIONS SKULLC_USE_HAL_ESP)
|
||||
endif ()
|
||||
if (DEFINED SKULLC_WITH_CORO)
|
||||
list(APPEND PERIPHERALS_DEFINITIONS SKULLC_WITH_CORO)
|
||||
endif ()
|
||||
|
||||
target_compile_definitions(peripherals
|
||||
INTERFACE
|
||||
${PERIPHERALS_DEFINITIONS}
|
||||
)
|
||||
|
||||
## INSTALL
|
||||
skullc_install_packages(skullc peripherals ${SKULLC_VERSION})
|
||||
|
||||
@ -10,14 +10,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "peripherals_hal_concepts.hpp"
|
||||
|
||||
#ifdef SKULLC_WITH_CORO
|
||||
#include "skullc/coro/sleep.hpp"
|
||||
#include "skullc/coro/task.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
#endif
|
||||
|
||||
namespace Peripherals
|
||||
{
|
||||
|
||||
@ -28,7 +20,7 @@ enum class ButtonPress : std::uint32_t
|
||||
LONG_PRESS
|
||||
};
|
||||
|
||||
template<Hal::Gpio G, Hal::StaticHal H>
|
||||
template<typename G, typename H>
|
||||
class Button
|
||||
{
|
||||
public:
|
||||
@ -42,7 +34,7 @@ public:
|
||||
|
||||
Button() = delete;
|
||||
explicit Button(const gpio& sw)
|
||||
: sw(sw), was_pressed_(sw.read())
|
||||
: sw(sw)
|
||||
{}
|
||||
|
||||
void update()
|
||||
@ -59,15 +51,7 @@ public:
|
||||
else if (!is_pressed && was_pressed_)
|
||||
{
|
||||
if (time_held > TIMEOUT_SHORT_PRESS && time_held <= TIMEOUT_LONG_PRESS)
|
||||
{
|
||||
new_state = ButtonPress::SHORT_PRESS;
|
||||
}
|
||||
|
||||
if (state_exhausted_)
|
||||
{
|
||||
current_state_ = ButtonPress::NOT_PRESSED;
|
||||
state_exhausted_ = false;
|
||||
}
|
||||
|
||||
time_pressed_down_ = 0;
|
||||
}
|
||||
@ -78,48 +62,18 @@ public:
|
||||
}
|
||||
|
||||
was_pressed_ = is_pressed;
|
||||
|
||||
if (!state_exhausted_)
|
||||
current_state_ = new_state;
|
||||
current_state_ = new_state;
|
||||
}
|
||||
|
||||
[[nodiscard]] ButtonPress getState() const
|
||||
{
|
||||
if (!state_exhausted_)
|
||||
{
|
||||
if (current_state_ != ButtonPress::NOT_PRESSED && current_state_ != ButtonPress::SHORT_PRESS)
|
||||
state_exhausted_ = true;
|
||||
|
||||
return current_state_;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ButtonPress::NOT_PRESSED;
|
||||
}
|
||||
return current_state_;
|
||||
}
|
||||
|
||||
#ifdef SKULLC_WITH_CORO
|
||||
skullc::coro::Task<ButtonPress> await_press()
|
||||
{
|
||||
ButtonPress result = ButtonPress::NOT_PRESSED;
|
||||
while (result == ButtonPress::NOT_PRESSED)
|
||||
{
|
||||
update();
|
||||
result = getState();
|
||||
|
||||
if (result == ButtonPress::NOT_PRESSED)
|
||||
co_await skullc::coro::sleep(std::chrono::milliseconds(hal::getMillis()), std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
co_return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool was_pressed_;
|
||||
bool was_pressed_ = false;
|
||||
std::uint32_t time_pressed_down_ = 0;
|
||||
ButtonPress current_state_ = ButtonPress::NOT_PRESSED;
|
||||
mutable bool state_exhausted_ = false;
|
||||
};
|
||||
|
||||
}// namespace Peripherals
|
||||
|
||||
@ -11,21 +11,15 @@
|
||||
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>;
|
||||
@ -33,16 +27,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{});
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept StaticHal = requires() {
|
||||
{ T::getMillis() } -> std::same_as<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 */
|
||||
|
||||
@ -14,10 +14,6 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#ifdef SKULLC_WITH_CORO
|
||||
#include "skullc/coro/peripheral_awaiters.hpp"
|
||||
#endif
|
||||
|
||||
#define USE_DELAY_US
|
||||
|
||||
namespace Peripherals
|
||||
@ -31,7 +27,7 @@ template<typename Origin>
|
||||
using IsrCallbackFn = void (*)(Origin*);
|
||||
|
||||
template<typename Origin, typename Handler, void (Handler::*func)(),
|
||||
typename Tag = decltype([] {})>
|
||||
typename Tag>
|
||||
IsrCallbackFn<Origin> createCallback(Handler& h_in)
|
||||
{
|
||||
static Handler* h = &h_in;
|
||||
@ -65,7 +61,8 @@ 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
|
||||
@ -96,13 +93,6 @@ struct Gpio
|
||||
void toggle() { HAL_GPIO_TogglePin(port, pin); }
|
||||
|
||||
bool read() const { return inverted ? !bool(HAL_GPIO_ReadPin(port, pin)) : bool(HAL_GPIO_ReadPin(port, pin)); }
|
||||
|
||||
#ifdef SKULLC_WITH_CORO
|
||||
auto await_edge(const Edge& edge)
|
||||
{
|
||||
return skullc::coro::PinEdgeAwaiter(this, edge);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#define CREATE_GPIO(name) \
|
||||
@ -200,104 +190,6 @@ struct SerialInterfaceAsync
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef SKULLC_WITH_CORO
|
||||
template<typename T,
|
||||
HAL_StatusTypeDef (*transmit_)(T*, std::uint8_t* data,
|
||||
std::uint16_t data_len),
|
||||
HAL_StatusTypeDef (*receive_)(T*, std::uint8_t* data,
|
||||
std::uint16_t data_len)>
|
||||
struct SerialInterfaceCoro
|
||||
{
|
||||
private:
|
||||
void on_rx_complete()
|
||||
{
|
||||
if (rx_awaiter_)
|
||||
rx_awaiter_->set_completed();
|
||||
}
|
||||
|
||||
void on_tx_complete()
|
||||
{
|
||||
if (tx_awaiter_)
|
||||
tx_awaiter_->set_completed();
|
||||
}
|
||||
|
||||
std::optional<skullc::coro::ISRAwaiter> rx_awaiter_ = std::nullopt;
|
||||
std::optional<skullc::coro::ISRAwaiter> tx_awaiter_ = std::nullopt;
|
||||
|
||||
public:
|
||||
using underlying_handle_type = T;
|
||||
underlying_handle_type* handle;
|
||||
|
||||
SerialInterfaceCoro() = delete;
|
||||
|
||||
template<typename = decltype([] {})>
|
||||
explicit SerialInterfaceCoro(underlying_handle_type* handle)
|
||||
: handle(handle)
|
||||
{
|
||||
handle->RxCpltCallback = createCallback<T,
|
||||
std::decay_t<decltype(*this)>,
|
||||
&std::decay_t<decltype(*this)>::on_rx_complete>(*this);
|
||||
|
||||
handle->TxCpltCallback = createCallback<T,
|
||||
std::decay_t<decltype(*this)>,
|
||||
&std::decay_t<decltype(*this)>::on_tx_complete>(*this);
|
||||
}
|
||||
|
||||
skullc::coro::Task<bool> transmit(std::uint8_t* data, const std::uint32_t data_len)
|
||||
{
|
||||
tx_awaiter_.emplace();
|
||||
const auto status = transmit_(handle, data, data_len);
|
||||
if (status != HAL_StatusTypeDef::HAL_OK)
|
||||
co_return false;
|
||||
|
||||
co_await *tx_awaiter_;
|
||||
tx_awaiter_ = std::nullopt;
|
||||
|
||||
co_return true;
|
||||
}
|
||||
|
||||
template<typename Td, std::size_t N>
|
||||
skullc::coro::Task<bool> transmit(std::array<Td, N>& array)
|
||||
{
|
||||
static_assert(sizeof(Td) == sizeof(std::uint8_t), "Data is not a byte large.");
|
||||
|
||||
return transmit(reinterpret_cast<std::uint8_t*>(array.data()), std::uint32_t(N));
|
||||
}
|
||||
|
||||
skullc::coro::Task<bool> receive(std::uint8_t* data, const std::uint32_t data_len)
|
||||
{
|
||||
rx_awaiter_.emplace();
|
||||
const auto status = receive_(handle, data, data_len);
|
||||
if (status != HAL_StatusTypeDef::HAL_OK)
|
||||
co_return false;
|
||||
|
||||
co_await *rx_awaiter_;
|
||||
rx_awaiter_ = std::nullopt;
|
||||
|
||||
co_return true;
|
||||
}
|
||||
|
||||
template<typename Td, std::size_t N>
|
||||
skullc::coro::Task<bool> receive(std::array<Td, N>& array)
|
||||
{
|
||||
static_assert(sizeof(Td) == sizeof(std::uint8_t), "Data is not a byte large.");
|
||||
|
||||
return receive(reinterpret_cast<std::uint8_t*>(array.data()), std::uint32_t(N));
|
||||
}
|
||||
|
||||
template<std::size_t N>
|
||||
skullc::coro::Task<std::array<std::uint8_t, N>> receive()
|
||||
{
|
||||
std::array<std::uint8_t, N> data;
|
||||
data.fill(0);
|
||||
|
||||
co_await receive(data.data(), std::uint32_t(N));
|
||||
|
||||
co_return data;
|
||||
}
|
||||
};
|
||||
#endif// SKULLC_WITH_CORO
|
||||
|
||||
#ifdef HAL_SPI_MODULE_ENABLED
|
||||
|
||||
using SpiInterface =
|
||||
@ -394,11 +286,6 @@ inline HAL_StatusTypeDef uartTransmitDma(UART_HandleTypeDef* huart, std::uint8_t
|
||||
return HAL_UART_Transmit_DMA(huart, data, size);
|
||||
}
|
||||
|
||||
inline HAL_StatusTypeDef uartTransmitIt(UART_HandleTypeDef* huart, std::uint8_t* data, const std::uint16_t size)
|
||||
{
|
||||
return HAL_UART_Transmit_IT(huart, data, size);
|
||||
}
|
||||
|
||||
}// namespace _Details
|
||||
|
||||
using UartInterface =
|
||||
@ -406,14 +293,6 @@ using UartInterface =
|
||||
using UartInterfaceDMA =
|
||||
SerialInterfaceAsync<UART_HandleTypeDef, _Details::uartTransmitDma,
|
||||
HAL_UART_Receive_DMA>;
|
||||
#ifdef SKULLC_WITH_CORO
|
||||
using UartInterfaceCoro =
|
||||
SerialInterfaceCoro<UART_HandleTypeDef, _Details::uartTransmitIt,
|
||||
HAL_UART_Receive_IT>;
|
||||
using UartInterfaceCoroDma =
|
||||
SerialInterfaceCoro<UART_HandleTypeDef, _Details::uartTransmitDma,
|
||||
HAL_UART_Receive_DMA>;
|
||||
#endif// SKULLC_WITH_CORO
|
||||
|
||||
#endif// HAL_UART_MODULE_ENABLED
|
||||
|
||||
@ -467,7 +346,7 @@ struct ItmSerialInterface
|
||||
}// namespace Peripherals
|
||||
|
||||
#else
|
||||
#warning "ST HAL included without SKULLC_USE_HAL_ST being defined."
|
||||
# warning "ESP HAL included without SKULLC_USE_HAL_ESP being defined."
|
||||
#endif /* SKULLC_USE_HAL_ST */
|
||||
|
||||
#endif /* SKULLC_PERIPHERALS_HAL_ST_HPP_ */
|
||||
|
||||
@ -31,22 +31,19 @@ add_executable(tests
|
||||
enum_helpers.cpp
|
||||
bytes.cpp
|
||||
filters.cpp
|
||||
coro.cpp
|
||||
this_coro.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(tests
|
||||
PUBLIC
|
||||
skullc::utility
|
||||
skullc::messaging
|
||||
skullc::coro
|
||||
skullc::peripherals
|
||||
Catch2::Catch2
|
||||
)
|
||||
|
||||
set_target_properties(tests
|
||||
PROPERTIES
|
||||
CXX_STANDARD 23
|
||||
CXX_STANDARD 17
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/contrib)
|
||||
|
||||
@ -33,26 +33,20 @@ std::uint32_t HAL::millis = 1;
|
||||
|
||||
struct Gpio
|
||||
{
|
||||
static bool is_set;
|
||||
static bool set;
|
||||
|
||||
Gpio()
|
||||
{
|
||||
is_set = false;
|
||||
set = false;
|
||||
}
|
||||
|
||||
bool read() const
|
||||
bool read()
|
||||
{
|
||||
return is_set;
|
||||
return set;
|
||||
}
|
||||
|
||||
void set(const bool value)
|
||||
{}
|
||||
|
||||
void toggle()
|
||||
{}
|
||||
};
|
||||
|
||||
bool Gpio::is_set = false;
|
||||
bool Gpio::set = false;
|
||||
|
||||
}// namespace
|
||||
|
||||
@ -81,13 +75,13 @@ TEST_CASE("Button reads presses properly.", "[peripherals],[button]")
|
||||
|
||||
Button button{g};
|
||||
|
||||
Gpio::is_set = true;
|
||||
Gpio::set = true;
|
||||
button.update();
|
||||
|
||||
SECTION("Too short of a press is debounced.")
|
||||
{
|
||||
HAL::millis = Button::TIMEOUT_SHORT_PRESS - 1;
|
||||
Gpio::is_set = false;
|
||||
Gpio::set = false;
|
||||
|
||||
button.update();
|
||||
|
||||
@ -97,7 +91,7 @@ TEST_CASE("Button reads presses properly.", "[peripherals],[button]")
|
||||
SECTION("Short press is identified properly.")
|
||||
{
|
||||
HAL::millis = Button::TIMEOUT_SHORT_PRESS + 2;
|
||||
Gpio::is_set = false;
|
||||
Gpio::set = false;
|
||||
|
||||
button.update();
|
||||
|
||||
@ -130,7 +124,7 @@ TEST_CASE("Button reads presses properly.", "[peripherals],[button]")
|
||||
SECTION("Releasing keeps NOT_PRESSED state.")
|
||||
{
|
||||
HAL::millis += 1;
|
||||
Gpio::is_set = false;
|
||||
Gpio::set = false;
|
||||
|
||||
button.update();
|
||||
|
||||
|
||||
409
Tests/coro.cpp
409
Tests/coro.cpp
@ -1,409 +0,0 @@
|
||||
//
|
||||
// Created by erki on 30/01/25.
|
||||
//
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/semaphore.hpp"
|
||||
#include "skullc/coro/signal.hpp"
|
||||
#include "skullc/coro/sleep.hpp"
|
||||
#include "skullc/coro/task.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
#include "skullc/coro/composition.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;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
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_edge(TestGpio* pin, const Peripherals::Hal::Edge edge)
|
||||
{
|
||||
co_await skullc::coro::PinEdgeAwaiter(pin, edge);
|
||||
test_coro_called = 1;
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
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_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_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
skullc::coro::Task<> await_signal(const int expected, skullc::coro::Signal<int>* signal)
|
||||
{
|
||||
const int value = co_await signal->receive();
|
||||
REQUIRE(value == expected);
|
||||
test_coro_called = 1;
|
||||
co_return;
|
||||
}
|
||||
|
||||
skullc::coro::Task<> await_signal_n(const auto& expecteds, skullc::coro::Signal<int>* signal)
|
||||
{
|
||||
for (const int e : expecteds)
|
||||
{
|
||||
const int value = co_await signal->receive();
|
||||
REQUIRE(value == e);
|
||||
test_coro_called++;
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
skullc::coro::Task<> send_signal(const int value, skullc::coro::Signal<int>* signal)
|
||||
{
|
||||
co_await skullc::coro::sleep(0ms, 10ms);
|
||||
signal->send(value);
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
TEST_CASE("Signal awaiters work.", "[coro],[signal]")
|
||||
{
|
||||
using namespace skullc::coro;
|
||||
Scheduler<std::vector> scheduler;
|
||||
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
||||
|
||||
test_coro_called = 0;
|
||||
|
||||
Signal<int> signal;
|
||||
|
||||
SECTION("Sending from main thread.")
|
||||
{
|
||||
scheduler.start_tasks(await_signal(10, &signal));
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(1);
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
signal.send(10);
|
||||
scheduler.loop(2);
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
|
||||
SECTION("Sending from another coroutine.")
|
||||
{
|
||||
scheduler.start_tasks(await_signal(10, &signal), send_signal(10, &signal));
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(1);
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
scheduler.loop(10);
|
||||
REQUIRE(test_coro_called == 0);
|
||||
scheduler.loop(10);
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
|
||||
const std::vector<int> values = {10, 11, 13};
|
||||
|
||||
SECTION("Sending multiple values.")
|
||||
{
|
||||
scheduler.start_tasks(await_signal_n(values, &signal));
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(1);
|
||||
REQUIRE(test_coro_called == 0);
|
||||
|
||||
for (int i = 0; i < values.size(); i++)
|
||||
{
|
||||
signal.send(values[i]);
|
||||
scheduler.loop(i + 1);
|
||||
REQUIRE(test_coro_called == i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
skullc::coro::Task<int> test_sleepy_coro_return(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 expected;
|
||||
}
|
||||
|
||||
skullc::coro::Task<> test_wait_all()
|
||||
{
|
||||
auto val = co_await skullc::coro::wait_all(
|
||||
test_sleepy_coro(0, 10ms),
|
||||
test_sleepy_coro_return(1, 20ms)
|
||||
);
|
||||
|
||||
REQUIRE(std::get<0>(val) == std::monostate{});
|
||||
REQUIRE(std::get<1>(val) == 1);
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
skullc::coro::Task<> testwait_first()
|
||||
{
|
||||
auto val = co_await skullc::coro::wait_first(
|
||||
test_sleepy_coro_return(0, 10ms),
|
||||
test_sleepy_coro_return(1, 20ms)
|
||||
);
|
||||
|
||||
REQUIRE(std::get<0>(val) == 0);
|
||||
REQUIRE(std::get<1>(val) == std::nullopt);
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("Wait all awaiter works.", "[coro],[wait_all]")
|
||||
{
|
||||
using namespace skullc::coro;
|
||||
Scheduler<std::vector> scheduler;
|
||||
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
||||
|
||||
test_coro_called = 0;
|
||||
|
||||
scheduler.start_tasks(test_wait_all());
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
scheduler.loop(10);
|
||||
scheduler.loop(10);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
scheduler.loop(20);
|
||||
scheduler.loop(20);
|
||||
scheduler.loop(20);
|
||||
|
||||
REQUIRE(test_coro_called == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Wait one awaiter works.", "[coro],[wait_first]")
|
||||
{
|
||||
using namespace skullc::coro;
|
||||
Scheduler<std::vector> scheduler;
|
||||
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
||||
|
||||
test_coro_called = 0;
|
||||
|
||||
scheduler.start_tasks(testwait_first());
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(0);
|
||||
scheduler.loop(0);
|
||||
|
||||
REQUIRE(test_coro_called == 0);
|
||||
scheduler.loop(10);
|
||||
scheduler.loop(10);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
scheduler.loop(20);
|
||||
scheduler.loop(20);
|
||||
scheduler.loop(20);
|
||||
|
||||
REQUIRE(test_coro_called == 1);
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
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>
|
||||
)
|
||||
|
||||
target_compile_definitions(coro
|
||||
INTERFACE
|
||||
SKULLC_WITH_CORO
|
||||
)
|
||||
|
||||
## INSTALL
|
||||
skullc_install_packages(skullc coro ${SKULLC_VERSION})
|
||||
@ -1,2 +0,0 @@
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-config-version.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/skullc-coro-targets.cmake)
|
||||
@ -1,230 +0,0 @@
|
||||
//
|
||||
// Created by erki on 25/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
#include "skullc/coro/task.hpp"
|
||||
|
||||
#include <coroutine>
|
||||
#include <variant>
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template<typename Awaitable>
|
||||
struct AwaitableResumeType
|
||||
{
|
||||
using raw_type = typename std::conditional<
|
||||
std::is_void_v<typename Awaitable::value_type>,
|
||||
std::monostate,
|
||||
typename Awaitable::value_type>::type;
|
||||
using value_type = std::optional<raw_type>;
|
||||
};
|
||||
|
||||
template<typename... Awaitables>
|
||||
struct WaitAllAwaitable
|
||||
{
|
||||
WaitAllAwaitable() = delete;
|
||||
|
||||
WaitAllAwaitable(Awaitables&&... args)
|
||||
: WaitAllAwaitable(std::index_sequence_for<Awaitables...>(), std::forward<Awaitables>(args)...)
|
||||
{}
|
||||
|
||||
template<std::size_t... Is>
|
||||
WaitAllAwaitable(std::index_sequence<Is...>, Awaitables&&... args)
|
||||
{
|
||||
((std::get<Is>(result) = std::nullopt), ...);
|
||||
(register_awaitable<Is>(std::forward<Awaitables>(args)), ...);
|
||||
}
|
||||
|
||||
~WaitAllAwaitable()
|
||||
{
|
||||
if (continuation)
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
|
||||
template<std::size_t I, typename Awaitable>
|
||||
void register_awaitable(Awaitable&& task)
|
||||
{
|
||||
start_awaitable();
|
||||
auto t = run_awaitable<I>(task);
|
||||
this_coro::scheduler().schedule(t.get_handle(), 0);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
template<std::size_t I, typename Awaitable>
|
||||
Task<> run_awaitable(Awaitable&& awaitable)
|
||||
{
|
||||
using decayed = std::decay_t<Awaitable>;
|
||||
if constexpr (std::is_void_v<typename decayed::value_type> == true)
|
||||
{
|
||||
co_await awaitable;
|
||||
std::get<I>(result) = std::monostate{};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto val = co_await awaitable;
|
||||
std::get<I>(result) = std::move(val);
|
||||
}
|
||||
|
||||
awaitable_completed();
|
||||
co_return;
|
||||
}
|
||||
|
||||
void awaitable_completed()
|
||||
{
|
||||
|
||||
pending--;
|
||||
if (pending == 0 && continuation)
|
||||
this_coro::scheduler().schedule(continuation, 0);
|
||||
}
|
||||
|
||||
void start_awaitable()
|
||||
{
|
||||
pending++;
|
||||
}
|
||||
|
||||
auto operator co_await() noexcept
|
||||
{
|
||||
struct Awaitable
|
||||
{
|
||||
WaitAllAwaitable* wait_all;
|
||||
|
||||
bool await_ready() { return false; }
|
||||
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
wait_all->continuation = h;
|
||||
}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
return std::move(wait_all->result);
|
||||
}
|
||||
};
|
||||
|
||||
return Awaitable{this};
|
||||
}
|
||||
|
||||
std::tuple<typename AwaitableResumeType<Awaitables>::value_type...> result;
|
||||
std::coroutine_handle<> continuation{};
|
||||
int pending = 0;
|
||||
};
|
||||
|
||||
template<typename... Awaitables>
|
||||
struct WaitFirstAwaitable
|
||||
{
|
||||
WaitFirstAwaitable() = delete;
|
||||
|
||||
WaitFirstAwaitable(Awaitables&&... args)
|
||||
: WaitFirstAwaitable(std::index_sequence_for<Awaitables...>(), std::forward<Awaitables>(args)...)
|
||||
{}
|
||||
|
||||
template<std::size_t... Is>
|
||||
WaitFirstAwaitable(std::index_sequence<Is...>, Awaitables&&... args)
|
||||
{
|
||||
((std::get<Is>(result) = std::nullopt), ...);
|
||||
pending = true;
|
||||
(register_awaitable<Is>(std::forward<Awaitables>(args)), ...);
|
||||
}
|
||||
|
||||
~WaitFirstAwaitable()
|
||||
{
|
||||
if (continuation)
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
|
||||
template<std::size_t I, typename Awaitable>
|
||||
void register_awaitable(Awaitable&& task)
|
||||
{
|
||||
auto t = run_awaitable<I>(task);
|
||||
this_coro::scheduler().schedule(t.get_handle(), 0);
|
||||
coroutines[I] = t.get_handle();
|
||||
t.detach();
|
||||
}
|
||||
|
||||
template<std::size_t I, typename Awaitable>
|
||||
Task<> run_awaitable(Awaitable&& awaitable)
|
||||
{
|
||||
using decayed = std::decay_t<Awaitable>;
|
||||
if constexpr (std::is_void_v<typename decayed::value_type> == true)
|
||||
{
|
||||
co_await awaitable;
|
||||
awaitable_completed<I>(std::monostate{});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto val = co_await awaitable;
|
||||
awaitable_completed<I>(std::move(val));
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
template<std::size_t I>
|
||||
void awaitable_completed(auto&& val)
|
||||
{
|
||||
if (pending)
|
||||
{
|
||||
pending = false;
|
||||
for (auto j = 0; j < coroutines.size(); j++)
|
||||
{
|
||||
if (j != I)
|
||||
{
|
||||
this_coro::scheduler().remove(coroutines[j]);
|
||||
coroutines[j].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
std::get<I>(result) = val;
|
||||
this_coro::scheduler().schedule(continuation, 0);
|
||||
}
|
||||
}
|
||||
|
||||
auto operator co_await() noexcept
|
||||
{
|
||||
struct Awaitable
|
||||
{
|
||||
WaitFirstAwaitable* wait_first;
|
||||
|
||||
bool await_ready() { return false; }
|
||||
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
wait_first->continuation = h;
|
||||
}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
return std::move(wait_first->result);
|
||||
}
|
||||
};
|
||||
|
||||
return Awaitable{this};
|
||||
}
|
||||
|
||||
std::array<std::coroutine_handle<>, sizeof...(Awaitables)> coroutines;
|
||||
std::tuple<typename AwaitableResumeType<Awaitables>::value_type...> result;
|
||||
std::coroutine_handle<> continuation{};
|
||||
int pending = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename... Awaitables>
|
||||
auto wait_all(Awaitables&&... args)
|
||||
{
|
||||
return detail::WaitAllAwaitable<Awaitables...>(std::forward<Awaitables>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Awaitables>
|
||||
auto wait_first(Awaitables&&... args)
|
||||
{
|
||||
return detail::WaitFirstAwaitable<Awaitables...>(std::forward<Awaitables>(args)...);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
//
|
||||
// Created by erki on 01/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <coroutine>
|
||||
#include <optional>
|
||||
|
||||
#include "peripherals_hal_concepts.hpp"
|
||||
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/this_coro.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), previous_is_set(pin->read())
|
||||
{}
|
||||
|
||||
~PinEdgeAwaiter() override
|
||||
{
|
||||
if (continuation)
|
||||
this_coro::scheduler().remove(continuation);
|
||||
|
||||
if (stored_coro)
|
||||
this_coro::scheduler().remove(stored_coro);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
struct ISRAwaiter
|
||||
{
|
||||
ISRAwaiter() = default;
|
||||
|
||||
~ISRAwaiter()
|
||||
{
|
||||
if (continuation)
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
|
||||
bool await_ready()
|
||||
{
|
||||
return isr_completed;
|
||||
}
|
||||
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
continuation = h;
|
||||
suspended = true;
|
||||
}
|
||||
|
||||
auto await_resume()
|
||||
{
|
||||
continuation = {};
|
||||
}
|
||||
|
||||
void set_completed()
|
||||
{
|
||||
isr_completed = true;
|
||||
if (suspended == true)
|
||||
this_coro::scheduler().schedule(continuation, 0);
|
||||
}
|
||||
|
||||
std::coroutine_handle<> continuation;
|
||||
std::atomic<bool> isr_completed = false;
|
||||
std::atomic<bool> suspended = false;
|
||||
};
|
||||
|
||||
}// namespace skullc::coro
|
||||
@ -1,130 +0,0 @@
|
||||
//
|
||||
// Created by erki on 30/01/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <coroutine>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include "skullc/coro/scheduler_base.hpp"
|
||||
#include "skullc/coro/task.hpp"
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
concept TaskType = requires(T task) {
|
||||
{
|
||||
task.get_handle().resume()
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
public:
|
||||
using Container = C<std::pair<uint32_t, std::coroutine_handle<>>>;
|
||||
using PollerContainer = C<TaskPoller*>;
|
||||
|
||||
Scheduler() = default;
|
||||
|
||||
Scheduler(const Scheduler&) = delete;
|
||||
Scheduler(Scheduler&&) = delete;
|
||||
Scheduler& operator=(const Scheduler&) = delete;
|
||||
Scheduler& operator=(Scheduler&&) = delete;
|
||||
|
||||
template<TaskType... T>
|
||||
void start_tasks(T... tasks)
|
||||
{
|
||||
start(tasks...);
|
||||
}
|
||||
|
||||
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::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
|
||||
{
|
||||
bool found = false;
|
||||
for (auto& p : scheduled_)
|
||||
if (p.second == handle)
|
||||
{
|
||||
std::swap(p, scheduled_.back());
|
||||
scheduled_.pop_back();
|
||||
std::make_heap(scheduled_.begin(), scheduled_.end(), cmp);
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pollers_.size() > 0)
|
||||
pollers_.erase(std::remove_if(pollers_.begin(), pollers_.end(),
|
||||
[handle](const auto* poller) { return poller->stored_coro == handle; }));
|
||||
return found;
|
||||
}
|
||||
|
||||
private:
|
||||
Container scheduled_;
|
||||
PollerContainer pollers_;
|
||||
|
||||
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
|
||||
@ -1,32 +0,0 @@
|
||||
//
|
||||
// Created by erki on 02/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#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;
|
||||
};
|
||||
|
||||
}// namespace skullc::coro
|
||||
@ -1,59 +0,0 @@
|
||||
//
|
||||
// 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};
|
||||
}
|
||||
|
||||
}// namespace skullc::coro
|
||||
@ -1,92 +0,0 @@
|
||||
//
|
||||
// Created by erki on 06/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
|
||||
namespace skullc::coro
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
class Signal
|
||||
{
|
||||
struct Awaiter
|
||||
{
|
||||
Signal* signal;
|
||||
std::coroutine_handle<> continuation;
|
||||
|
||||
Awaiter() = delete;
|
||||
Awaiter(const Awaiter&) = delete;
|
||||
Awaiter(Awaiter&&) = delete;
|
||||
explicit Awaiter(Signal* signal)
|
||||
: signal(signal)
|
||||
{}
|
||||
|
||||
Awaiter& operator=(const Awaiter&) = delete;
|
||||
Awaiter& operator=(Awaiter&&) = delete;
|
||||
|
||||
~Awaiter()
|
||||
{
|
||||
if (continuation)
|
||||
this_coro::scheduler().remove(continuation);
|
||||
}
|
||||
|
||||
bool await_ready() { return false; }
|
||||
|
||||
void await_suspend(std::coroutine_handle<> h)
|
||||
{
|
||||
continuation = h;
|
||||
}
|
||||
|
||||
T&& await_resume()
|
||||
{
|
||||
continuation = {};
|
||||
return std::move(*signal->data_);
|
||||
}
|
||||
|
||||
T&& get_return_object() noexcept
|
||||
{
|
||||
return std::move(*signal->data_);
|
||||
}
|
||||
|
||||
void trigger_resume()
|
||||
{
|
||||
this_coro::scheduler().schedule(continuation, 0);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
void send(const T& value)
|
||||
{
|
||||
data_.emplace(value);
|
||||
|
||||
if (awaiter_.has_value())
|
||||
awaiter_->trigger_resume();
|
||||
}
|
||||
|
||||
void send(T&& value)
|
||||
{
|
||||
data_.emplace(value);
|
||||
|
||||
if (awaiter_.has_value())
|
||||
awaiter_->trigger_resume();
|
||||
}
|
||||
|
||||
auto& receive()
|
||||
{
|
||||
awaiter_.emplace(this);
|
||||
|
||||
return *awaiter_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<T> data_;
|
||||
std::optional<Awaiter> awaiter_;
|
||||
};
|
||||
|
||||
}// namespace skullc::coro
|
||||
@ -1,59 +0,0 @@
|
||||
//
|
||||
// Created by erki on 01/02/25.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "skullc/coro/scheduler.hpp"
|
||||
#include "skullc/coro/this_coro.hpp"
|
||||
|
||||
#include "peripherals_hal_concepts.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()};
|
||||
}
|
||||
|
||||
template<Peripherals::Hal::StaticHal Hal>
|
||||
auto sleep(const std::chrono::duration<uint32_t, std::milli>& duration)
|
||||
{
|
||||
return detail::TimeAwaitable{Hal::getMillis() + duration.count()};
|
||||
}
|
||||
|
||||
}// namespace skullc::coro
|
||||
@ -1,160 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
@ -1,30 +0,0 @@
|
||||
//
|
||||
// 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