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;
};
struct UndoSaveStateBuffer : public SaveStateBuffer
{
time_t timestamp;
};
} // namespace
static void CheckCacheLineSize();
@ -318,7 +323,7 @@ struct ALIGN_TO_CACHE_LINE StateVars
Threading::ThreadHandle cpu_thread_handle;
// 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.
u64 session_start_time = 0;
@ -2024,7 +2029,7 @@ void System::ClearRunningGame()
s_state.running_game_entry = nullptr;
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);
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,
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);
}
@ -5282,12 +5287,14 @@ bool System::UndoLoadState()
Host::ReportErrorAsync("Error",
fmt::format("Failed to load undo state, resetting system:\n", error.GetDescription()));
s_state.undo_load_state.reset();
Host::OnSystemUndoStateAvailabilityChanged(false, 0);
ResetSystem();
return false;
}
INFO_LOG("Loaded undo save state.");
s_state.undo_load_state.reset();
Host::OnSystemUndoStateAvailabilityChanged(false, 0);
return true;
}
@ -5303,10 +5310,13 @@ bool System::SaveUndoLoadState()
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save undo load state:\n{}"), error.GetDescription()),
Host::OSD_CRITICAL_ERROR_DURATION);
s_state.undo_load_state.reset();
Host::OnSystemUndoStateAvailabilityChanged(false, 0);
return false;
}
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;
}

View File

@ -115,8 +115,11 @@ void OnSystemAbnormalShutdown(const std::string_view reason);
void OnPerformanceCountersUpdated(const GPUBackend* gpu_backend);
/// 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,
GameHash game_hash);
void OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name,
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.
void OnMediaCaptureStarted();

View File

@ -1185,8 +1185,8 @@ void MiniHost::WarnAboutInterface()
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,
GameHash game_hash)
void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
const std::string& game_name, GameHash game_hash)
{
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());
}
void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
{
//
}
void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false */)
{
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_path;
static QIcon s_current_game_icon;
static std::optional<std::time_t> s_undo_state_timestamp;
bool QtHost::IsSystemPaused()
{
@ -576,6 +577,7 @@ void MainWindow::onSystemDestroyed()
s_system_starting = false;
s_system_valid = false;
s_system_paused = false;
s_undo_state_timestamp.reset();
// If we're closing or in batch mode, quit the whole application now.
if (m_is_closing || QtHost::InBatchMode())
@ -593,7 +595,7 @@ void MainWindow::onSystemDestroyed()
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_title = game_title;
@ -603,6 +605,14 @@ void MainWindow::onRunningGameChanged(const QString& filename, const QString& ga
updateWindowTitle();
}
void MainWindow::onSystemUndoStateAvailabilityChanged(bool available, quint64 timestamp)
{
if (!available)
s_undo_state_timestamp.reset();
else
s_undo_state_timestamp = timestamp;
}
void MainWindow::onMediaCaptureStarted()
{
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)));
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);
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);
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);
});
QAction* load_from_state = menu->addAction(tr("Undo Load State"));
load_from_state->setEnabled(System::CanUndoLoadState());
QAction* load_from_state =
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);
menu->addSeparator();
@ -949,7 +962,7 @@ void MainWindow::populateSaveStateMenu(std::string_view game_serial, QMenu* menu
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
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);
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::systemPaused, this, &MainWindow::onSystemPaused);
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::mediaCaptureStopped, this, &MainWindow::onMediaCaptureStopped);
connect(g_emu_thread, &EmuThread::mouseModeRequested, this, &MainWindow::onMouseModeRequested);

View File

@ -146,7 +146,8 @@ private Q_SLOTS:
void onSystemDestroyed();
void onSystemPaused();
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 onMediaCaptureStopped();
void onAchievementsLoginRequested(Achievements::LoginRequestReason reason);
@ -215,7 +216,6 @@ private Q_SLOTS:
void onDebugLogChannelsMenuAboutToShow();
void openCPUDebugger();
protected:
void showEvent(QShowEvent* event) override;
void closeEvent(QCloseEvent* event) override;
@ -296,6 +296,8 @@ private:
void startFileOrChangeDisc(const QString& path);
void promptForDiscChange(const QString& path);
static QString formatTimestampForSaveStateMenu(u64 timestamp);
Ui::MainWindow m_ui;
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);
}
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name,
GameHash hash)
void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
const std::string& game_name, GameHash hash)
{
emit g_emu_thread->runningGameChanged(QString::fromStdString(disc_path), QString::fromStdString(game_serial),
QString::fromStdString(game_name));
emit g_emu_thread->systemGameChanged(QString::fromStdString(disc_path), QString::fromStdString(game_serial),
QString::fromStdString(game_name));
}
void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
{
emit g_emu_thread->systemUndoStateAvailabilityChanged(available, timestamp);
}
void Host::OnMediaCaptureStarted()

View File

@ -140,6 +140,8 @@ Q_SIGNALS:
void systemDestroyed();
void systemPaused();
void systemResumed();
void systemGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void systemUndoStateAvailabilityChanged(bool available, quint64 timestamp);
void gameListRefreshed();
void gameListRowsChanged(const QList<int>& rows_changed);
std::optional<WindowInfo> onAcquireRenderWindowRequested(RenderAPI render_api, bool fullscreen,
@ -148,7 +150,6 @@ Q_SIGNALS:
void onResizeRenderWindowRequested(qint32 width, qint32 height);
void onReleaseRenderWindowRequested();
void focusDisplayWidgetRequested();
void runningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title);
void inputProfileLoaded();
void mouseModeRequested(bool relative, bool hide_cursor);
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,
GameHash hash)
void Host::OnSystemGameChanged(const std::string& disc_path, const std::string& game_serial,
const std::string& game_name, GameHash hash)
{
INFO_LOG("Disc Path: {}", disc_path);
INFO_LOG("Game Serial: {}", game_serial);
INFO_LOG("Game Name: {}", game_name);
}
void Host::OnSystemUndoStateAvailabilityChanged(bool available, u64 timestamp)
{
//
}
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()));
s_base_settings_interface.SetStringValue("CPU", "ExecutionMode",
Settings::GetCPUExecutionModeName(cpu.value()));
Settings::GetCPUExecutionModeName(cpu.value()));
continue;
}
else if (CHECK_ARG("-pgxp"))