summaryrefslogtreecommitdiffstats
path: root/xbmc/addons/kodi-dev-kit/include/kodi/tools/Timer.h
blob: 0e0ced7769ed3afb5b760064a4c4085d9002f602 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
/*
 *  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 <functional>

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 <kodi/tools/Timer.h>
///
/// 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<void()> 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<milliseconds>(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<float>(duration_cast<milliseconds>(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 <kodi/tools/Timer.h>
    ///
    /// 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<std::mutex> lock(mutex);
      const auto waitTime = duration_cast<milliseconds>(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<void()> 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 */