Qt: Backport undo load state timestamps

This commit is contained in:
Stenzek 2025-06-08 17:08:33 +10:00
parent 29e55a2e5b
commit cb2dfabeeb
No known key found for this signature in database
8 changed files with 70 additions and 24 deletions

View File

@ -137,6 +137,11 @@ struct SaveStateBuffer
size_t state_size; size_t state_size;
}; };
struct UndoSaveStateBuffer : public SaveStateBuffer
{
time_t timestamp;
};
} // namespace } // namespace
static void CheckCacheLineSize(); static void CheckCacheLineSize();
@ -318,7 +323,7 @@ struct ALIGN_TO_CACHE_LINE StateVars
Threading::ThreadHandle cpu_thread_handle; Threading::ThreadHandle cpu_thread_handle;
// temporary save state, created when loading, used to undo load state // temporary save state, created when loading, used to undo load state
std::optional<System::SaveStateBuffer> undo_load_state; std::optional<UndoSaveStateBuffer> undo_load_state;
// Used to track play time. We use a monotonic timer here, in case of clock changes. // Used to track play time. We use a monotonic timer here, in case of clock changes.
u64 session_start_time = 0; u64 session_start_time = 0;
@ -2024,7 +2029,7 @@ void System::ClearRunningGame()
s_state.running_game_entry = nullptr; s_state.running_game_entry = nullptr;
s_state.running_game_hash = 0; s_state.running_game_hash = 0;
Host::OnGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title, Host::OnSystemGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title,
s_state.running_game_hash); s_state.running_game_hash);
UpdateRichPresence(true); UpdateRichPresence(true);
@ -4212,7 +4217,7 @@ void System::UpdateRunningGame(const std::string& path, CDImage* image, bool boo
FullscreenUI::OnRunningGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title, FullscreenUI::OnRunningGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title,
s_state.running_game_hash); s_state.running_game_hash);
Host::OnGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title, Host::OnSystemGameChanged(s_state.running_game_path, s_state.running_game_serial, s_state.running_game_title,
s_state.running_game_hash); s_state.running_game_hash);
} }
@ -5282,12 +5287,14 @@ bool System::UndoLoadState()
Host::ReportErrorAsync("Error", Host::ReportErrorAsync("Error",
fmt::format("Failed to load undo state, resetting system:\n", error.GetDescription())); fmt::format("Failed to load undo state, resetting system:\n", error.GetDescription()));
s_state.undo_load_state.reset(); s_state.undo_load_state.reset();
Host::OnSystemUndoStateAvailabilityChanged(false, 0);
ResetSystem(); ResetSystem();
return false; return false;
} }
INFO_LOG("Loaded undo save state."); INFO_LOG("Loaded undo save state.");
s_state.undo_load_state.reset(); s_state.undo_load_state.reset();
Host::OnSystemUndoStateAvailabilityChanged(false, 0);
return true; return true;
} }
@ -5303,10 +5310,13 @@ bool System::SaveUndoLoadState()
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save undo load state:\n{}"), error.GetDescription()), fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save undo load state:\n{}"), error.GetDescription()),
Host::OSD_CRITICAL_ERROR_DURATION); Host::OSD_CRITICAL_ERROR_DURATION);
s_state.undo_load_state.reset(); s_state.undo_load_state.reset();
Host::OnSystemUndoStateAvailabilityChanged(false, 0);
return false; return false;
} }
INFO_LOG("Saved undo load state: {} bytes", s_state.undo_load_state->state_size); INFO_LOG("Saved undo load state: {} bytes", s_state.undo_load_state->state_size);
s_state.undo_load_state->timestamp = std::time(nullptr);
Host::OnSystemUndoStateAvailabilityChanged(true, static_cast<u64>(s_state.undo_load_state->timestamp));
return true; return true;
} }

View File

@ -115,8 +115,11 @@ void OnSystemAbnormalShutdown(const std::string_view reason);
void OnPerformanceCountersUpdated(const GPUBackend* gpu_backend); void OnPerformanceCountersUpdated(const GPUBackend* gpu_backend);
/// Provided by the host; called when the running executable changes. /// Provided by the host; called when the running executable changes.
void OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, void OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name,
GameHash game_hash); GameHash game_hash);
/// Provided by the host; called when the undo save state availability changes.
void OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp);
/// Called when media capture starts/stops. /// Called when media capture starts/stops.
void OnMediaCaptureStarted(); void OnMediaCaptureStarted();

View File

