Skip to content

Commit

Permalink
Split timer into monotonic/realtime (#474)
Browse files Browse the repository at this point in the history
There are different clock times that can be queried on Linux, which are
well described in clock_gettime() manpage. There are two main
categories:

* a monotonic clock that starts at an arbitrary value and only
  increases. It cannot be modified by the user
* a realtime clock whose value corresponds to the Unix timestamp, and
  which can be modified by the user

In one speedrun we modify the system clock to save time, so in
preparation for its support, we need to know which function returns
which category of clock time. We make our monotonic clock starts at 0,
so that it is easier to compute movie length and such. Realtime is the
one that is set by the user at startup. Both are shown on GUI main
window, and realtime also has the corresponding date and time.
  • Loading branch information
clementgallet committed Apr 12, 2022
1 parent a265dd4 commit c769194
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 122 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## [Unreleased]
### Added

* Split deterministic timer into monotonic/realtime (#474)

### Changed
### Fixed

Expand Down
66 changes: 55 additions & 11 deletions src/library/DeterministicTimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ const char* const DeterministicTimer::gettimes_names[] =
"time()",
"gettimeofday()",
"clock()",
"clock_gettime()",
"clock_gettime(CLOCK_REALTIME)",
"clock_gettime(CLOCK_MONOTONIC)",
"SDL_GetTicks()",
"SDL_GetPerformanceCounter()",
"GetTickCount()",
Expand All @@ -54,7 +55,7 @@ const char* const DeterministicTimer::gettimes_names[] =

struct timespec DeterministicTimer::getTicks()
{
return getTicks(SharedConfig::TIMETYPE_UNTRACKED);
return getTicks(SharedConfig::TIMETYPE_UNTRACKED_MONOTONIC);
}

struct timespec DeterministicTimer::getTicks(SharedConfig::TimeCallType type)
Expand All @@ -68,17 +69,26 @@ struct timespec DeterministicTimer::getTicks(SharedConfig::TimeCallType type)
/* If we are in the native global state, just return the real time */
if (GlobalState::isNative()) {
struct timespec realtime;
clock_gettime(CLOCK_REALTIME, &realtime);
if (isTimeCallMonotonic(type))
clock_gettime(CLOCK_MONOTONIC, &realtime);
else
clock_gettime(CLOCK_REALTIME, &realtime);
return realtime;
}

if (shared_config.debug_state & SharedConfig::DEBUG_UNCONTROLLED_TIME) {
return nonDetTimer.getTicks(); // disable deterministic time
}

if ((type == SharedConfig::TIMETYPE_UNTRACKED) || GlobalState::isOwnCode()) {
TimeHolder fakeTicks = ticks + fakeExtraTicks;
return fakeTicks;
if ((type == SharedConfig::TIMETYPE_UNTRACKED_MONOTONIC) || GlobalState::isOwnCode()) {
TimeHolder returnTicks = ticks + fakeExtraTicks;
return returnTicks;
}

if ((type == SharedConfig::TIMETYPE_UNTRACKED_REALTIME)) {
TimeHolder returnTicks = ticks + fakeExtraTicks;
returnTicks += realtime_delta;
return returnTicks;
}

DEBUGLOGCALL(LCF_TIMEGET | LCF_FREQUENT);
Expand Down Expand Up @@ -140,8 +150,10 @@ struct timespec DeterministicTimer::getTicks(SharedConfig::TimeCallType type)
addDelay(delay);
}

TimeHolder fakeTicks = ticks + fakeExtraTicks;
return fakeTicks;
TimeHolder returnTicks = ticks + fakeExtraTicks;
if (!isTimeCallMonotonic(type))
returnTicks += realtime_delta;
return returnTicks;
}

void DeterministicTimer::addDelay(struct timespec delayTicks)
Expand Down Expand Up @@ -342,10 +354,12 @@ void DeterministicTimer::fakeAdvanceTimerFrame() {
}
}

