Skip to content

Commit

Permalink
Work on thread modes (#780)
Browse files Browse the repository at this point in the history
  • Loading branch information
dirkwhoffmann committed Jan 27, 2024
1 parent c129b22 commit cc34223
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 166 deletions.
2 changes: 1 addition & 1 deletion Emulator/Base/Defaults.cpp
Expand Up @@ -21,7 +21,7 @@ Defaults::Defaults()
{
setFallback(OPT_WARP_BOOT, 0);
setFallback(OPT_WARP_MODE, WARP_NEVER);
setFallback(OPT_SYNC_MODE, THREAD_ADAPTIVE);
setFallback(OPT_SYNC_MODE, SYNC_ADAPTIVE);
setFallback(OPT_TIME_SLICES, 1);
setFallback(OPT_AUTO_FPS, true);
setFallback(OPT_PROPOSED_FPS, 60);
Expand Down
143 changes: 68 additions & 75 deletions Emulator/Base/Thread.cpp
Expand Up @@ -28,12 +28,56 @@ Thread::~Thread()
join();
}

template <ThreadMode M> void
util::Time
Thread::frameDuration() const
{
return util::Time(i64(1000000000.0 / refreshRate()));
}

util::Time
Thread::sliceDuration() const
{
return util::Time(i64(1000000000.0 / refreshRate() / slicesPerFrame()));
}

isize
Thread::missingSlices() const
{
if (getSyncMode() == SYNC_PULSED) {

return slicesPerFrame();
}
if (getSyncMode() == SYNC_ADAPTIVE) {

// Compute the elapsed time
auto elapsed = util::Time::now() - baseTime;

// Compute which slice should be reached by now
auto target = slicesPerFrame() * elapsed.asNanoseconds() * i64(refreshRate()) / 1000000000;

// Compute the number of missing slices
return isize(target - sliceCounter);
}

return 0;
}

void
Thread::resync()
{
targetTime = util::Time::now();
baseTime = util::Time::now();
deltaTime = 0;
sliceCounter = 0;
missing = 0;
}

template <SyncMode M> void
Thread::execute()
{
if (missing > 0 || warp) {

trace(TIM_DEBUG, "execute<%s>: %lld us\n", ThreadModeEnum::key(M),
trace(TIM_DEBUG, "execute<%s>: %lld us\n", SyncModeEnum::key(M),
execClock.restart().asMicroseconds());

loadClock.go();
Expand All @@ -47,7 +91,7 @@ Thread::execute()
}

template <> void
Thread::sleep<THREAD_PERIODIC>()
Thread::sleep<SYNC_PERIODIC>()
{
auto now = util::Time::now();

Expand All @@ -71,7 +115,7 @@ Thread::sleep<THREAD_PERIODIC>()
}

template <> void
Thread::sleep<THREAD_PULSED>()
Thread::sleep<SYNC_PULSED>()
{
// Only proceed if we're not running in warp mode
if (warp) return;
Expand Down Expand Up @@ -107,7 +151,7 @@ Thread::sleep<THREAD_PULSED>()
if (missing > 0) {
warn("Emulation is way too slow: %ld time slices behind\n", missing);
} else {
warn("Emulation is way too fast: %ld time slices ahead\n", missing);
warn("Emulation is way too fast: %ld time slices ahead\n", -missing);
}

resync();
Expand All @@ -117,20 +161,9 @@ Thread::sleep<THREAD_PULSED>()
}

template <> void
Thread::sleep<THREAD_ADAPTIVE>()
Thread::sleep<SYNC_ADAPTIVE>()
{
sleep<THREAD_PULSED>();
}

void
Thread::resync()
{
targetTime = util::Time::now();
baseTime = util::Time::now();
deltaTime = 0;
sliceCounter = 0;
frameCounter = 0;
missing = 0;
sleep<SYNC_PULSED>();
}

void
Expand All @@ -144,21 +177,21 @@ Thread::main()

if (isRunning()) {

switch (getThreadMode()) {
switch (getSyncMode()) {

case THREAD_PERIODIC: execute<THREAD_PERIODIC>(); break;
case THREAD_PULSED: execute<THREAD_PULSED>(); break;
case THREAD_ADAPTIVE: execute<THREAD_ADAPTIVE>(); break;
case SYNC_PERIODIC: execute<SYNC_PERIODIC>(); break;
case SYNC_PULSED: execute<SYNC_PULSED>(); break;
case SYNC_ADAPTIVE: execute<SYNC_ADAPTIVE>(); break;
}
}

if (!warp || !isRunning()) {

switch (getThreadMode()) {
switch (getSyncMode()) {

case THREAD_PERIODIC: sleep<THREAD_PERIODIC>(); break;
case THREAD_PULSED: sleep<THREAD_PULSED>(); break;
case THREAD_ADAPTIVE: sleep<THREAD_ADAPTIVE>(); break;
case SYNC_PERIODIC: sleep<SYNC_PERIODIC>(); break;
case SYNC_PULSED: sleep<SYNC_PULSED>(); break;
case SYNC_ADAPTIVE: sleep<SYNC_ADAPTIVE>(); break;
}
}

Expand All @@ -172,61 +205,21 @@ Thread::main()
if (state == EXEC_HALTED) return;
}

// Have we completed an entire frame?
if (sliceCounter % slicesPerFrame() == 0) {

frameCounter++;
// Compute the CPU load once in a while
if (sliceCounter % (32 * slicesPerFrame()) == 0) {

// Compute the CPU load once in a while
if (frameCounter % 32 == 0) {
auto used = loadClock.getElapsedTime().asSeconds();
auto total = nonstopClock.getElapsedTime().asSeconds();

auto used = loadClock.getElapsedTime().asSeconds();
auto total = nonstopClock.getElapsedTime().asSeconds();
cpuLoad = used / total;

cpuLoad = used / total;

loadClock.restart();
loadClock.stop();
nonstopClock.restart();
}
loadClock.restart();
loadClock.stop();
nonstopClock.restart();
}
}
}

util::Time
Thread::frameDuration() const
{
return util::Time(i64(1000000000.0 / refreshRate()));
}

util::Time
Thread::sliceDuration() const
{
return util::Time(i64(1000000000.0 / refreshRate() / slicesPerFrame()));
}

isize
Thread::missingSlices() const
{
if (getThreadMode() == THREAD_PULSED) {

return slicesPerFrame();
}
if (getThreadMode() == THREAD_ADAPTIVE) {

// Compute the elapsed time
auto elapsed = util::Time::now() - baseTime;

// Compute which slice should be reached by now
auto target = slicesPerFrame() * elapsed.asNanoseconds() * i64(refreshRate()) / 1000000000;

// Compute the number of missing slices
return isize(target - sliceCounter);
}

return 0;
}

void
Thread::switchState(ExecutionState newState)
{
Expand Down Expand Up @@ -435,7 +428,7 @@ Thread::changeStateTo(ExecutionState requestedState)
void
Thread::wakeUp()
{
if (getThreadMode() != THREAD_PERIODIC) {
if (getSyncMode() != SYNC_PERIODIC) {

trace(TIM_DEBUG, "wakeup: %lld us\n", wakeupClock.restart().asMicroseconds());
util::Wakeable::wakeUp();
Expand Down
62 changes: 31 additions & 31 deletions Emulator/Base/Thread.h
Expand Up @@ -164,29 +164,26 @@ class Thread : public CoreComponent, util::Wakeable {
// Counters
isize suspendCounter = 0;
isize sliceCounter = 0;
isize frameCounter = 0;

// Debug clocks
util::Clock execClock;
util::Clock wakeupClock;

// Reference time stamp for periodic sync mode
util::Time targetTime;

// Reference time stamp for adaptive sync mode
// Time stamps for calculating wakeup times
util::Time baseTime;

// Experimental
util::Time deltaTime;
util::Time targetTime;

// Number of time slices that need to be computed
isize missing = 0;

// Clocks for measuring the CPU load
util::Clock nonstopClock;
util::Clock loadClock;

// The current CPU load (%)
// The current CPU load in percent
double cpuLoad = 0.0;

// Debug clocks
util::Clock execClock;
util::Clock wakeupClock;


//
// Initializing
Expand All @@ -206,35 +203,38 @@ class Thread : public CoreComponent, util::Wakeable {

private:

template <ThreadMode M> void execute();
template <ThreadMode M> void sleep();
// The code to be executed in each iteration (implemented by the subclass)
virtual void execute() = 0;

// Called inside execute when an out-of-sync condition has been detected
void resync();
// Target frame rate of this thread (provided by the subclass)
virtual double refreshRate() const = 0;

// The main entry point (called when the thread is created)
void main();
// Number of thread syncs per frame (provided by the subclass)
virtual isize slicesPerFrame() const = 0;

// Returns the time to elapse between two frames
// Time span between two wakeup calls (provided by the subclass)
virtual util::Time wakeupPeriod() const = 0;

// Computes the time span between two frames
util::Time frameDuration() const;

// Returns the time to elapse between two slices
// Computes the time span between two time slices
util::Time sliceDuration() const;

// Returns the number of pending emulation chunks
// Computes the number of overdue time slices
isize missingSlices() const;

// The code to be executed in each iteration (implemented by the subclass)
virtual void execute() = 0;
// Rectifies an out-of-sync condition by resetting all counters and clocks
void resync();

// Target frame rate of this thread (provided by the subclass)
virtual double refreshRate() const = 0;
// Executes a single time slice (if one is pending)
template <SyncMode M> void execute();

// Number of thread syncs per frame (provided by the subclass)
virtual isize slicesPerFrame() const = 0;
// Suspends the thread until the next time slice is due
template <SyncMode M> void sleep();

// Time span between two wakeup calls (provided by the subclass)
virtual util::Time wakeupPeriod() const = 0;
// The main entry point (called when the thread is created)
void main();

public:

Expand Down Expand Up @@ -299,9 +299,9 @@ class Thread : public CoreComponent, util::Wakeable {
public:

// Provides the current sync mode
virtual ThreadMode getThreadMode() const = 0;
virtual SyncMode getSyncMode() const = 0;

// Awakes the thread if it runs in pulse mode
// Awakes the thread if it runs in pulse mode or adaptive mode
void wakeUp();

private:
Expand Down
24 changes: 12 additions & 12 deletions Emulator/Base/ThreadTypes.h
Expand Up @@ -52,29 +52,29 @@ struct ExecutionStateEnum : util::Reflection<ExecutionStateEnum, ExecutionState>
};
#endif

enum_long(THREAD_MODE)
enum_long(SYNC_MODE)
{
THREAD_PERIODIC,
THREAD_PULSED,
THREAD_ADAPTIVE
SYNC_PERIODIC,
SYNC_PULSED,
SYNC_ADAPTIVE
};
typedef THREAD_MODE ThreadMode;
typedef SYNC_MODE SyncMode;

#ifdef __cplusplus
struct ThreadModeEnum : util::Reflection<ThreadModeEnum, ThreadMode>
struct SyncModeEnum : util::Reflection<SyncModeEnum, SyncMode>
{
static constexpr long minVal = 0;
static constexpr long maxVal = THREAD_ADAPTIVE;
static constexpr long maxVal = SYNC_ADAPTIVE;
static bool isValid(auto val) { return val >= minVal && val <= maxVal; }

static const char *prefix() { return "THREAD"; }
static const char *key(ThreadMode value)
static const char *prefix() { return "SYNC"; }
static const char *key(SyncMode value)
{
switch (value) {

case THREAD_PERIODIC: return "PERIODIC";
case THREAD_PULSED: return "PULSED";
case THREAD_ADAPTIVE: return "ADAPTIVE";
case SYNC_PERIODIC: return "PERIODIC";
case SYNC_PULSED: return "PULSED";
case SYNC_ADAPTIVE: return "ADAPTIVE";
}
return "???";
}
Expand Down

0 comments on commit cc34223

Please sign in to comment.