@ -1185,8 +1185,8 @@ void MiniHost::WarnAboutInterface()
Host::AddIconOSDWarning("MiniWarning", ICON_EMOJI_WARNING, message, Host::OSD_INFO_DURATION); Host::AddIconOSDWarning("MiniWarning", ICON_EMOJI_WARNING, message, Host::OSD_INFO_DURATION);
} }
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
GameHash game_hash) const std::string& game_name, GameHash game_hash)
{ {
using namespace MiniHost; using namespace MiniHost;
@ -1195,6 +1195,11 @@ void Host::OnGameChanged(const std::string& disc_path, const std::string& game_s
SDL_SetWindowTitle(s_state.sdl_window, GetWindowTitle(game_name).c_str()); SDL_SetWindowTitle(s_state.sdl_window, GetWindowTitle(game_name).c_str());
} }
void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
{
//
}
void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false */) void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false */)
{ {
using namespace MiniHost; using namespace MiniHost;

View File

@ -113,6 +113,7 @@ static QString s_current_game_title;
static QString s_current_game_serial; static QString s_current_game_serial;
static QString s_current_game_path; static QString s_current_game_path;
static QIcon s_current_game_icon; static QIcon s_current_game_icon;
static std::optional<std::time_t> s_undo_state_timestamp;
bool QtHost::IsSystemPaused() bool QtHost::IsSystemPaused()
{ {
@ -576,6 +577,7 @@ void MainWindow::onSystemDestroyed()
s_system_starting = false; s_system_starting = false;
s_system_valid = false; s_system_valid = false;
s_system_paused = false; s_system_paused = false;
s_undo_state_timestamp.reset();
// If we're closing or in batch mode, quit the whole application now. // If we're closing or in batch mode, quit the whole application now.
if (m_is_closing || QtHost::InBatchMode()) if (m_is_closing || QtHost::InBatchMode())
@ -593,7 +595,7 @@ void MainWindow::onSystemDestroyed()
switchToGameListView(); switchToGameListView();
} }
void MainWindow::onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title) void MainWindow::onSystemGameChanged(const QString& filename, const QString& game_serial, const QString& game_title)
{ {
s_current_game_path = filename; s_current_game_path = filename;
s_current_game_title = game_title; s_current_game_title = game_title;
@ -603,6 +605,14 @@ void MainWindow::onRunningGameChanged(const QString& filename, const QString& ga
updateWindowTitle(); updateWindowTitle();
} }
void MainWindow::onSystemUndoStateAvailabilityChanged(bool available, quint64 timestamp)
{
if (!available)
s_undo_state_timestamp.reset();
else
s_undo_state_timestamp = timestamp;
}
void MainWindow::onMediaCaptureStarted() void MainWindow::onMediaCaptureStarted()
{ {
QSignalBlocker sb(m_ui.actionMediaCapture); QSignalBlocker sb(m_ui.actionMediaCapture);
@ -891,7 +901,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
} }
} }
static QString FormatTimestampForSaveStateMenu(u64 timestamp) QString MainWindow::formatTimestampForSaveStateMenu(u64 timestamp)
{ {
const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(timestamp))); const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast<qint64>(timestamp)));
return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat)); return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
@ -904,7 +914,7 @@ void MainWindow::populateLoadStateMenu(std::string_view game_serial, QMenu* menu
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot); std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
const QString menu_title = const QString menu_title =
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot); ssi.has_value() ? title.arg(slot).arg(formatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
QAction* load_action = menu->addAction(menu_title); QAction* load_action = menu->addAction(menu_title);
load_action->setEnabled(ssi.has_value()); load_action->setEnabled(ssi.has_value());
@ -925,8 +935,11 @@ void MainWindow::populateLoadStateMenu(std::string_view game_serial, QMenu* menu
g_emu_thread->loadState(path); g_emu_thread->loadState(path);
}); });
QAction* load_from_state = menu->addAction(tr("Undo Load State")); QAction* load_from_state =
load_from_state->setEnabled(System::CanUndoLoadState()); menu->addAction(s_undo_state_timestamp.has_value() ?
tr("Undo Load State (%1)").arg(formatTimestampForSaveStateMenu(s_undo_state_timestamp.value())) :
tr("Undo Load State"));
load_from_state->setEnabled(s_undo_state_timestamp.has_value());
connect(load_from_state, &QAction::triggered, g_emu_thread, &EmuThread::undoLoadState); connect(load_from_state, &QAction::triggered, g_emu_thread, &EmuThread::undoLoadState);
menu->addSeparator(); menu->addSeparator();
@ -949,7 +962,7 @@ void MainWindow::populateSaveStateMenu(std::string_view game_serial, QMenu* menu
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot); std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
const QString menu_title = const QString menu_title =
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot); ssi.has_value() ? title.arg(slot).arg(formatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
QAction* save_action = menu->addAction(menu_title); QAction* save_action = menu->addAction(menu_title);
connect(save_action, &QAction::triggered, connect(save_action, &QAction::triggered,
@ -2123,7 +2136,9 @@ void MainWindow::connectSignals()
connect(g_emu_thread, &EmuThread::systemDestroyed, this, &MainWindow::onSystemDestroyed); connect(g_emu_thread, &EmuThread::systemDestroyed, this, &MainWindow::onSystemDestroyed);
connect(g_emu_thread, &EmuThread::systemPaused, this, &MainWindow::onSystemPaused); connect(g_emu_thread, &EmuThread::systemPaused, this, &MainWindow::onSystemPaused);
connect(g_emu_thread, &EmuThread::systemResumed, this, &MainWindow::onSystemResumed); connect(g_emu_thread, &EmuThread::systemResumed, this, &MainWindow::onSystemResumed);
connect(g_emu_thread, &EmuThread::runningGameChanged, this, &MainWindow::onRunningGameChanged); connect(g_emu_thread, &EmuThread::systemGameChanged, this, &MainWindow::onSystemGameChanged);
connect(g_emu_thread, &EmuThread::systemUndoStateAvailabilityChanged, this,
&MainWindow::onSystemUndoStateAvailabilityChanged);
connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted); connect(g_emu_thread, &EmuThread::mediaCaptureStarted, this, &MainWindow::onMediaCaptureStarted);
connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped); connect(g_emu_thread, &EmuThread::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested); connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested);

