/* 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(); }