diff --git a/CHANGELOG.md b/CHANGELOG.md index 46c39422..a3a2a879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## [Unreleased] ### Added + +* Split deterministic timer into monotonic/realtime (#474) + ### Changed ### Fixed diff --git a/src/library/DeterministicTimer.cpp b/src/library/DeterministicTimer.cpp index b0f2d443..c5192b23 100644 --- a/src/library/DeterministicTimer.cpp +++ b/src/library/DeterministicTimer.cpp @@ -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()", @@ -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) @@ -68,7 +69,10 @@ 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; } @@ -76,9 +80,15 @@ struct timespec DeterministicTimer::getTicks(SharedConfig::TimeCallType type) 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); @@ -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) @@ -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; @@ -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; } diff --git a/src/library/DeterministicTimer.h b/src/library/DeterministicTimer.h index 3da7f7a9..c6b9dfdf 100644 --- a/src/library/DeterministicTimer.h +++ b/src/library/DeterministicTimer.h @@ -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(); @@ -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; @@ -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. diff --git a/src/library/frame.cpp b/src/library/frame.cpp index 14eb0a59..c8928f8e 100644 --- a/src/library/frame.cpp +++ b/src/library/frame.cpp @@ -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 draw, RenderHUD& hud) #else @@ -267,19 +287,8 @@ void frameBoundary(std::function 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) { @@ -783,13 +792,7 @@ static void receive_messages(std::function 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 @@ -833,16 +836,7 @@ static void receive_messages(std::function 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: diff --git a/src/library/main.cpp b/src/library/main.cpp index 35f5fc3a..30dde374 100644 --- a/src/library/main.cpp +++ b/src/library/main.cpp @@ -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) { @@ -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); @@ -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(); @@ -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(); diff --git a/src/library/pthreadwrappers.cpp b/src/library/pthreadwrappers.cpp index 63a1b693..78eda47b 100644 --- a/src/library/pthreadwrappers.cpp +++ b/src/library/pthreadwrappers.cpp @@ -697,7 +697,7 @@ static std::map& 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); } @@ -707,7 +707,7 @@ static std::map& 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); } diff --git a/src/library/timewrappers.cpp b/src/library/timewrappers.cpp index 5a33a2f4..40a71e65 100644 --- a/src/library/timewrappers.cpp +++ b/src/library/timewrappers.cpp @@ -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) { diff --git a/src/program/Context.h b/src/program/Context.h index 40f59e0a..60f082a8 100644 --- a/src/program/Context.h +++ b/src/program/Context.h @@ -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; diff --git a/src/program/GameLoop.cpp b/src/program/GameLoop.cpp index fea2d9f1..1c5bde0f 100644 --- a/src/program/GameLoop.cpp +++ b/src/program/GameLoop.cpp @@ -163,9 +163,6 @@ void GameLoop::init() if (context->status != Context::RESTARTING) context->framecount = 0; - /* Set the initial frame count for the game */ - context->config.sc.initial_framecount = context->framecount; - /* Reset the rerecord count if not restarting */ if (context->status != Context::RESTARTING) context->rerecord_count = 0; @@ -250,10 +247,12 @@ void GameLoop::init() } } - /* Set the current time to the initial time, except when restarting */ + /* Set the current realtime to the initial time, except when restarting */ if (context->status != Context::RESTARTING) { - context->current_time_sec = context->config.sc.initial_time_sec; - context->current_time_nsec = context->config.sc.initial_time_nsec; + context->current_time_sec = 0; + context->current_time_nsec = 0; + context->current_realtime_sec = context->config.sc.initial_time_sec; + context->current_realtime_nsec = context->config.sc.initial_time_nsec; } /* If auto-restart is set, write back savefiles on game exit */ @@ -316,16 +315,22 @@ void GameLoop::initProcessMessages() /* Send shared config */ - /* This is a bit hackish, change the initial time to the current time before + /* This is a bit hackish, change the initial time to the current realtime before * sending so that the game gets the correct time after restarting. */ struct timespec it = {context->config.sc.initial_time_sec, context->config.sc.initial_time_nsec}; - context->config.sc.initial_time_sec = context->current_time_sec; - context->config.sc.initial_time_nsec = context->current_time_nsec; + context->config.sc.initial_time_sec = context->current_realtime_sec; + context->config.sc.initial_time_nsec = context->current_realtime_nsec; sendMessage(MSGN_CONFIG); sendData(&context->config.sc, sizeof(SharedConfig)); context->config.sc.initial_time_sec = it.tv_sec; context->config.sc.initial_time_nsec = it.tv_nsec; + /* Send initial framecount and elapsed time */ + sendMessage(MSGN_INITIAL_FRAMECOUNT_TIME); + sendData(&context->framecount, sizeof(uint64_t)); + sendData(&context->current_time_sec, sizeof(uint64_t)); + sendData(&context->current_time_nsec, sizeof(uint64_t)); + /* Send dump file if dumping from the beginning */ if (context->config.sc.av_dumping) { sendMessage(MSGN_DUMP_FILE); @@ -400,6 +405,8 @@ bool GameLoop::startFrameMessages() receiveData(&context->framecount, sizeof(uint64_t)); receiveData(&context->current_time_sec, sizeof(uint64_t)); receiveData(&context->current_time_nsec, sizeof(uint64_t)); + receiveData(&context->current_realtime_sec, sizeof(uint64_t)); + receiveData(&context->current_realtime_nsec, sizeof(uint64_t)); if (context->config.sc.recording == SharedConfig::RECORDING_WRITE) { /* If the input editor is opened, recording does not truncate inputs */ bool notTruncInputs = false; @@ -407,12 +414,8 @@ bool GameLoop::startFrameMessages() if (!notTruncInputs || (context->framecount > context->config.sc.movie_framecount)) { context->config.sc.movie_framecount = context->framecount; - context->movie_time_sec = context->current_time_sec - context->config.sc.initial_time_sec; - context->movie_time_nsec = context->current_time_nsec - context->config.sc.initial_time_nsec; - if (context->movie_time_nsec < 0) { - context->movie_time_nsec += 1000000000; - context->movie_time_sec--; - } + context->movie_time_sec = context->current_time_sec; + context->movie_time_nsec = context->current_time_nsec; } } break; @@ -657,18 +660,14 @@ void GameLoop::processInputs(AllInputs &ai) /* First frame after movie end */ if (ret == -2) { /* Check for the moviefile length */ - int64_t cur_sec, cur_nsec; - cur_sec = context->current_time_sec - context->config.sc.initial_time_sec; - cur_nsec = context->current_time_nsec - context->config.sc.initial_time_nsec; - if ((context->movie_time_sec != -1) && - ((context->movie_time_sec != cur_sec) || - (context->movie_time_nsec != cur_nsec))) { + ((context->movie_time_sec != context->current_time_sec) || + (context->movie_time_nsec != context->current_time_nsec))) { - emit alertToShow(QString("Movie length mismatch. Metadata stores %1.%2 seconds but end time is %3.%4 seconds.").arg(context->movie_time_sec).arg(context->movie_time_nsec, 9, 10, QChar('0')).arg(cur_sec).arg(cur_nsec, 9, 10, QChar('0'))); + emit alertToShow(QString("Movie length mismatch. Metadata stores %1.%2 seconds but end time is %3.%4 seconds.").arg(context->movie_time_sec).arg(context->movie_time_nsec, 9, 10, QChar('0')).arg(context->current_time_sec).arg(context->current_time_nsec, 9, 10, QChar('0'))); } - context->movie_time_sec = cur_sec; - context->movie_time_nsec = cur_nsec; + context->movie_time_sec = context->current_time_sec; + context->movie_time_nsec = context->current_time_nsec; } } diff --git a/src/program/movie/MovieFileHeader.cpp b/src/program/movie/MovieFileHeader.cpp index 887c2d0e..76697c84 100644 --- a/src/program/movie/MovieFileHeader.cpp +++ b/src/program/movie/MovieFileHeader.cpp @@ -69,7 +69,8 @@ void MovieFileHeader::load() context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_TIME] = config.value("time").toInt(); context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_GETTIMEOFDAY] = config.value("gettimeofday").toInt(); context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCK] = config.value("clock").toInt(); - context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCKGETTIME] = config.value("clock_gettime").toInt(); + context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME] = config.value("clock_gettime_real").toInt(); + context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC] = config.value("clock_gettime_monotonic").toInt(); context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_SDLGETTICKS] = config.value("sdl_getticks").toInt(); context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_SDLGETPERFORMANCECOUNTER] = config.value("sdl_getperformancecounter").toInt(); config.endGroup(); @@ -148,7 +149,8 @@ void MovieFileHeader::save(uint64_t tot_frames, uint64_t nb_frames) config.setValue("time", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_TIME]); config.setValue("gettimeofday", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_GETTIMEOFDAY]); config.setValue("clock", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCK]); - config.setValue("clock_gettime", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCKGETTIME]); + config.setValue("clock_gettime_real", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME]); + config.setValue("clock_gettime_monotonic", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC]); config.setValue("sdl_getticks", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_SDLGETTICKS]); config.setValue("sdl_getperformancecounter", context->config.sc.main_gettimes_threshold[SharedConfig::TIMETYPE_SDLGETPERFORMANCECOUNTER]); config.endGroup(); diff --git a/src/program/ui/MainWindow.cpp b/src/program/ui/MainWindow.cpp index 4b8becaa..7b2d3147 100644 --- a/src/program/ui/MainWindow.cpp +++ b/src/program/ui/MainWindow.cpp @@ -196,15 +196,25 @@ MainWindow::MainWindow(Context* c) : QMainWindow(), context(c) rerecordCount->setReadOnly(true); rerecordCount->setMaximum(std::numeric_limits::max()); - /* Initial time */ - initialTimeSec = new QSpinBox(); - initialTimeSec->setMaximum(std::numeric_limits::max()); - initialTimeSec->setMinimumWidth(50); - initialTimeNsec = new QSpinBox(); - initialTimeNsec->setMaximum(std::numeric_limits::max()); - initialTimeNsec->setMinimumWidth(50); - disabledWidgetsOnStart.append(initialTimeSec); - disabledWidgetsOnStart.append(initialTimeNsec); + /* Elapsed and Real time */ + elapsedTimeSec = new QSpinBox(); + elapsedTimeSec->setMaximum(std::numeric_limits::max()); + elapsedTimeSec->setMinimumWidth(50); + elapsedTimeSec->setReadOnly(true); + elapsedTimeNsec = new QSpinBox(); + elapsedTimeNsec->setMaximum(std::numeric_limits::max()); + elapsedTimeNsec->setMinimumWidth(50); + elapsedTimeNsec->setReadOnly(true); + + realTimeSec = new QSpinBox(); + realTimeSec->setMaximum(std::numeric_limits::max()); + realTimeSec->setMinimumWidth(50); + realTimeNsec = new QSpinBox(); + realTimeNsec->setMaximum(std::numeric_limits::max()); + realTimeNsec->setMinimumWidth(50); + realTimeFormat = new QLabel(); + connect(realTimeSec, QOverload::of(&QSpinBox::valueChanged), this, &MainWindow::slotRealTimeFormat); + connect(realTimeNsec, QOverload::of(&QSpinBox::valueChanged), this, &MainWindow::slotRealTimeFormat); /* Pause/FF */ pauseCheck = new QCheckBox("Pause"); @@ -329,15 +339,18 @@ MainWindow::MainWindow(Context* c) : QMainWindow(), context(c) generalFpsLayout->addWidget(fpsValues); generalFpsLayout->addStretch(1); - QHBoxLayout *generalTimeLayout = new QHBoxLayout; - generalTimeLayout->addWidget(new QLabel(tr("System time:"))); - generalTimeLayout->addStretch(1); - generalTimeLayout->addWidget(initialTimeSec); - generalTimeLayout->addWidget(new QLabel(tr("sec"))); - generalTimeLayout->addStretch(1); - generalTimeLayout->addWidget(initialTimeNsec); - generalTimeLayout->addWidget(new QLabel(tr("nsec"))); - generalTimeLayout->addStretch(1); + QGridLayout *generalTimeLayout = new QGridLayout; + generalTimeLayout->addWidget(new QLabel(tr("Elapsed time:")), 0, 0); + generalTimeLayout->addWidget(elapsedTimeSec, 0, 2); + generalTimeLayout->addWidget(new QLabel(tr("sec")), 0, 3); + generalTimeLayout->addWidget(elapsedTimeNsec, 0, 5); + generalTimeLayout->addWidget(new QLabel(tr("nsec")), 0, 6); + generalTimeLayout->addWidget(new QLabel(tr("System time:")), 1, 0); + generalTimeLayout->addWidget(realTimeSec, 1, 2); + generalTimeLayout->addWidget(new QLabel(tr("sec")), 1, 3); + generalTimeLayout->addWidget(realTimeNsec, 1, 5); + generalTimeLayout->addWidget(new QLabel(tr("nsec")), 1, 6); + generalTimeLayout->addWidget(realTimeFormat, 1, 7); QHBoxLayout *generalControlLayout = new QHBoxLayout; generalControlLayout->addWidget(pauseCheck); @@ -547,7 +560,8 @@ void MainWindow::createActions() addActionCheckable(timeMainGroup, tr("time()"), SharedConfig::TIMETYPE_TIME); addActionCheckable(timeMainGroup, tr("gettimeofday()"), SharedConfig::TIMETYPE_GETTIMEOFDAY); addActionCheckable(timeMainGroup, tr("clock()"), SharedConfig::TIMETYPE_CLOCK); - addActionCheckable(timeMainGroup, tr("clock_gettime()"), SharedConfig::TIMETYPE_CLOCKGETTIME); + addActionCheckable(timeMainGroup, tr("clock_gettime() realtime"), SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME); + addActionCheckable(timeMainGroup, tr("clock_gettime() monotonic"), SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC); addActionCheckable(timeMainGroup, tr("SDL_GetTicks()"), SharedConfig::TIMETYPE_SDLGETTICKS); addActionCheckable(timeMainGroup, tr("SDL_GetPerformanceCounter()"), SharedConfig::TIMETYPE_SDLGETPERFORMANCECOUNTER); addActionCheckable(timeMainGroup, tr("GetTickCount()"), SharedConfig::TIMETYPE_GETTICKCOUNT); @@ -560,7 +574,8 @@ void MainWindow::createActions() addActionCheckable(timeSecGroup, tr("time()"), SharedConfig::TIMETYPE_TIME); addActionCheckable(timeSecGroup, tr("gettimeofday()"), SharedConfig::TIMETYPE_GETTIMEOFDAY); addActionCheckable(timeSecGroup, tr("clock()"), SharedConfig::TIMETYPE_CLOCK); - addActionCheckable(timeSecGroup, tr("clock_gettime()"), SharedConfig::TIMETYPE_CLOCKGETTIME); + addActionCheckable(timeSecGroup, tr("clock_gettime() realtime"), SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME); + addActionCheckable(timeSecGroup, tr("clock_gettime() monotonic"), SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC); addActionCheckable(timeSecGroup, tr("SDL_GetTicks()"), SharedConfig::TIMETYPE_SDLGETTICKS); addActionCheckable(timeSecGroup, tr("SDL_GetPerformanceCounter()"), SharedConfig::TIMETYPE_SDLGETPERFORMANCECOUNTER); addActionCheckable(timeSecGroup, tr("GetTickCount()"), SharedConfig::TIMETYPE_GETTICKCOUNT); @@ -979,10 +994,14 @@ void MainWindow::updateStatus() movieBox->setCheckable(true); movieBox->setChecked(context->config.sc.recording != SharedConfig::NO_RECORDING); - fpsNumField->setEnabled(true); - fpsDenField->setEnabled(true); - initialTimeSec->setValue(context->config.sc.initial_time_sec); - initialTimeNsec->setValue(context->config.sc.initial_time_nsec); + fpsNumField->setReadOnly(false); + fpsDenField->setReadOnly(false); + elapsedTimeSec->setValue(0); + elapsedTimeNsec->setValue(0); + realTimeSec->setValue(context->config.sc.initial_time_sec); + realTimeNsec->setValue(context->config.sc.initial_time_nsec); + realTimeSec->setReadOnly(false); + realTimeNsec->setReadOnly(false); launchGdbButton->setEnabled(true); @@ -1014,9 +1033,12 @@ void MainWindow::updateStatus() a->setEnabled(false); if (!context->config.sc.variable_framerate) { - fpsNumField->setEnabled(false); - fpsDenField->setEnabled(false); + fpsNumField->setReadOnly(true); + fpsDenField->setReadOnly(true); } + + realTimeSec->setReadOnly(true); + realTimeNsec->setReadOnly(true); launchGdbButton->setEnabled(false); @@ -1121,11 +1143,13 @@ void MainWindow::updateUIFrequent() movieFrameCount->setValue(context->config.sc.movie_framecount); /* Update time */ - initialTimeSec->setValue(context->current_time_sec); - initialTimeNsec->setValue(context->current_time_nsec); + elapsedTimeSec->setValue(context->current_time_sec); + elapsedTimeNsec->setValue(context->current_time_nsec); + realTimeSec->setValue(context->current_realtime_sec); + realTimeNsec->setValue(context->current_realtime_nsec); /* Update movie time */ - double sec = context->current_time_sec - context->config.sc.initial_time_sec + ((double)(context->current_time_nsec - context->config.sc.initial_time_nsec))/1000000000; + double sec = context->current_time_sec + ((double) context->current_time_nsec)/1000000000; int imin = (int)(sec/60); double dsec = sec - 60*imin; currentLength->setText(QString("Current Time: %1m %2s").arg(imin).arg(dsec, 0, 'f', 2)); @@ -1270,8 +1294,8 @@ void MainWindow::updateMovieParams() setRadioFromList(joystickGroup, context->config.sc.nb_controllers); fpsNumField->setValue(context->config.sc.framerate_num); fpsDenField->setValue(context->config.sc.framerate_den); - initialTimeSec->setValue(context->config.sc.initial_time_sec); - initialTimeNsec->setValue(context->config.sc.initial_time_nsec); + realTimeSec->setValue(context->config.sc.initial_time_sec); + realTimeNsec->setValue(context->config.sc.initial_time_nsec); autoRestartAction->setChecked(context->config.auto_restart); variableFramerateAction->setChecked(context->config.sc.variable_framerate); for (auto& action : timeMainGroup->actions()) { @@ -1416,8 +1440,8 @@ void MainWindow::slotLaunch(bool attach_gdb) /* Set a few parameters */ context->config.sc.framerate_num = fpsNumField->value(); context->config.sc.framerate_den = fpsDenField->value(); - context->config.sc.initial_time_sec = initialTimeSec->value(); - context->config.sc.initial_time_nsec = initialTimeNsec->value(); + context->config.sc.initial_time_sec = realTimeSec->value(); + context->config.sc.initial_time_nsec = realTimeNsec->value(); setListFromRadio(frequencyGroup, context->config.sc.audio_frequency); setListFromRadio(bitDepthGroup, context->config.sc.audio_bitdepth); @@ -1798,6 +1822,16 @@ void MainWindow::slotLuaReset() Lua::Main::reset(context); } +void MainWindow::slotRealTimeFormat() +{ + char buf[22]; + struct timespec realtime = {realTimeSec->value(), realTimeNsec->value()}; + struct tm tm; + gmtime_r(&realtime.tv_sec, &tm); + strftime(buf, 21, "%FT%TZ", gmtime(&realtime.tv_sec)); + realTimeFormat->setText(QString(buf)); +} + void MainWindow::alertOffer(QString alert_msg, void* promise) { std::promise* saveAnswer = static_cast*>(promise); diff --git a/src/program/ui/MainWindow.h b/src/program/ui/MainWindow.h index 1956fa5d..610cf2c9 100644 --- a/src/program/ui/MainWindow.h +++ b/src/program/ui/MainWindow.h @@ -171,8 +171,11 @@ class MainWindow : public QMainWindow QLabel *currentLength; QLabel *movieLength; - QSpinBox *initialTimeSec; - QSpinBox *initialTimeNsec; + QSpinBox *elapsedTimeSec; + QSpinBox *elapsedTimeNsec; + QSpinBox *realTimeSec; + QSpinBox *realTimeNsec; + QLabel *realTimeFormat; QPushButton *launchButton; QToolButton *launchGdbButton; @@ -289,6 +292,7 @@ private slots: void slotMouseGameWarp(bool checked); void slotLuaExecute(); void slotLuaReset(); + void slotRealTimeFormat(); }; #endif diff --git a/src/program/ui/TimeTraceModel.cpp b/src/program/ui/TimeTraceModel.cpp index f655c60c..55f37043 100644 --- a/src/program/ui/TimeTraceModel.cpp +++ b/src/program/ui/TimeTraceModel.cpp @@ -112,8 +112,10 @@ QVariant TimeTraceModel::data(const QModelIndex &index, int role) const return tr("gettimeofday()"); case SharedConfig::TIMETYPE_CLOCK: return tr("clock()"); - case SharedConfig::TIMETYPE_CLOCKGETTIME: - return tr("clock_gettime()"); + case SharedConfig::TIMETYPE_CLOCKGETTIME_REALTIME: + return tr("clock_gettime() realtime"); + case SharedConfig::TIMETYPE_CLOCKGETTIME_MONOTONIC: + return tr("clock_gettime() monotonic"); case SharedConfig::TIMETYPE_SDLGETTICKS: return tr("SDL_GetTicks()"); case SharedConfig::TIMETYPE_SDLGETPERFORMANCECOUNTER: diff --git a/src/shared/SharedConfig.h b/src/shared/SharedConfig.h index 28d86253..d06673dd 100644 --- a/src/shared/SharedConfig.h +++ b/src/shared/SharedConfig.h @@ -46,9 +46,6 @@ struct __attribute__((packed, aligned(8))) SharedConfig { /* Movie framecount */ uint64_t movie_framecount = 0; - /* Frame count at the start of the game. Used when game has restarted */ - uint64_t initial_framecount = 0; - /* Log status */ enum LogStatus { NO_LOGGING, @@ -138,11 +135,13 @@ struct __attribute__((packed, aligned(8))) SharedConfig { /* An enum indicating which time-getting function query the time */ enum TimeCallType { - TIMETYPE_UNTRACKED = -1, + TIMETYPE_UNTRACKED_REALTIME = -2, + TIMETYPE_UNTRACKED_MONOTONIC = -1, TIMETYPE_TIME = 0, TIMETYPE_GETTIMEOFDAY, TIMETYPE_CLOCK, - TIMETYPE_CLOCKGETTIME, + TIMETYPE_CLOCKGETTIME_REALTIME, + TIMETYPE_CLOCKGETTIME_MONOTONIC, TIMETYPE_SDLGETTICKS, TIMETYPE_SDLGETPERFORMANCECOUNTER, TIMETYPE_GETTICKCOUNT, diff --git a/src/shared/messages.h b/src/shared/messages.h index fdbe7c26..8545b9aa 100644 --- a/src/shared/messages.h +++ b/src/shared/messages.h @@ -35,8 +35,8 @@ enum { MSGN_START_FRAMEBOUNDARY, /* - * The game sends the frame number and time - * Argument: 3 uint64_t + * The game sends the frame number, monotonic and realtime + * Argument: 5 uint64_t */ MSGB_FRAMECOUNT_TIME, @@ -65,6 +65,12 @@ enum { */ MSGN_CONFIG, + /* + * Send initial framecount and time. Non-zero when restarting + * Argument: 3 uint64_t + */ + MSGN_INITIAL_FRAMECOUNT_TIME, + /* * The programs tells the game to end the frame boundary * Argument: none diff --git a/utils/gettimes.cpp b/utils/gettimes.cpp new file mode 100644 index 00000000..6bb78788 --- /dev/null +++ b/utils/gettimes.cpp @@ -0,0 +1,70 @@ +/* This code calls all functions to get the system time + * Can be compiled with: g++ -o gettimes gettimes.cpp `pkg-config --libs --cflags sdl2` + + * functions altered by setting time: time, gettimeofday, clock_gettime with CLOCK_REALTIME, CLOCK_REALTIME_COARSE and CLOCK_TAI + */ + +#include +#include +#include +#include +#include + +int main() +{ + while (true) { + time_t tt = time(NULL); + std::cout << "time() returns " << tt << std::endl; + + struct timeval tv; + gettimeofday(&tv, NULL); + std::cout << "gettimeofday() returns " << tv.tv_sec << " s and " << tv.tv_usec << " us" << std::endl; + + clock_t ct = clock(); + std::cout << "clock() returns " << ct << std::endl; + + struct timespec tp; + clock_gettime (CLOCK_REALTIME, &tp); + std::cout << "clock_gettime() with CLOCK_REALTIME returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_REALTIME_COARSE, &tp); + std::cout << "clock_gettime() with CLOCK_REALTIME_COARSE returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_TAI, &tp); + std::cout << "clock_gettime() with CLOCK_TAI returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_MONOTONIC, &tp); + std::cout << "clock_gettime() with CLOCK_MONOTONIC returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_MONOTONIC_COARSE, &tp); + std::cout << "clock_gettime() with CLOCK_MONOTONIC_COARSE returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_MONOTONIC_RAW, &tp); + std::cout << "clock_gettime() with CLOCK_MONOTONIC_RAW returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_BOOTTIME, &tp); + std::cout << "clock_gettime() with CLOCK_BOOTTIME returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_BOOTTIME_ALARM, &tp); + std::cout << "clock_gettime() with CLOCK_BOOTTIME_ALARM returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &tp); + std::cout << "clock_gettime() with CLOCK_PROCESS_CPUTIME_ID returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tp); + std::cout << "clock_gettime() with CLOCK_THREAD_CPUTIME_ID returns " << tp.tv_sec << " s and " << tp.tv_nsec << " us" << std::endl; + + Uint32 sdlt = SDL_GetTicks(); + std::cout << "SDL_GetTicks() returns " << sdlt << std::endl; + + // Uint64 sdlt64 = SDL_GetTicks64(); + // std::cout << "SDL_GetTicks64() returns " << sdlt64 << std::endl; + + Uint64 sdlpc = SDL_GetPerformanceCounter(); + std::cout << "SDL_GetPerformanceCounter() returns " << sdlpc << std::endl; + + usleep(1000000); + } + + return 0; +}