mirror of https://github.com/stella-emu/stella.git
219 lines
6.8 KiB
C++
219 lines
6.8 KiB
C++
//============================================================================
|
|
//
|
|
// SSSS tt lll lll
|
|
// SS SS tt ll ll
|
|
// SS tttttt eeee ll ll aaaa
|
|
// SSSS tt ee ee ll ll aa
|
|
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
|
|
// SS SS tt ee ll ll aa aa
|
|
// SSSS ttt eeeee llll llll aaaaa
|
|
//
|
|
// Copyright (c) 1995-2024 by Bradford W. Mott, Stephen Anthony
|
|
// and the Stella Team
|
|
//
|
|
// See the file "License.txt" for information on usage and redistribution of
|
|
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|
//============================================================================
|
|
|
|
#ifndef TIMER_MANAGER_HXX
|
|
#define TIMER_MANAGER_HXX
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <chrono>
|
|
#include <unordered_map>
|
|
#include <set>
|
|
#include <cstdint>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
|
|
#include "bspf.hxx"
|
|
|
|
/**
|
|
This class provides a portable periodic/one-shot timer infrastructure
|
|
using worker threads and generic C++11 code.
|
|
|
|
@author Doug Gale (doug65536)
|
|
From "Code Review"
|
|
https://codereview.stackexchange.com/questions/127552/portable-periodic-one-shot-timer-thread-follow-up
|
|
|
|
Modifications and cleanup for Stella by Stephen Anthony
|
|
*/
|
|
class TimerManager
|
|
{
|
|
public:
|
|
// Each Timer is assigned a unique ID of type TimerId
|
|
using TimerId = uInt64;
|
|
|
|
// Function object we actually use
|
|
using TFunction = std::function<void()>;
|
|
|
|
// Values that are a large-range millisecond count
|
|
using millisec = uInt64;
|
|
|
|
// Constructor does not start worker until there is a Timer.
|
|
explicit TimerManager();
|
|
|
|
// Destructor is thread safe, even if a timer callback is running.
|
|
// All callbacks are guaranteed to have returned before this
|
|
// destructor returns.
|
|
~TimerManager();
|
|
|
|
/**
|
|
Create a new timer using milliseconds, and add it to the internal queue.
|
|
|
|
@param msDelay Callback starts firing this many milliseconds from now
|
|
@param msPeriod If non-zero, callback is fired again after this period
|
|
@param func The callback to run at the specified interval
|
|
|
|
@return Id used to identify the timer for later use
|
|
*/
|
|
TimerId addTimer(millisec msDelay, millisec msPeriod, const TFunction& func);
|
|
|
|
/**
|
|
Convenience function; setInterval API like browser javascript.
|
|
|
|
Call function every 'period' ms, starting 'period' ms from now.
|
|
*/
|
|
TimerId setInterval(const TFunction& func, millisec period) {
|
|
return addTimer(period, period, func);
|
|
}
|
|
|
|
/**
|
|
Convenience function; setTimeout API like browser javascript.
|
|
|
|
Call function once 'timeout' ms from now.
|
|
*/
|
|
TimerId setTimeout(const TFunction& func, millisec timeout) {
|
|
return addTimer(timeout, 0, func);
|
|
}
|
|
|
|
/**
|
|
Destroy the specified timer.
|
|
|
|
Synchronizes with the worker thread if the callback for this timer
|
|
is running, which guarantees that the handler for that callback is
|
|
not running before clear() returns.
|
|
|
|
You are not required to clear any timers. You can forget their
|
|
TimerId if you do not need to cancel them.
|
|
|
|
The only time you need this is when you want to stop a timer that
|
|
has a repetition period, or you want to cancel a timeout that has
|
|
not fired yet.
|
|
*/
|
|
bool clear(TimerId id);
|
|
|
|
/**
|
|
Destroy all timers, but preserve id uniqueness.
|
|
This carefully makes sure every timer is not executing its callback
|
|
before destructing it.
|
|
*/
|
|
void clear();
|
|
|
|
// Peek at current state
|
|
std::size_t size() const noexcept;
|
|
bool empty() const noexcept;
|
|
|
|
// Returns lazily initialized singleton
|
|
static TimerManager& global();
|
|
|
|
/**
|
|
This method returns number of ticks in microseconds since some
|
|
pre-defined time in the past. *NOTE*: it is necessary that this
|
|
pre-defined time exists between runs of the application, and must
|
|
be (relatively) unique. For example, the time since the system
|
|
started running is not a good choice, since it can be duplicated.
|
|
The current implementation uses time since the UNIX epoch.
|
|
|
|
@return Current time in microseconds.
|
|
*/
|
|
static uInt64 getTicks() {
|
|
using namespace std::chrono;
|
|
return duration_cast<duration<uInt64, std::ratio<1, 1000000> > >
|
|
(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
private:
|
|
using Lock = std::mutex;
|
|
using ScopedLock = std::unique_lock<Lock>;
|
|
using ConditionVar = std::condition_variable;
|
|
|
|
using Clock = std::chrono::steady_clock;
|
|
using Timestamp = std::chrono::time_point<Clock>;
|
|
using Duration = std::chrono::milliseconds;
|
|
|
|
struct Timer
|
|
{
|
|
explicit Timer(TimerId tid = 0) : id{tid} { }
|
|
Timer(Timer&& r) noexcept;
|
|
|
|
Timer(TimerId id, Timestamp next, Duration period, const TFunction& func) noexcept;
|
|
|
|
// Never called
|
|
Timer() = default;
|
|
~Timer() = default;
|
|
Timer(Timer const& r) = delete;
|
|
Timer& operator=(Timer const& r) = delete;
|
|
Timer& operator=(Timer&& r) = delete;
|
|
|
|
TimerId id{0};
|
|
Timestamp next;
|
|
Duration period{0};
|
|
TFunction handler;
|
|
|
|
// You must be holding the 'sync' lock to assign waitCond
|
|
std::unique_ptr<ConditionVar> waitCond;
|
|
|
|
bool running{false};
|
|
};
|
|
|
|
// Comparison functor to sort the timer "queue" by Timer::next
|
|
struct NextActiveComparator
|
|
{
|
|
bool operator()(Timer const& a, Timer const& b) const noexcept
|
|
{
|
|
return a.next < b.next;
|
|
}
|
|
};
|
|
|
|
// Queue is a set of references to Timer objects, sorted by next
|
|
using QueueValue = std::reference_wrapper<Timer>;
|
|
using Queue = std::multiset<QueueValue, NextActiveComparator>;
|
|
using TimerMap = std::unordered_map<TimerId, Timer>;
|
|
|
|
void timerThreadWorker();
|
|
bool destroy_impl(ScopedLock& lock, TimerMap::iterator i, bool notify);
|
|
|
|
// Inexhaustible source of unique IDs
|
|
TimerId nextId;
|
|
|
|
// The Timer objects are physically stored in this map
|
|
TimerMap active;
|
|
|
|
// The ordering queue holds references to items in 'active'
|
|
Queue queue;
|
|
|
|
// One worker thread for an unlimited number of timers is acceptable
|
|
// Lazily started when first timer is started
|
|
// TODO: Implement auto-stopping the timer thread when it is idle for
|
|
// a configurable period.
|
|
mutable Lock sync;
|
|
ConditionVar wakeUp;
|
|
std::thread worker;
|
|
bool done{false};
|
|
|
|
// Valid IDs are guaranteed not to be this value
|
|
static TimerId constexpr no_timer = TimerId(0);
|
|
|
|
private:
|
|
// Following constructors and assignment operators not supported
|
|
TimerManager(const TimerManager&) = delete;
|
|
TimerManager(TimerManager&&) = delete;
|
|
TimerManager& operator=(const TimerManager&) = delete;
|
|
TimerManager& operator=(TimerManager&&) = delete;
|
|
};
|
|
|
|
#endif // TIMERTHREAD_H
|