diff --git a/CppTick/CMakeLists.txt b/CppTick/CMakeLists.txt index 49243ee..f575fa5 100644 --- a/CppTick/CMakeLists.txt +++ b/CppTick/CMakeLists.txt @@ -1,10 +1,13 @@ cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -add_library(cpptick INTERFACE) +add_library(cpptick STATIC + Src/timer.cpp +) + add_library(skullc::cpptick ALIAS cpptick) target_include_directories(cpptick - INTERFACE + PUBLIC $ $ ) @@ -16,7 +19,7 @@ set_target_properties(cpptick ) target_link_libraries(cpptick - INTERFACE + PUBLIC skullc::utility ) diff --git a/CppTick/Inc/cpptick/argstore.hpp b/CppTick/Inc/cpptick/argstore.hpp index 9290b45..2f5e3e1 100644 --- a/CppTick/Inc/cpptick/argstore.hpp +++ b/CppTick/Inc/cpptick/argstore.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace cpptick { diff --git a/CppTick/Inc/cpptick/cpptick.hpp b/CppTick/Inc/cpptick/cpptick.hpp index 5e5bb24..a980398 100644 --- a/CppTick/Inc/cpptick/cpptick.hpp +++ b/CppTick/Inc/cpptick/cpptick.hpp @@ -5,5 +5,6 @@ #pragma once #include "cpptick/argstore.hpp" +#include "cpptick/timer.hpp" #include "cpptick/scheduler.hpp" #include "cpptick/slot.hpp" diff --git a/CppTick/Inc/cpptick/scheduler.hpp b/CppTick/Inc/cpptick/scheduler.hpp index 0684e4d..d45f97e 100644 --- a/CppTick/Inc/cpptick/scheduler.hpp +++ b/CppTick/Inc/cpptick/scheduler.hpp @@ -5,21 +5,40 @@ #pragma once #include +#include +#include #include #include #include "cpptick/argstore.hpp" +#include "cpptick/timer.hpp" namespace cpptick { -struct Scheduler +struct BaseScheduler { using StoredCall = std::pair*, ArgStorage>; Utility::Ringbuffer stored_calls; + using StoredTimer = std::pair; + std::array stored_timers; + + BaseScheduler() + { + stored_timers.fill({-1u, nullptr}); + } + + virtual ~BaseScheduler() = default; + void tick() { + if (auto start = stored_timers.begin(); + start->first != -1u) + { + checkInvokeTimers(); + } + if (!stored_calls.empty()) { auto* f = stored_calls.begin()->first; @@ -39,6 +58,66 @@ struct Scheduler storage.reset(); return storage; } + + virtual void storeTimer(Timer* timer) = 0; + virtual void checkInvokeTimers() = 0; +}; + +template +struct Scheduler : BaseScheduler +{ + Scheduler() = default; + + void storeTimer(Timer* timer) override + { + const std::uint32_t current_time = HAL::getMillis(); + const std::uint32_t time_to_run = timer->expirationTime(current_time); + + auto empty_slot = std::find_if(stored_timers.begin(), stored_timers.end(), + [](const StoredTimer& timer) -> bool { + return timer.first == -1u; + }); + + if (empty_slot == stored_timers.end()) + return; + + *empty_slot = { time_to_run, timer }; + std::sort(stored_timers.begin(), stored_timers.end()); + } + + void checkInvokeTimers() override + { + const std::uint32_t current_time = HAL::getMillis(); + + bool timers_executed = false; + for (auto timer = stored_timers.begin(); timer->first != -1u; timer++) + { + if (timer->first <= current_time) + { + timers_executed = true; + storeCall(timer->second->getSlot()); + + if (timer->second->is_one_shot) + { + timer->first = -1u; + timer->second = nullptr; + } + else + { + timer->first = timer->second->expirationTime(current_time); + } + } + else + { + break; + } + } + + if (timers_executed) + { + std::sort(stored_timers.begin(), stored_timers.end()); + } + } }; } diff --git a/CppTick/Inc/cpptick/slot.hpp b/CppTick/Inc/cpptick/slot.hpp index cb19702..c954f77 100644 --- a/CppTick/Inc/cpptick/slot.hpp +++ b/CppTick/Inc/cpptick/slot.hpp @@ -24,12 +24,12 @@ template struct Slot { std::array*, 12> signals; - Scheduler* scheduler; + BaseScheduler* scheduler; Utility::FunctionOwned, void (ArgStorage&)> invoke_ptr; Slot() = delete; - explicit Slot(Scheduler* sched) + explicit Slot(BaseScheduler* sched) : scheduler(sched) , invoke_ptr(*this, &Slot::callUp) { diff --git a/CppTick/Inc/cpptick/timer.hpp b/CppTick/Inc/cpptick/timer.hpp new file mode 100644 index 0000000..7bd9548 --- /dev/null +++ b/CppTick/Inc/cpptick/timer.hpp @@ -0,0 +1,63 @@ +// +// Created by erki on 24/10/23. +// + + +#pragma once + +#include + +#include + +#include "cpptick/argstore.hpp" + +namespace cpptick +{ + +struct BaseScheduler; + +struct Timer +{ + Timer(BaseScheduler* scheduler, const std::uint32_t timeout_ms, const bool one_shot = false) + : scheduler_(scheduler) + , period_ms(timeout_ms) + , is_one_shot(one_shot) + { } + + Timer() = delete; + Timer(const Timer&) = delete; + Timer(Timer&&) = delete; + Timer& operator=(const Timer&) = delete; + Timer& operator=(Timer&&) = delete; + + void start(); + + template + void setSlot(T& slot) + { + setSlot(&slot.invoke_ptr); + } + + void setSlot(Utility::IFunction* slot) + { + slot_ = slot; + } + + Utility::IFunction* getSlot() + { + return slot_; + } + + std::uint32_t expirationTime(const std::uint32_t current_time) const + { + return period_ms + current_time; + } + + std::uint32_t period_ms; + bool is_one_shot; +private: + BaseScheduler* scheduler_; + Utility::IFunction* slot_ = nullptr; +}; + +} diff --git a/CppTick/Src/timer.cpp b/CppTick/Src/timer.cpp new file mode 100644 index 0000000..860cba0 --- /dev/null +++ b/CppTick/Src/timer.cpp @@ -0,0 +1,17 @@ +// +// Created by erki on 24/10/23. +// + +#include "cpptick/timer.hpp" + +#include "cpptick/scheduler.hpp" + +namespace cpptick +{ + +void Timer::start() +{ + scheduler_->storeTimer(this); +} + +} diff --git a/Tests/cpptick.cpp b/Tests/cpptick.cpp index 4270547..ddc0570 100644 --- a/Tests/cpptick.cpp +++ b/Tests/cpptick.cpp @@ -6,6 +6,11 @@ #include "cpptick/cpptick.hpp" +#include +#include + +using namespace std::chrono_literals; + namespace { @@ -21,11 +26,27 @@ void callbackByN(int to_add) callback_count += to_add; } +struct TestHal +{ + static std::uint32_t getMillis() + { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::system_clock; + + return std::uint32_t( + duration_cast(system_clock::now().time_since_epoch()) + .count() + ); + } +}; + } + TEST_CASE("Slot calls function properly with void args.", "[cpptick]") { - cpptick::Scheduler scheduler; + cpptick::Scheduler scheduler; cpptick::Slot slot(&scheduler); auto* f = slot.connect(callbackByOne); @@ -76,7 +97,7 @@ TEST_CASE("Slot calls function properly with void args.", "[cpptick]") TEST_CASE("Slot calls function properly with args.", "[cpptick]") { - cpptick::Scheduler scheduler; + cpptick::Scheduler scheduler; cpptick::Slot slot(&scheduler); auto* f = slot.connect(callbackByN); @@ -96,3 +117,41 @@ TEST_CASE("Slot calls function properly with args.", "[cpptick]") delete f; } + +TEST_CASE("Timer will be invoked.", "[cpptick]") +{ + cpptick::Scheduler scheduler; + + cpptick::Slot slot(&scheduler); + auto* f = slot.connect(callbackByOne); + + callback_count = 0; + REQUIRE(callback_count == 0); + + cpptick::Timer timer(&scheduler, 100, true); + timer.setSlot(slot); + + SECTION("Timer will be invoked after 100 milliseconds.") + { + timer.start(); + auto current = TestHal::getMillis(); + + scheduler.tick(); + CHECK(callback_count == 0); + + std::this_thread::sleep_for(110ms); + + scheduler.tick(); + CHECK(callback_count == 1); + + SECTION("Single shot timer isn't executed again.") + { + CHECK(callback_count == 1); + + std::this_thread::sleep_for(110ms); + CHECK(callback_count == 1); + } + } + + delete f; +}