/*
PIP - Platform Independent Primitives
Timer
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
*/
#include "pitimer.h"
#include "piconditionvar.h"
#include "piliterals_time.h"
#include "pithread.h"
//! \addtogroup Thread
//! \{
//! \class PITimer pitimer.h
//!
//! \~\brief
//! \~english Timer
//! \~russian Таймер
//!
//! \~\details
//!
//! \~english \section PITimer_sec0 Synopsis
//! \~russian \section PITimer_sec0 Краткий обзор
//! \~english
//! This class implements timer function. PIP timers supports 3 way to tick notify,
//! deferred start and frequency delimiters.
//!
//! \~russian
//! Этот класс реализует таймер. Таймер PIP поддерживает 3 варианта уведомления,
//! отложенный старт и делители частоты.
//!
//!
//! \~english \section PITimer_sec1 Notify variants
//! \~russian \section PITimer_sec1 Варианты уведомления
//! \~english
//! Notify variants:
//! * "slot" - static function with format void func(int delimiter) or [lambda
//! expression](https://en.cppreference.com/w/cpp/language/lambda);
//! * event - \a tickEvent();
//! * virtual function - \a tick().
//!
//! Lambda should be [ ]( ){ } or [ ](int){ } format.
//!
//! All these variants are equivalent, use most applicable.
//! \~russian
//! Варианты уведомления:
//! * "slot" - статический метод в формате void func(int delimiter) или
//! [лямбда-выражение](https://ru.cppreference.com/w/cpp/language/lambda);
//! * event - \a tickEvent();
//! * виртуальный метод - \a tick().
//!
//! Лямбда-функция должна быть в формате [ ]( ){ } или [ ](int){ }.
//!
//! Все варианты аналогичны друг другу, используйте самый удобный.
//!
//!
//! \~english \section PITimer_sec2 Frequency delimiters
//! \~russian \section PITimer_sec2 Делители частоты
//! \~english
//! Frequency delimiter is an integer number and "slot" function. If "slot" function is null
//! timer main "slot" will be used. Each delimiter numbers tick timer will be execute
//! delimiters or timer main "slot" function with \b delimiter value = delimiter number.
//!
//! Example:
//!
//! \~russian
//! Делитель частоты это целое число и необязательный метод "slot". Если метод не указан,
//! то будет использован основной "slot". Каждые \b delimiter тиков будет дополнительно вызван
//! "slot" делителя либо основной "slot" с аргументом \b delimiter равным значению делителя.
//!
//! Пример:
//!
//! \~\code
//! void tfunc(int delim) {
//! piCout << "tick with delimiter" << delim;
//! };
//! void tfunc4(int delim) {
//! piCout << "tick4 with delimiter" << delim;
//! };
//! int main() {
//! PITimer timer(tfunc);
//! timer.addDelimiter(2);
//! timer.addDelimiter(4, tfunc4);
//! timer.start(50);
//! piMSleep(200);
//! timer.stopAndWait();
//! return 0;
//! };
//! /* Result:
//! tick with delimiter 1
//! tick with delimiter 1
//! tick with delimiter 2
//! tick with delimiter 1
//! tick with delimiter 1
//! tick with delimiter 2
//! tick4 with delimiter 4
//! */
//! \endcode
//!
//! \}
PITimer::PITimer(): PIObject() {
initFirst();
}
PITimer::PITimer(std::function func) {
initFirst();
ret_func = func;
}
PITimer::PITimer(std::function func) {
initFirst();
ret_func = [func](int) { func(); };
}
PITimer::~PITimer() {
stopAndWait();
piDeleteSafety(thread);
}
PISystemTime PITimer::interval() const {
return m_interval;
}
void PITimer::setInterval(PISystemTime interval) {
if (m_interval == interval) return;
m_interval = interval;
if (isRunning()) {
// piCout << "change interval runtime";
stopAndWait();
start();
}
}
bool PITimer::isRunning() const {
return thread->isRunning();
}
bool PITimer::isStopping() const {
return thread->isStopping();
}
bool PITimer::waitForFinish(PISystemTime timeout) {
return thread->waitForFinish(timeout);
}
void PITimer::initFirst() {
thread = new PIThread([this] { threadFunc(); });
thread->setName("_S.PITimer.thread");
setProperty("interval", PISystemTime());
}
void PITimer::threadFunc() {
PISystemTime st_wait = m_time_next - PISystemTime::current(true);
// piCout << "wait" << this << st_wait;
if (st_wait.abs() > m_interval_x5 || st_wait.seconds <= -5) {
// piCout << &thread_ << "adjust" << "...";
adjustTimes();
// piCout << &thread_ << "adjust" << "ok";
return;
}
if (thread->isStopping()) return;
if (st_wait.isPositive()) {
thread->mutex().lock();
event.waitFor(thread->mutex(), st_wait);
thread->mutex().unlock();
}
if (thread->isStopping()) return;
m_time_next += m_interval;
// piCout << &thread_ << "tfunc" << "...";
execTick();
}
void PITimer::adjustTimes() {
PISystemTime cst = PISystemTime::current(true);
if (m_time_next < cst) {
int rs = (cst - m_time_next).toSeconds() / m_interval.toSeconds();
if (rs >= 100)
m_time_next = cst + m_interval;
else {
while (m_time_next < cst)
m_time_next += m_interval;
}
} else {
int rs = (m_time_next - cst).toSeconds() / m_interval.toSeconds();
if (rs >= 100)
m_time_next = cst - m_interval;
else {
cst += m_interval;
while (m_time_next > cst)
m_time_next -= m_interval;
}
}
}
void PITimer::execTick() {
if (!isRunning()) return;
if (lockRun) lock();
if (ret_func) ret_func(1);
tick(1);
tickEvent(1);
if (callEvents) maybeCallQueuedEvents();
for (Delimiter & i: delims) {
if (i.delim > ++(i.tick)) continue;
i.tick = 0;
if (i.func)
i.func(i.delim);
else if (ret_func)
ret_func(i.delim);
tick(i.delim);
tickEvent(i.delim);
}
if (lockRun) unlock();
}
bool PITimer::start() {
if (isRunning()) return true;
m_interval_x5 = m_interval * 5;
if (m_interval_x5.toSeconds() < 1.) m_interval_x5 = 1_s;
m_time_next = PISystemTime::current(true) + m_interval;
if (!thread->start()) return false;
return thread->waitForStart();
}
bool PITimer::start(PISystemTime interval) {
if (isRunning()) stopAndWait();
setInterval(interval);
return start();
}
bool PITimer::start(PISystemTime interval, std::function func) {
if (isRunning()) stopAndWait();
setInterval(interval);
setSlot(func);
return start();
}
void PITimer::stopAndWait(PISystemTime timeout) {
stop();
thread->waitForFinish(timeout);
}
void PITimer::addDelimiter(int delim, std::function func) {
delims << Delimiter(func, delim);
}
void PITimer::addDelimiter(int delim, std::function func) {
delims << Delimiter([func](int) { func(); }, delim);
}
void PITimer::removeDelimiter(int delim) {
for (int i = 0; i < delims.size_s(); ++i)
if (delims[i].delim == delim) {
delims.remove(i);
i--;
}
}
bool PITimer::restart() {
stopAndWait();
return start();
}
void PITimer::stop() {
thread->stop();
event.notifyAll();
}