stella/src/common/TimerManager.cxx

261 lines
6.7 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-2019 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.
//============================================================================
#include <cassert>
#include "TimerManager.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager::TimerManager()
: nextId(no_timer + 1),
queue(),
done(false)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager::~TimerManager()
{
ScopedLock lock(sync);
// The worker might not be running
if (worker.joinable())
{
done = true;
lock.unlock();
wakeUp.notify_all();
// If a timer handler is running, this
// will make sure it has returned before
// allowing any deallocations to happen
worker.join();
// Note that any timers still in the queue
// will be destructed properly but they
// will not be invoked
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager::TimerId TimerManager::addTimer(
millisec msDelay,
millisec msPeriod,
const TFunction& func)
{
ScopedLock lock(sync);
// Lazily start thread when first timer is requested
if (!worker.joinable())
worker = std::thread(&TimerManager::timerThreadWorker, this);
// Assign an ID and insert it into function storage
auto id = nextId++;
auto iter = active.emplace(id, Timer(id, Clock::now() + Duration(msDelay),
Duration(msPeriod), std::move(func)));
// Insert a reference to the Timer into ordering queue
Queue::iterator place = queue.emplace(iter.first->second);
// We need to notify the timer thread only if we inserted
// this timer into the front of the timer queue
bool needNotify = (place == queue.begin());
lock.unlock();
if (needNotify)
wakeUp.notify_all();
return id;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool TimerManager::clear(TimerId id)
{
ScopedLock lock(sync);
auto i = active.find(id);
return destroy_impl(lock, i, true);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TimerManager::clear()
{
ScopedLock lock(sync);
while (!active.empty())
destroy_impl(lock, active.begin(), queue.size() == 1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
std::size_t TimerManager::size() const noexcept
{
ScopedLock lock(sync);
return active.size();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool TimerManager::empty() const noexcept
{
ScopedLock lock(sync);
return active.empty();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager& TimerManager::global()
{
static TimerManager singleton;
return singleton;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TimerManager::timerThreadWorker()
{
ScopedLock lock(sync);
while (!done)
{
if (queue.empty())
{
// Wait for done or work
wakeUp.wait(lock, [this] { return done || !queue.empty(); });
continue;
}
auto queueHead = queue.begin();
Timer& timer = *queueHead;
auto now = Clock::now();
if (now >= timer.next)
{
queue.erase(queueHead);
// Mark it as running to handle racing destroy
timer.running = true;
// Call the handler outside the lock
lock.unlock();
timer.handler();
lock.lock();
if (timer.running)
{
timer.running = false;
// If it is periodic, schedule a new one
if (timer.period.count() > 0)
{
timer.next = timer.next + timer.period;
queue.emplace(timer);
}
else
{
// Not rescheduling, destruct it
active.erase(timer.id);
}
}
else
{
// timer.running changed!
//
// Running was set to false, destroy was called
// for this Timer while the callback was in progress
// (this thread was not holding the lock during the callback)
// The thread trying to destroy this timer is waiting on
// a condition variable, so notify it
timer.waitCond->notify_all();
// The clearTimer call expects us to remove the instance
// when it detects that it is racing with its callback
active.erase(timer.id);
}
}
else
{
// Wait until the timer is ready or a timer creation notifies
Timestamp next = timer.next;
wakeUp.wait_until(lock, next);
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// NOTE: if notify is true, returns with lock unlocked
bool TimerManager::destroy_impl(ScopedLock& lock, TimerMap::iterator i,
bool notify)
{
assert(lock.owns_lock());
if (i == active.end())
return false;
Timer& timer = i->second;
if (timer.running)
{
// A callback is in progress for this Timer,
// so flag it for deletion in the worker
timer.running = false;
// Assign a condition variable to this timer
timer.waitCond.reset(new ConditionVar);
// Block until the callback is finished
if (std::this_thread::get_id() != worker.get_id())
timer.waitCond->wait(lock);
}
else
{
queue.erase(timer);
active.erase(i);
if (notify)
{
lock.unlock();
wakeUp.notify_all();
}
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// TimerManager::Timer implementation
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager::Timer::Timer(TimerId tid)
: id(tid),
running(false)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager::Timer::Timer(Timer&& r) noexcept
: id(std::move(r.id)),
next(std::move(r.next)),
period(std::move(r.period)),
handler(std::move(r.handler)),
running(std::move(r.running))
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimerManager::Timer::Timer(TimerId tid, Timestamp tnext, Duration tperiod,
const TFunction& func) noexcept
: id(tid),
next(tnext),
period(tperiod),
handler(std::move(func)),
running(false)
{
}