Files
pip/libs/main/thread/pitimer.cpp
2022-11-10 15:44:35 +03:00

732 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
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 <http://www.gnu.org/licenses/>.
*/
#include "pitimer.h"
#include "piincludes_p.h"
#include "piconditionvar.h"
#ifdef PIP_TIMER_RT
# include <csignal>
#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"<<interval_ms<<"...";
running_ = startTimer(interval_ms);
return running_;
}
void _PITimerBase::startDeferred(double interval_ms, PIDateTime start_datetime) {
if (isRunning()) stop();
deferred_ = true;
deferred_mode = true;
deferred_datetime = start_datetime;
setInterval(interval_ms);
running_ = startTimer(interval_ms);
}
void _PITimerBase::startDeferred(double interval_ms, double delay_ms) {
if (isRunning()) stop();
deferred_ = true;
deferred_mode = false;
deferred_delay = delay_ms;
setInterval(interval_ms);
running_ = startTimer(interval_ms);
}
bool _PITimerBase::stop() {
//piCout << GetCurrentThreadId() << "_PITimerBase::stop" << wait << isRunning();
if (!isRunning()) return true;
//piCout << "_PITimerBase::stopTimer ...";
running_ = !stopTimer();
return !running_;
}
class _PITimerImp_Thread: public _PITimerBase {
public:
_PITimerImp_Thread();
virtual ~_PITimerImp_Thread() {stop();}
protected:
void prepareStart(double interval_ms);
bool threadFunc(); // returns true if repeat is needed
int wait_dt, wait_dd, wait_tick;
private:
bool startTimer(double interval_ms) override;
bool stopTimer() override;
static void threadFuncS(void * d) {((_PITimerImp_Thread*)d)->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");
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 = PISystemTime::fromSeconds(1.);
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");
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<void ()> slot, PITimer::TimerImplementation ti) {
imp_mode = ti;
initFirst();
ret_func = [slot](void *, int){slot();};
}
PITimer::PITimer(std::function<void (void *)> 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<PITimer*>(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();
}