mirror of https://github.com/stella-emu/stella.git
261 lines
6.7 KiB
C++
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)
|
|
{
|
|
}
|