/* * Copyright (C) 2005-2020 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #pragma once #ifdef __cplusplus #include "Thread.h" #include namespace kodi { namespace tools { //============================================================================== /// @defgroup cpp_kodi_tools_CTimer class CTimer /// @ingroup cpp_kodi_tools /// @brief **Time interval management**\n /// Class which enables a time interval to be called up by a given function or /// class by means of a thread. /// /// His code uses the support of platform-independent thread system introduced /// with C++11. /// /// /// ---------------------------------------------------------------------------- /// /// **Example:** /// ~~~~~~~~~~~~~{.cpp} /// #include /// /// class ATTRIBUTE_HIDDEN CExample /// { /// public: /// CExample() : m_timer([this](){TimerCall();}) /// { /// m_timer.Start(5000, true); // let call continuously all 5 seconds /// } /// /// void TimerCall() /// { /// fprintf(stderr, "Hello World\n"); /// } /// /// private: /// kodi::tools::CTimer m_timer; /// }; /// ~~~~~~~~~~~~~ /// ///@{ class CTimer : protected CThread { public: class ITimerCallback; //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Class constructor to pass individual other class as callback. /// /// @param[in] callback Child class of parent @ref ITimerCallback with /// implemented function @ref ITimerCallback::OnTimeout(). /// explicit CTimer(kodi::tools::CTimer::ITimerCallback* callback) : CTimer(std::bind(&ITimerCallback::OnTimeout, callback)) { } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Class constructor to pass individual function as callback. /// /// @param[in] callback Function to pass as callback about timeout. /// /// **Callback function style:** /// ~~~~~~~~~~~~~{.cpp} /// void TimerCallback() /// { /// } /// ~~~~~~~~~~~~~ explicit CTimer(std::function const& callback) : m_callback(callback) {} //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Class destructor. /// ~CTimer() override { Stop(true); } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Start the timer by given time in milliseconds to make his call /// by arrive of them. /// /// If interval is activated, it calls the associated callback function /// continuously in the given interval. /// /// @param[in] timeout Timeout in milliseconds /// @param[in] interval [opt] To run continuously if true, false only one time /// and default /// @return True if successfully done, false if not (callback missing, /// timeout = 0 or was already running. /// bool Start(uint64_t timeout, bool interval = false) { using namespace std::chrono; if (m_callback == nullptr || timeout == 0 || IsRunning()) return false; m_timeout = milliseconds(timeout); m_interval = interval; CreateThread(); return true; } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Stop the timer if it is active. /// /// @param[in] wait [opt] Wait until timer is stopped, false is default and /// call unblocked /// @return True if timer was active and was stopped, false if already was /// stopped. /// bool Stop(bool wait = false) { if (!IsRunning()) return false; m_threadStop = true; m_eventTimeout.notify_all(); StopThread(wait); return true; } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Restart timer complete by stop and restart his thread again. /// /// @note Restart only possible as long the timer was running and not done his /// work. /// /// @return True if start was successfully done, on error, or if was already /// finished returned as false /// bool Restart() { using namespace std::chrono; if (!IsRunning()) return false; Stop(true); return Start(duration_cast(m_timeout).count(), m_interval); } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Restart the timer with new timeout without touch of his thread. /// /// @param[in] timeout Time as milliseconds to wait for next call /// void RestartAsync(uint64_t timeout) { using namespace std::chrono; m_timeout = milliseconds(timeout); const auto now = system_clock::now(); m_endTime = now.time_since_epoch() + m_timeout; m_eventTimeout.notify_all(); } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Check timer is still active to wait for next call. /// /// @return True if active, false if all his work done and no more running /// bool IsRunning() const { return CThread::IsRunning(); } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Get elapsed time as floating point of timer as seconds. /// /// @return Elapsed time /// float GetElapsedSeconds() const { return GetElapsedMilliseconds() / 1000.0f; } //---------------------------------------------------------------------------- //============================================================================ /// @ingroup cpp_kodi_tools_CTimer /// @brief Get elapsed time as floating point of timer as milliseconds. /// /// @return Elapsed time /// float GetElapsedMilliseconds() const { using namespace std::chrono; if (!IsRunning()) return 0.0f; const auto now = system_clock::now(); return static_cast(duration_cast(now.time_since_epoch() - (m_endTime - m_timeout)).count()); } //---------------------------------------------------------------------------- //============================================================================ /// @defgroup cpp_kodi_tools_CTimer_CB class ITimerCallback /// @ingroup cpp_kodi_tools_CTimer /// @brief **Callback class of timer**\n /// To give on contructor by @ref CTimer(kodi::tools::CTimer::ITimerCallback* callback) /// class ITimerCallback { public: //========================================================================== /// @ingroup cpp_kodi_tools_CTimer_CB /// @brief Class destructor. /// virtual ~ITimerCallback() = default; //-------------------------------------------------------------------------- //========================================================================== /// @ingroup cpp_kodi_tools_CTimer_CB /// @brief Callback function to implement if constuctor @ref CTimer(kodi::tools::CTimer::ITimerCallback* callback) /// is used and this as parent on related class /// /// ---------------------------------------------------------------------------- /// /// **Example:** /// ~~~~~~~~~~~~~{.cpp} /// #include /// /// class CExample : public kodi::tools::CTimer, /// private kodi::tools::CTimer::ITimerCallback /// { /// public: /// CExample() : kodi::tools::CTimer(this) /// { /// } /// /// void OnTimeout() override /// { /// // Some work /// } /// }; /// /// ~~~~~~~~~~~~~ /// virtual void OnTimeout() = 0; //-------------------------------------------------------------------------- }; //---------------------------------------------------------------------------- protected: void Process() override { using namespace std::chrono; while (!m_threadStop) { auto currentTime = system_clock::now(); m_endTime = currentTime.time_since_epoch() + m_timeout; // wait the necessary time std::mutex mutex; std::unique_lock lock(mutex); const auto waitTime = duration_cast(m_endTime - currentTime.time_since_epoch()); if (m_eventTimeout.wait_for(lock, waitTime) == std::cv_status::timeout) { currentTime = system_clock::now(); if (m_endTime.count() <= currentTime.time_since_epoch().count()) { // execute OnTimeout() callback m_callback(); // continue if this is an interval timer, or if it was restarted during callback if (!m_interval && m_endTime.count() <= currentTime.time_since_epoch().count()) break; } } } } private: bool m_interval = false; std::function m_callback; std::chrono::system_clock::duration m_timeout; std::chrono::system_clock::duration m_endTime; std::condition_variable_any m_eventTimeout; }; ///@} //------------------------------------------------------------------------------ } /* namespace tools */ } /* namespace kodi */ #endif /* __cplusplus */