mirror of https://github.com/stella-emu/stella.git
253 lines
6.7 KiB
C++
253 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-2025 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}
|
|
{
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
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++;
|
|
const auto iter = active.emplace(id, Timer(id, Clock::now() + Duration(msDelay),
|
|
Duration(msPeriod), func));
|
|
|
|
// Insert a reference to the Timer into ordering queue
|
|
const auto 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
|
|
const bool needNotify = (place == queue.begin());
|
|
|
|
lock.unlock();
|
|
|
|
if (needNotify)
|
|
wakeUp.notify_all();
|
|
|
|
return id;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool TimerManager::clear(TimerId id)
|
|
{
|
|
ScopedLock lock(sync);
|
|
const 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
|
|
{
|
|
const ScopedLock lock(sync);
|
|
return active.size();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool TimerManager::empty() const noexcept
|
|
{
|
|
const ScopedLock lock(sync);
|
|
return active.empty(); // NOLINT: bugprone-standalone-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;
|
|
}
|
|
|
|
const auto queueHead = queue.begin();
|
|
Timer& timer = *queueHead;
|
|
const 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. Note that wait until accesses
|
|
// time_point by reference, so we make a copy in case the current time is deleted while the
|
|
// thread sleeps.
|
|
const auto 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 = std::make_unique<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(Timer&& r) noexcept
|
|
: id{r.id},
|
|
next{r.next},
|
|
period{r.period},
|
|
handler{std::move(r.handler)},
|
|
running{r.running}
|
|
{
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
TimerManager::Timer::Timer(TimerId tid, Timestamp tnext, Duration tperiod,
|
|
const TFunction& func) noexcept
|
|
: id{tid},
|
|
next{tnext},
|
|
period{tperiod},
|
|
handler{func}
|
|
{
|
|
}
|