Files
pip/libs/main/thread/pitimer.cpp
2024-09-17 15:58:06 +03:00

305 lines
7.6 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 "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<void(int)> func) {
initFirst();
ret_func = func;
}
PITimer::PITimer(std::function<void()> 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<void()> 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<void(int)> func) {
delims << Delimiter(func, delim);
}
void PITimer::addDelimiter(int delim, std::function<void()> 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();
}