/*
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 "piincludes_p.h"
#include "piliterals.h"
#ifdef PIP_TIMER_RT
# include
#endif
//! \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(void * data, int delimiter) or [lambda
//! expression](https://en.cppreference.com/w/cpp/language/lambda);
//! * event - \a tickEvent();
//! * virtual function - \a tick().
//!
//! Lambda should be [ ]( ){ } or [ ](void*){ } format.
//!
//! All these variants are equivalent, use most applicable.
//! \~russian
//! Варианты уведомления:
//! * "slot" - статический метод в формате void func(void * data, int delimiter) или
//! [лямбда-выражение](https://ru.cppreference.com/w/cpp/language/lambda);
//! * event - \a tickEvent();
//! * виртуальный метод - \a tick().
//!
//! Лямбда-функция должна быть в формате [ ]( ){ } или [ ](void*){ }.
//!
//! Все варианты аналогичны друг другу, используйте самый удобный.
//!
//!
//! \~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(void * , int delim) {
//! piCout << "tick with delimiter" << delim;
//! };
//! void tfunc4(void * , 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.stop();
//! timer.waitForFinish();
//! 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
//!
//! \}
_PITimerBase::_PITimerBase() {
interval_ = 1000;
deferred_delay = 0.;
running_ = deferred_ = deferred_mode = false;
tfunc = 0;
parent = 0;
}
void _PITimerBase::setInterval(double i) {
interval_ = i;
if (isRunning()) {
// piCout << "change interval runtime";
stop();
start();
}
}
bool _PITimerBase::start(double interval_ms) {
if (isRunning()) stop();
deferred_ = false;
setInterval(interval_ms);
// piCout << "_PITimerBase::startTimer"<threadFunc(); }
void adjustTimes();
bool smallWait(int ms);
PIThread thread_;
PISystemTime st_time, st_inc, st_wait, st_odt;
PIConditionVariable event;
};
#ifdef PIP_TIMER_RT
struct _PITimerImp_RT_Private_;
class _PITimerImp_RT: public _PITimerBase {
public:
_PITimerImp_RT();
virtual ~_PITimerImp_RT();
protected:
private:
bool startTimer(double interval_ms) override;
bool stopTimer() override;
int ti;
_PITimerImp_RT_Private_ * priv;
};
#endif
class _PITimerImp_Pool: public _PITimerImp_Thread {
public:
_PITimerImp_Pool();
virtual ~_PITimerImp_Pool() { stop(); }
private:
class Pool: public PIThread {
public:
static Pool * instance();
void add(_PITimerImp_Pool * t);
void remove(_PITimerImp_Pool * t);
void run() override;
PIVector<_PITimerImp_Pool *> timers, to_remove;
private:
explicit Pool();
virtual ~Pool();
};
bool startTimer(double interval_ms) override;
bool stopTimer() override;
};
_PITimerImp_Thread::_PITimerImp_Thread() {
thread_.setName("__S__PITimerImp_Thread::thread"_a);
wait_dt = 1000;
wait_dd = 2000;
wait_tick = 1000;
// piCout << "_PITimerImp_Thread" << this << ", thread& =" << &thread_;
// piCout << "new _PITimerImp_Thread";
}
void _PITimerImp_Thread::prepareStart(double interval_ms) {
if (interval_ms <= 0.) {
piCout << "Achtung! Start PITimer with interval <= 0!";
piCout << "Achtung! Parent" << parent;
assert(interval_ms > 0.);
}
st_inc = PISystemTime::fromMilliseconds(interval_ms);
st_odt = st_inc * 5;
if (st_odt.toSeconds() < 1.) st_odt = 1_s;
if (deferred_) {
if (!deferred_mode) st_time = PISystemTime::current(true) + PISystemTime::fromMilliseconds(deferred_delay);
st_time -= st_inc;
} else
st_time = PISystemTime::current(true) + st_inc;
}
bool _PITimerImp_Thread::startTimer(double interval_ms) {
prepareStart(interval_ms);
thread_.setData(this);
return thread_.start(threadFuncS);
}
bool _PITimerImp_Thread::stopTimer() {
thread_.stop();
event.notifyAll();
thread_.waitForFinish();
// if (wait)
// if (!thread_.waitForFinish(10))
// if (thread_.isRunning())
// thread_.terminate();
return true;
}
bool _PITimerImp_Thread::threadFunc() {
if (!running_) return false;
if (deferred_) {
PISystemTime dwt;
int wth(wait_dt);
if (deferred_mode) {
dwt = deferred_datetime.toSystemTime() - PISystemTime::current();
wth = wait_dd;
} else
dwt = st_time - PISystemTime::current(true);
if (wth > 0) {
if (dwt.toMilliseconds() > wth + 1.) {
smallWait(wth);
return false;
} else {
if (!smallWait(dwt.toMilliseconds())) return false;
deferred_ = false;
st_time = PISystemTime::current(true);
}
} else {
if (dwt.toMilliseconds() > 0.1) return false;
}
}
st_wait = st_time - PISystemTime::current(true);
// piCout << "wait" << this << st_wait;
if (st_wait.abs() > st_odt || st_wait.seconds <= -5) {
// piCout << &thread_ << "adjust" << "...";
adjustTimes();
// piCout << &thread_ << "adjust" << "ok";
return true;
}
if (wait_tick > 0) {
if (st_wait.toMilliseconds() > wait_tick + 1.) {
smallWait(wait_tick);
return false;
} else {
// piCout << &thread_ << "sleep for" << st_wait;
if (!smallWait(st_wait.toMilliseconds())) return false;
}
} else {
if (st_wait.toMilliseconds() > 0.1) return false;
}
st_time += st_inc;
if (!parent->isPIObject()) {
piCout << "Achtung! PITimer \"parent\" is not PIObject!";
return false;
}
// piCout << &thread_ << "tfunc" << "...";
tfunc(parent);
// piCout << &thread_ << "tfunc" << "ok";
return true;
}
void _PITimerImp_Thread::adjustTimes() {
PISystemTime cst = PISystemTime::current(true);
if (st_time < cst) {
int rs = (cst - st_time).toSeconds() / st_inc.toSeconds();
if (rs >= 100)
st_time = cst + st_inc;
else {
while (st_time < cst)
st_time += st_inc;
}
} else {
int rs = (st_time - cst).toSeconds() / st_inc.toSeconds();
if (rs >= 100)
st_time = cst - st_inc;
else {
cst += st_inc;
while (st_time > cst)
st_time -= st_inc;
}
}
}
bool _PITimerImp_Thread::smallWait(int ms) {
if (thread_.isStopping()) return false;
if (ms > 0) {
thread_.mutex().lock();
event.waitFor(thread_.mutex(), ms);
thread_.mutex().unlock();
}
return !thread_.isStopping();
}
#ifdef PIP_TIMER_RT
void threadFuncS(sigval sv) {
((_PITimerImp_RT *)sv.sival_ptr)->tfunc(((_PITimerImp_RT *)sv.sival_ptr)->parent);
}
struct _PITimerImp_RT_Private_ {
itimerspec spec;
timer_t tt;
sigevent se;
};
_PITimerImp_RT::_PITimerImp_RT() {
// piCout << "new _PITimerImp_RT";
priv = new _PITimerImp_RT_Private_();
priv->tt = 0;
ti = -1;
memset(&(priv->se), 0, sizeof(priv->se));
priv->se.sigev_notify = SIGEV_THREAD;
priv->se.sigev_value.sival_ptr = this;
priv->se.sigev_notify_function = threadFuncS;
priv->se.sigev_notify_attributes = 0;
}
_PITimerImp_RT::~_PITimerImp_RT() {
stop();
delete priv;
}
bool _PITimerImp_RT::startTimer(double interval_ms) {
int flags(0);
priv->spec.it_interval.tv_nsec = ((int)(interval_ms * 1000) % 1000000) * 1000;
priv->spec.it_interval.tv_sec = (time_t)(interval_ms / 1000);
if (deferred_) {
if (deferred_mode) {
PISystemTime dtm = deferred_datetime.toSystemTime();
priv->spec.it_value.tv_nsec = dtm.nanoseconds;
priv->spec.it_value.tv_sec = dtm.seconds;
flags = TIMER_ABSTIME;
} else {
priv->spec.it_value.tv_nsec = ((int)(deferred_delay * 1000) % 1000000) * 1000;
priv->spec.it_value.tv_sec = (time_t)(deferred_delay / 1000);
}
} else {
priv->spec.it_value = priv->spec.it_interval;
}
ti = timer_create(CLOCK_REALTIME, &(priv->se), &(priv->tt));
// cout << "***create timer " << msecs << " msecs\n";
if (ti == -1) {
piCout << "Can`t create RT timer for " << interval_ms << " msecs: " << errorString();
return false;
}
timer_settime(priv->tt, flags, &(priv->spec), 0);
return true;
}
bool _PITimerImp_RT::stopTimer() {
if (ti < 0) return true;
timer_delete(priv->tt);
ti = -1;
priv->tt = 0;
return true;
}
#endif
_PITimerImp_Pool::_PITimerImp_Pool(): _PITimerImp_Thread() {
wait_dt = wait_dd = wait_tick = 0;
// piCout << "new _PITimerImp_Pool";
}
_PITimerImp_Pool::Pool::Pool(): PIThread() {
setName("__S__PITimerImp_Pool::Pool"_a);
needLockRun(true);
#ifndef FREERTOS
timers.reserve(64);
start(PIP_MIN_MSLEEP * 5);
#else
start(PIP_MIN_MSLEEP);
#endif
}
_PITimerImp_Pool::Pool::~Pool() {
stop();
if (!waitForFinish(500)) terminate();
unlock();
timers.clear();
}
_PITimerImp_Pool::Pool * _PITimerImp_Pool::Pool::instance() {
static Pool pool;
return &pool;
}
void _PITimerImp_Pool::Pool::add(_PITimerImp_Pool * t) {
// piCout << "add ...";
lock();
to_remove.removeAll(t);
if (!timers.contains(t)) timers << t;
unlock();
// piCout << "add done";
}
void _PITimerImp_Pool::Pool::remove(_PITimerImp_Pool * t) {
// piCout << "remove ...";
lock();
to_remove << t;
unlock();
// piCout << "remove done";
}
void _PITimerImp_Pool::Pool::run() {
if (!to_remove.isEmpty()) {
piForeach(_PITimerImp_Pool * t, to_remove)
timers.removeAll(t);
to_remove.clear();
}
piForeach(_PITimerImp_Pool * t, timers)
t->threadFunc();
}
bool _PITimerImp_Pool::startTimer(double interval_ms) {
prepareStart(interval_ms);
Pool::instance()->add(this);
return true;
}
bool _PITimerImp_Pool::stopTimer() {
Pool::instance()->remove(this);
return true;
}
PITimer::PITimer(): PIObject() {
#ifdef FREERTOS
imp_mode = PITimer::Thread;
#else
imp_mode = PITimer::Thread;
#endif
initFirst();
}
PITimer::PITimer(PITimer::TimerImplementation ti): PIObject() {
imp_mode = ti;
initFirst();
}
PITimer::PITimer(TimerEvent slot, void * data, PITimer::TimerImplementation ti): PIObject() {
imp_mode = ti;
initFirst();
data_t = data;
ret_func = slot;
}
PITimer::PITimer(std::function slot, PITimer::TimerImplementation ti) {
imp_mode = ti;
initFirst();
ret_func = [slot](void *, int) { slot(); };
}
PITimer::PITimer(std::function slot, void * data, PITimer::TimerImplementation ti) {
imp_mode = ti;
initFirst();
data_t = data;
ret_func = [slot](void * d, int) { slot(d); };
}
PITimer::~PITimer() {
destroy();
}
double PITimer::interval() const {
init();
return imp->interval_;
}
void PITimer::setInterval(double ms) {
init();
setProperty("interval", ms);
imp->setInterval(ms);
}
bool PITimer::isRunning() const {
init();
return imp->running_;
}
bool PITimer::isStopped() const {
init();
return !imp->running_;
}
void PITimer::initFirst() {
lockRun = false;
callEvents = true;
data_t = 0;
ret_func = 0;
imp = 0;
setProperty("interval", 0.);
}
void PITimer::init() const {
if (imp) return;
switch (imp_mode) {
case PITimer::Pool: imp = new _PITimerImp_Pool(); break;
case PITimer::ThreadRT:
#ifdef PIP_TIMER_RT
imp = new _PITimerImp_RT();
break;
#else
piCoutObj << "Warning: \"ThreadRT\" is not available at this system! Using \"Thread\".";
#endif
case PITimer::Thread: imp = new _PITimerImp_Thread(); break;
default: piCout << "Fatal: invalid implementation() of" << this << "!"; assert(0);
}
if (!imp) return;
// piCout << this << "init" << imp;
imp->tfunc = tickImpS;
imp->parent = const_cast(this);
}
void PITimer::destroy() {
if (!imp) return;
// piCout << this << "destroy" << imp;
imp->stop();
delete imp;
imp = 0;
}
void PITimer::tickImp() {
if (!isRunning()) return;
if (lockRun) lock();
if (ret_func) ret_func(data_t, 1);
tick(data_t, 1);
tickEvent(data_t, 1);
if (callEvents) maybeCallQueuedEvents();
piForeach(Delimiter & i, delims) {
if (i.delim > ++(i.tick)) continue;
i.tick = 0;
if (i.slot)
i.slot(data_t, i.delim);
else if (ret_func)
ret_func(data_t, i.delim);
tick(data_t, i.delim);
tickEvent(data_t, i.delim);
}
if (lockRun) unlock();
}
bool PITimer::start() {
init();
// piCout << this << "start" << imp;
return imp->start();
}
bool PITimer::start(double interval_ms_d) {
init();
// piCout << this << "start" << imp << interval_ms_d;
setProperty("interval", interval_ms_d);
return imp->start(interval_ms_d);
}
bool PITimer::start(int interval_ms_i) {
return start((double)interval_ms_i);
}
//! \~\details
//! \~english
//! Timer wait "delay_msecs" milliseconds and then normally starts with \a interval() loop delay
//! \~russian
//! Таймер ожидает "delay_msecs" миллисекунд, а затем стартует с интервалом \a interval()
void PITimer::startDeferred(double delay_ms) {
init();
imp->startDeferred(delay_ms);
}
//! \~\details
//! \~english
//! Timer wait "delay_msecs" milliseconds and then normally starts with "interval_msecs" loop delay
//! \~russian
//! Таймер ожидает "delay_msecs" миллисекунд, а затем стартует с интервалом "interval_msecs"
void PITimer::startDeferred(double interval_ms, double delay_ms) {
init();
imp->startDeferred(interval_ms, delay_ms);
}
//! \~\details
//! \~english
//! Timer wait until "start_datetime" and then normally starts with \a interval() loop delay
//! \~russian
//! Таймер ожидает наступления "start_datetime", а затем стартует с интервалом \a interval()
void PITimer::startDeferred(PIDateTime start_datetime) {
startDeferred(imp->interval_, start_datetime);
}
//! \~\details
//! \~english
//! Timer wait until "start_datetime" and then normally starts with "interval_msecs" loop delay
//! \~russian
//! Таймер ожидает наступления "start_datetime", а затем стартует с интервалом "interval_msecs"
void PITimer::startDeferred(double interval_ms, PIDateTime start_datetime) {
init();
imp->startDeferred(interval_ms, start_datetime);
}
bool PITimer::restart() {
init();
imp->stop();
return imp->start();
}
bool PITimer::stop() {
init();
// piCout << this << "stop" << imp << wait;
return imp->stop();
}