Created
November 30, 2016 12:56
-
-
Save vtellier/6664547a3f9372de3ba6d2f3f264c922 to your computer and use it in GitHub Desktop.
Deterministic, re-settable, stoppable C++11 timer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <exception> | |
#include <system_error> | |
#include <thread> | |
#include <condition_variable> | |
#include <mutex> | |
#include <functional> | |
#include <chrono> | |
#include <memory> | |
using namespace std; | |
using namespace chrono; | |
class Timer | |
{ | |
public: | |
typedef shared_ptr<Timer> Ptr; | |
typedef function<void(Timer*)> Callback; | |
const static long long MinPeriod = 5; | |
const milliseconds period; | |
static Ptr Create(Callback callback, long long period, bool autoStop = false) | |
{ | |
return Ptr(new Timer(callback, period, autoStop)); | |
} | |
~Timer() | |
{ | |
Stop(); | |
if(_thread.joinable()) | |
{ | |
try | |
{ | |
_thread.join(); | |
} | |
catch(const std::system_error& e) | |
{ | |
// TODO use a standard logger. | |
cout << "Timer:: error: " << e.what() << endl; | |
throw e; | |
} | |
} | |
} | |
void Start(bool reset = false) | |
{ | |
// To reset, we must first stop it. | |
if(reset) | |
Stop(); | |
// When the timer has been stoppped, we must be able to restart it later. | |
if(_stopCommand && _thread.joinable()) | |
{ | |
_thread.join(); | |
} | |
// The actual start operation | |
{ | |
lock_guard<mutex> guard(_startStopMutex); | |
if(!_thread.joinable()) | |
{ | |
unique_lock<mutex> lk(_stopMutex); | |
_stopCommand = false; | |
lk.unlock(); | |
_stopCondition.notify_all(); | |
// We set the time of the first iteration | |
_nextCall = high_resolution_clock::now(); | |
// And we create the thread. | |
_thread = thread(&Timer::loop, this); | |
} | |
} | |
} | |
void Stop() | |
{ | |
lock_guard<mutex> guard(_startStopMutex); | |
if(_thread.joinable()) | |
{ | |
unique_lock<mutex> lk(_stopMutex); | |
_stopCommand = true; | |
lk.unlock(); | |
_stopCondition.notify_all(); | |
} | |
} | |
private: | |
time_point<high_resolution_clock> _nextCall; | |
Callback _callback; | |
thread _thread; | |
mutex _startStopMutex; | |
condition_variable _stopCondition; | |
mutex _stopMutex; | |
bool _stopCommand; | |
bool _autoStop; | |
Timer(Callback callback, long long period, bool autoStop) : | |
period(milliseconds{ period }), | |
_nextCall(high_resolution_clock::now()), | |
_callback(callback), | |
_stopCondition(), | |
_stopMutex(), | |
_stopCommand(false), | |
_autoStop(autoStop), | |
_thread() | |
{ | |
if(period < MinPeriod) | |
throw std::exception("Timer does not support a period lower or equal than 5 milliseconds."); | |
} | |
void loop() | |
{ | |
bool stop = false; | |
while(!stop) | |
{ | |
// By doing so we ensure the call to be deterministic. | |
do | |
{ | |
_nextCall += period; | |
//cout << "loop() +period" << endl; | |
} | |
while(_nextCall < high_resolution_clock::now()); | |
// We scope here because the user's call might call the stop method, and so create a deadlock. | |
{ | |
// We wait for a stop command with a timeout of 'period' | |
unique_lock<mutex> lk(_stopMutex); | |
if(_stopCondition.wait_until(lk, _nextCall, [&]{return _stopCommand;})) | |
{ | |
// mutex has been unlocked: this is the stop command | |
stop = true; | |
} | |
} | |
// If call to Stop in callback lead to deadlcok then uncomment this code. | |
if(!stop) | |
{ | |
// Call user's code | |
_callback(this); | |
} | |
// We call the Stop method because it is a normal exit, and the Stop function ensures | |
// that we can restart the timer using the Start method. | |
if(_autoStop) | |
Stop(); | |
} | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment