cpptick: add timers

This commit is contained in:
erki 2023-10-24 23:22:47 +03:00
parent 80a4f39f7b
commit 29ffd478ef
8 changed files with 231 additions and 8 deletions

View File

@ -1,10 +1,13 @@
cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 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) add_library(skullc::cpptick ALIAS cpptick)
target_include_directories(cpptick target_include_directories(cpptick
INTERFACE PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/Inc>
$<INSTALL_INTERFACE:include> $<INSTALL_INTERFACE:include>
) )
@ -16,7 +19,7 @@ set_target_properties(cpptick
) )
target_link_libraries(cpptick target_link_libraries(cpptick
INTERFACE PUBLIC
skullc::utility skullc::utility
) )

View File

@ -7,6 +7,7 @@
#include <type_traits> #include <type_traits>
#include <array> #include <array>
#include <cstring> #include <cstring>
#include <memory>
namespace cpptick namespace cpptick
{ {

View File

@ -5,5 +5,6 @@
#pragma once #pragma once
#include "cpptick/argstore.hpp" #include "cpptick/argstore.hpp"
#include "cpptick/timer.hpp"
#include "cpptick/scheduler.hpp" #include "cpptick/scheduler.hpp"
#include "cpptick/slot.hpp" #include "cpptick/slot.hpp"

View File

@ -5,21 +5,40 @@
#pragma once #pragma once
#include <tuple> #include <tuple>
#include <array>
#include <algorithm>
#include <utility_function.hpp> #include <utility_function.hpp>
#include <utility_ringbuffer.hpp> #include <utility_ringbuffer.hpp>
#include "cpptick/argstore.hpp" #include "cpptick/argstore.hpp"
#include "cpptick/timer.hpp"
namespace cpptick namespace cpptick
{ {
struct Scheduler struct BaseScheduler
{ {
using StoredCall = std::pair<Utility::IFunction<void (ArgStorage&)>*, ArgStorage>; using StoredCall = std::pair<Utility::IFunction<void (ArgStorage&)>*, ArgStorage>;
Utility::Ringbuffer<StoredCall, 12> stored_calls; Utility::Ringbuffer<StoredCall, 12> stored_calls;
using StoredTimer = std::pair<std::uint32_t, Timer*>;
std::array<StoredTimer, 12> stored_timers;
BaseScheduler()
{
stored_timers.fill({-1u, nullptr});
}
virtual ~BaseScheduler() = default;
void tick() void tick()
{ {
if (auto start = stored_timers.begin();
start->first != -1u)
{
checkInvokeTimers();
}
if (!stored_calls.empty()) if (!stored_calls.empty())
{ {
auto* f = stored_calls.begin()->first; auto* f = stored_calls.begin()->first;
@ -39,6 +58,66 @@ struct Scheduler
storage.reset(); storage.reset();
return storage; return storage;
} }
virtual void storeTimer(Timer* timer) = 0;
virtual void checkInvokeTimers() = 0;
};
template<typename HAL>
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());
}
}
}; };
} }

View File

@ -24,12 +24,12 @@ template<typename R, SlotArgument... Args>
struct Slot<R(Args...)> struct Slot<R(Args...)>
{ {
std::array<Utility::IFunction<R (Args...)>*, 12> signals; std::array<Utility::IFunction<R (Args...)>*, 12> signals;
Scheduler* scheduler; BaseScheduler* scheduler;
Utility::FunctionOwned<Slot<R(Args...)>, void (ArgStorage&)> invoke_ptr; Utility::FunctionOwned<Slot<R(Args...)>, void (ArgStorage&)> invoke_ptr;
Slot() = delete; Slot() = delete;
explicit Slot(Scheduler* sched) explicit Slot(BaseScheduler* sched)
: scheduler(sched) : scheduler(sched)
, invoke_ptr(*this, &Slot<R(Args...)>::callUp) , invoke_ptr(*this, &Slot<R(Args...)>::callUp)
{ {

View File

@ -0,0 +1,63 @@
//
// Created by erki on 24/10/23.
//
#pragma once
#include <cstdint>
#include <utility_function.hpp>
#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<typename T>
void setSlot(T& slot)
{
setSlot(&slot.invoke_ptr);
}
void setSlot(Utility::IFunction<void (ArgStorage&)>* slot)
{
slot_ = slot;
}
Utility::IFunction<void (ArgStorage&)>* 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<void (ArgStorage&)>* slot_ = nullptr;
};
}

17
CppTick/Src/timer.cpp Normal file
View File

@ -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);
}
}

View File

@ -6,6 +6,11 @@
#include "cpptick/cpptick.hpp" #include "cpptick/cpptick.hpp"
#include <chrono>
#include <thread>
using namespace std::chrono_literals;
namespace namespace
{ {
@ -21,11 +26,27 @@ void callbackByN(int to_add)
callback_count += 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<milliseconds>(system_clock::now().time_since_epoch())
.count()
);
}
};
} }
TEST_CASE("Slot calls function properly with void args.", "[cpptick]") TEST_CASE("Slot calls function properly with void args.", "[cpptick]")
{ {
cpptick::Scheduler scheduler; cpptick::Scheduler<TestHal> scheduler;
cpptick::Slot<void ()> slot(&scheduler); cpptick::Slot<void ()> slot(&scheduler);
auto* f = slot.connect(callbackByOne); 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]") TEST_CASE("Slot calls function properly with args.", "[cpptick]")
{ {
cpptick::Scheduler scheduler; cpptick::Scheduler<TestHal> scheduler;
cpptick::Slot<void (int)> slot(&scheduler); cpptick::Slot<void (int)> slot(&scheduler);
auto* f = slot.connect(callbackByN); auto* f = slot.connect(callbackByN);
@ -96,3 +117,41 @@ TEST_CASE("Slot calls function properly with args.", "[cpptick]")
delete f; delete f;
} }
TEST_CASE("Timer will be invoked.", "[cpptick]")
{
cpptick::Scheduler<TestHal> scheduler;
cpptick::Slot<void ()> 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;
}