void DeterministicTimer::initialize(void)
void DeterministicTimer::initialize(uint64_t initial_sec, uint64_t initial_nsec)
{
ticks.tv_sec = shared_config.initial_time_sec;
ticks.tv_nsec = shared_config.initial_time_nsec;
ticks = {initial_sec, initial_nsec};

realtime_delta = {shared_config.initial_time_sec, shared_config.initial_time_nsec};
realtime_delta -= ticks;

if (shared_config.framerate_num > 0) {
baseTimeIncrement.tv_sec = shared_config.framerate_den / shared_config.framerate_num;
Expand All @@ -372,6 +386,36 @@ bool DeterministicTimer::isInsideFrameBoundary()
return insideFrameBoundary;
}

void DeterministicTimer::setRealTime(struct timespec new_realtime)
{
TimeHolder th_real = new_realtime;
realtime_delta = th_real - ticks;
}

bool DeterministicTimer::isTimeCallMonotonic(SharedConfig::TimeCallType type)
{
switch (type) {
case SharedConfig::TIMETYPE_UNTRACKED_REALTIME:
case SharedConfig::TIMETYPE_TIME:
case SharedConfig::TIMETYPE_GETTIMEOFDAY:
case SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME:
return false;
case SharedConfig::TIMETYPE_UNTRACKED_MONOTONIC:
case SharedConfig::TIMETYPE_CLOCK:
case SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC:
case SharedConfig::TIMETYPE_SDLGETTICKS:
case SharedConfig::TIMETYPE_SDLGETPERFORMANCECOUNTER:
return true;
case SharedConfig::TIMETYPE_GETTICKCOUNT:
case SharedConfig::TIMETYPE_GETTICKCOUNT64:
case SharedConfig::TIMETYPE_QUERYPERFORMANCECOUNTER:
return true; // TODO: I don't know!
default:
return true;
}
}


DeterministicTimer detTimer;

}
17 changes: 14 additions & 3 deletions src/library/DeterministicTimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class DeterministicTimer

public:

/* Initialize the class members */
void initialize(void);
/* Initialize the class members and elapsed time */
void initialize(uint64_t initial_sec, uint64_t initial_nsec);

/* Update and return the time of the deterministic timer */
struct timespec getTicks();
Expand Down Expand Up @@ -82,6 +82,12 @@ class DeterministicTimer
/* Are we inside a frame boudary */
bool isInsideFrameBoundary();

/* Set a new value for the realtime clock */
void setRealTime(struct timespec new_realtime);

/* Returns if the time call returns a monotonic or realtime */
bool isTimeCallMonotonic(SharedConfig::TimeCallType type);

private:

bool insideFrameBoundary = false;
Expand All @@ -97,9 +103,14 @@ class DeterministicTimer
/* Current sum of all fractional increments */
unsigned int fractional_part;

/* State of the deterministic timer */
/* State of the deterministic (monotonic) timer, starts at 0.0 */
TimeHolder ticks;

/* Difference between the monotonic timer (which starts at 0.0)and the
* realtime timer (which starts at user specified value, and which can
* be modified during the run by the user) */
TimeHolder realtime_delta;

/*
* Extra ticks to add to GetTicks().
* Required for very specific situations.
Expand Down
54 changes: 24 additions & 30 deletions src/library/frame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,26 @@ static bool skipDraw(float fps)
return true;
}

static void sendFrameCountTime()
{
/* Detect an error on the first send, and exit the game if so */
int ret = sendMessage(MSGB_FRAMECOUNT_TIME);
if (ret == -1)
exit(1);

sendData(&framecount, sizeof(uint64_t));
struct timespec ticks = detTimer.getTicks(SharedConfig::TIMETYPE_UNTRACKED_MONOTONIC);
uint64_t ticks_val = ticks.tv_sec;
sendData(&ticks_val, sizeof(uint64_t));
ticks_val = ticks.tv_nsec;
sendData(&ticks_val, sizeof(uint64_t));
ticks = detTimer.getTicks(SharedConfig::TIMETYPE_UNTRACKED_REALTIME);
ticks_val = ticks.tv_sec;
sendData(&ticks_val, sizeof(uint64_t));
ticks_val = ticks.tv_nsec;
sendData(&ticks_val, sizeof(uint64_t));
}

#ifdef LIBTAS_ENABLE_HUD
void frameBoundary(std::function<void()> draw, RenderHUD& hud)
#else
Expand Down Expand Up @@ -267,19 +287,8 @@ void frameBoundary(std::function<void()> draw)
/* Other threads may send socket messages, so we lock the socket */
lockSocket();

/* Send framecount and internal time */

/* Detect an error on the first send, and exit the game if so */
int ret = sendMessage(MSGB_FRAMECOUNT_TIME);
if (ret == -1)
exit(1);

sendData(&framecount, sizeof(uint64_t));
struct timespec ticks = detTimer.getTicks();
uint64_t ticks_val = ticks.tv_sec;
sendData(&ticks_val, sizeof(uint64_t));
ticks_val = ticks.tv_nsec;
sendData(&ticks_val, sizeof(uint64_t));
/* Send framecount and internal time */
sendFrameCountTime();