View File

@ -146,7 +146,8 @@ private Q_SLOTS:
void onSystemDestroyed(); void onSystemDestroyed();
void onSystemPaused(); void onSystemPaused();
void onSystemResumed(); void onSystemResumed();
void onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title); void onSystemGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void onSystemUndoStateAvailabilityChanged(bool available, quint64 timestamp);
void onMediaCaptureStarted(); void onMediaCaptureStarted();
void onMediaCaptureStopped(); void onMediaCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason); void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
@ -215,7 +216,6 @@ private Q_SLOTS:
void onDebugLogChannelsMenuAboutToShow(); void onDebugLogChannelsMenuAboutToShow();
void openCPUDebugger(); void openCPUDebugger();
protected: protected:
void showEvent(QShowEvent* event) override; void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
@ -296,6 +296,8 @@ private:
void startFileOrChangeDisc(const QString& path); void startFileOrChangeDisc(const QString& path);
void promptForDiscChange(const QString& path); void promptForDiscChange(const QString& path);
static QString formatTimestampForSaveStateMenu(u64 timestamp);
Ui::MainWindow m_ui; Ui::MainWindow m_ui;
GameListWidget* m_game_list_widget = nullptr; GameListWidget* m_game_list_widget = nullptr;

View File

@ -2517,11 +2517,16 @@ void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
g_emu_thread->updatePerformanceCounters(gpu_backend); g_emu_thread->updatePerformanceCounters(gpu_backend);
} }
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
GameHash hash) const std::string& game_name, GameHash hash)
{ {
emit g_emu_thread->runningGameChanged(QString::fromStdString(disc_path), QString::fromStdString(game_serial), emit g_emu_thread->systemGameChanged(QString::fromStdString(disc_path), QString::fromStdString(game_serial),
QString::fromStdString(game_name)); QString::fromStdString(game_name));
}
void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
{
emit g_emu_thread->systemUndoStateAvailabilityChanged(available, timestamp);
} }
void Host::OnMediaCaptureStarted() void Host::OnMediaCaptureStarted()

View File

@ -140,6 +140,8 @@ Q_SIGNALS:
void systemDestroyed(); void systemDestroyed();
void systemPaused(); void systemPaused();
void systemResumed(); void systemResumed();
void systemGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void systemUndoStateAvailabilityChanged(bool available, quint64 timestamp);
void gameListRefreshed(); void gameListRefreshed();
void gameListRowsChanged(const QList<int>& rows_changed); void gameListRowsChanged(const QList<int>& rows_changed);
std::optional<WindowInfo> onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen, std::optional<WindowInfo> onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen,
@ -148,7 +150,6 @@ Q_SIGNALS:
void onResizeRenderWindowRequested(qint32 width, qint32 height); void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested(); void onReleaseRenderWindowRequested();
void focusDisplayWidgetRequested(); void focusDisplayWidgetRequested();
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void inputProfileLoaded(); void inputProfileLoaded();
void mouseModeRequested(bool relative, bool hide_cursor); void mouseModeRequested(bool relative, bool hide_cursor);
void fullscreenUIStartedOrStopped(bool running); void fullscreenUIStartedOrStopped(bool running);

View File

@ -316,14 +316,19 @@ void Host::OnPerformanceCountersUpdated(const GPUBackend* gpu_backend)
// //
} }
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
GameHash hash) const std::string& game_name, GameHash hash)
{ {
INFO_LOG("Disc Path: {}", disc_path); INFO_LOG("Disc Path: {}", disc_path);
INFO_LOG("Game Serial: {}", game_serial); INFO_LOG("Game Serial: {}", game_serial);
INFO_LOG("Game Name: {}", game_name); INFO_LOG("Game Name: {}", game_name);
} }
void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
{
//
}
void Host::OnMediaCaptureStarted() void Host::OnMediaCaptureStarted()
{ {
// //
@ -890,7 +895,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
INFO_LOG("Setting CPU execution mode to {}.", Settings::GetCPUExecutionModeName(cpu.value())); INFO_LOG("Setting CPU execution mode to {}.", Settings::GetCPUExecutionModeName(cpu.value()));
s_base_settings_interface.SetStringValue("CPU", "ExecutionMode", s_base_settings_interface.SetStringValue("CPU", "ExecutionMode",
Settings::GetCPUExecutionModeName(cpu.value())); Settings::GetCPUExecutionModeName(cpu.value()));
continue; continue;
} }
else if (CHECK_ARG("-pgxp")) else if (CHECK_ARG("-pgxp"))