/* Send GameInfo struct if needed */
if (game_info.tosend) {
Expand Down Expand Up @@ -783,13 +792,7 @@ static void receive_messages(std::function<void()> draw)
/* We must send again the frame count and time because it
* probably has changed.
*/
sendMessage(MSGB_FRAMECOUNT_TIME);
sendData(&framecount, sizeof(uint64_t));
struct timespec ticks = detTimer.getTicks();
uint64_t ticks_val = ticks.tv_sec;
sendData(&ticks_val, sizeof(uint64_t));
ticks_val = ticks.tv_nsec;
sendData(&ticks_val, sizeof(uint64_t));
sendFrameCountTime();

/* Screen should have changed after loading */
#ifdef LIBTAS_ENABLE_HUD
Expand Down Expand Up @@ -833,16 +836,7 @@ static void receive_messages(std::function<void()> draw)
* frame count and time because the program will pull a
* message in either case.
*/
sendMessage(MSGB_FRAMECOUNT_TIME);
sendData(&framecount, sizeof(uint64_t));
{
struct timespec ticks = detTimer.getTicks();
uint64_t ticks_val = ticks.tv_sec;
sendData(&ticks_val, sizeof(uint64_t));
ticks_val = ticks.tv_nsec;
sendData(&ticks_val, sizeof(uint64_t));
}

sendFrameCountTime();
break;

case MSGN_STOP_ENCODE:
Expand Down
15 changes: 11 additions & 4 deletions src/library/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ void __attribute__((constructor)) init(void)
/* End message */
sendMessage(MSGB_END_INIT);

/* Initial framecount and elapsed time */
uint64_t initial_sec = 0, initial_nsec = 0;
framecount = 0;

/* Receive information from the program */
int message = receiveMessage();
while (message != MSGN_END_INIT) {
Expand Down Expand Up @@ -153,6 +157,12 @@ void __attribute__((constructor)) init(void)
steamremotestorage = receiveString();
SteamSetRemoteStorageFolder(steamremotestorage);
break;
case MSGN_INITIAL_FRAMECOUNT_TIME:
/* Set the framecount and time to their initial values */
receiveData(&framecount, sizeof(uint64_t));
receiveData(&initial_sec, sizeof(uint64_t));
receiveData(&initial_nsec, sizeof(uint64_t));
break;
default:
debuglogstdio(LCF_ERROR | LCF_SOCKET, "Unknown socket message %d", message);
exit(1);
Expand All @@ -164,9 +174,6 @@ void __attribute__((constructor)) init(void)
raise(SIGINT);
}

/* Set the frame count to the initial frame count */
framecount = shared_config.initial_framecount;

ai.emptyInputs();
old_ai.emptyInputs();
game_ai.emptyInputs();
Expand All @@ -178,7 +185,7 @@ void __attribute__((constructor)) init(void)
* so they must be initialized after receiving it.
*/
nonDetTimer.initialize();
detTimer.initialize();
detTimer.initialize(initial_sec, initial_nsec);

/* Initialize sound parameters */
audiocontext.init();
Expand Down
4 changes: 2 additions & 2 deletions src/library/pthreadwrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ static std::map<pthread_cond_t*, clockid_t>& getCondClock() {
if (GlobalState::isNative())
return orig::sem_trywait(sem);

DEBUGLOGCALL(LCF_THREAD | LCF_WAIT | LCF_TODO);
DEBUGLOGCALL(LCF_WAIT | LCF_TODO);
return orig::sem_trywait(sem);
}

Expand All @@ -707,7 +707,7 @@ static std::map<pthread_cond_t*, clockid_t>& getCondClock() {
if (GlobalState::isNative())
return orig::sem_post(sem);

debuglogstdio(LCF_THREAD | LCF_WAIT, "%s called with sem %p", __func__, sem);
debuglogstdio(LCF_WAIT, "%s called with sem %p", __func__, sem);
return orig::sem_post(sem);
}

Expand Down
12 changes: 11 additions & 1 deletion src/library/timewrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,17 @@ DEFINE_ORIG_POINTER(clock_gettime)
}
}

*tp = detTimer.getTicks(SharedConfig::TIMETYPE_CLOCKGETTIME);
switch (clock_id) {
case CLOCK_REALTIME:
case CLOCK_REALTIME_ALARM:
case CLOCK_REALTIME_COARSE:
case CLOCK_TAI:
*tp = detTimer.getTicks(SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME);
break;
default:
*tp = detTimer.getTicks(SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC);
break;
}
debuglogstdio(LCF_TIMEGET | LCF_FREQUENT, " returning %d.%09d", tp->tv_sec, tp->tv_nsec);

if (shared_config.game_specific_timing & SharedConfig::GC_TIMING_CELESTE) {
Expand Down
6 changes: 5 additions & 1 deletion src/program/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,14 @@ struct Context {
/* frame count */
uint64_t framecount = 0;

/* current time */
/* current elapsed time since the game startup */
int64_t current_time_sec;
int64_t current_time_nsec;

/* current realtime (clock) */
int64_t current_realtime_sec;
int64_t current_realtime_nsec;

/* movie time */
int64_t movie_time_sec;
int64_t movie_time_nsec;
Expand Down
Loading

0 comments on commit c769194

Please sign in to comment.