From 762ab3ff438bac84268caf4587e1a572adb45d18 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 3 Jul 2021 15:58:29 +1000 Subject: [PATCH] CommonHostInterface: Add undo load state feature --- src/duckstation-qt/qthostinterface.cpp | 14 +++++ src/duckstation-qt/qthostinterface.h | 1 + src/frontend-common/common_host_interface.cpp | 58 +++++++++++++++++++ src/frontend-common/common_host_interface.h | 12 ++++ 4 files changed, 85 insertions(+) diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index b1b85182d..244f4e80b 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -993,6 +993,9 @@ void QtHostInterface::populateSaveStateMenus(const char* game_code, QMenu* load_ loadState(path); }); + QAction* load_from_state = load_menu->addAction(tr("Undo Load State")); + load_from_state->setEnabled(CanUndoLoadState()); + connect(load_from_state, &QAction::triggered, this, &QtHostInterface::undoLoadState); load_menu->addSeparator(); connect(save_menu->addAction(tr("Save To File...")), &QAction::triggered, [this]() { @@ -1341,6 +1344,17 @@ void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done SaveState(global, slot); } +void QtHostInterface::undoLoadState() +{ + if (!isOnWorkerThread()) + { + QMetaObject::invokeMethod(this, "undoLoadState", Qt::QueuedConnection); + return; + } + + UndoLoadState(); +} + void QtHostInterface::setAudioOutputVolume(int volume, int fast_forward_volume) { if (!isOnWorkerThread()) diff --git a/src/duckstation-qt/qthostinterface.h b/src/duckstation-qt/qthostinterface.h index aa33ef6fa..4f375f3a8 100644 --- a/src/duckstation-qt/qthostinterface.h +++ b/src/duckstation-qt/qthostinterface.h @@ -165,6 +165,7 @@ public Q_SLOTS: void loadState(bool global, qint32 slot); void saveState(const QString& filename, bool block_until_done = false); void saveState(bool global, qint32 slot, bool block_until_done = false); + void undoLoadState(); void setAudioOutputVolume(int volume, int fast_forward_volume); void setAudioOutputMuted(bool muted); void startDumpingAudio(); diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 056b147c5..bec087387 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -215,6 +215,7 @@ bool CommonHostInterface::BootSystem(std::shared_ptr param void CommonHostInterface::DestroySystem() { + m_undo_load_state.reset(); SetTimerResolutionIncreased(false); m_save_state_selector_ui->Close(); m_display->SetPostProcessingChain({}); @@ -700,9 +701,57 @@ void CommonHostInterface::UpdateControllerInterface() } } +bool CommonHostInterface::UndoLoadState() +{ + if (!m_undo_load_state) + return false; + + Assert(System::IsValid()); + + m_undo_load_state->SeekAbsolute(0); + if (!System::LoadState(m_undo_load_state.get())) + { + ReportError("Failed to load undo state, resetting system."); + m_undo_load_state.reset(); + ResetSystem(); + return false; + } + + System::ResetPerformanceCounters(); + System::ResetThrottler(); + +#ifdef WITH_CHEEVOS + Cheevos::Reset(); +#endif + + Log_InfoPrintf("Loaded undo save state."); + m_undo_load_state.reset(); + return true; +} + +bool CommonHostInterface::SaveUndoLoadState() +{ + if (m_undo_load_state) + m_undo_load_state.reset(); + + m_undo_load_state = ByteStream_CreateGrowableMemoryStream(nullptr, System::MAX_SAVE_STATE_SIZE); + if (!System::SaveState(m_undo_load_state.get(), 0)) + { + AddOSDMessage(TranslateStdString("OSDMessage", "Failed to save undo load state."), 15.0f); + m_undo_load_state.reset(); + return false; + } + + Log_InfoPrintf("Saved undo load state: % " PRIu64 " bytes", m_undo_load_state->GetSize()); + return true; +} + bool CommonHostInterface::LoadState(const char* filename) { const bool system_was_valid = System::IsValid(); + if (system_was_valid) + SaveUndoLoadState(); + const bool result = HostInterface::LoadState(filename); if (system_was_valid || !result) { @@ -711,6 +760,9 @@ bool CommonHostInterface::LoadState(const char* filename) #endif } + if (!result && CanUndoLoadState()) + UndoLoadState(); + return result; } @@ -2320,6 +2372,12 @@ void CommonHostInterface::RegisterSaveStateHotkeys() m_save_state_selector_ui->SelectNextSlot(); }); + RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), StaticString("UndoLoadState"), + StaticString(TRANSLATABLE("Hotkeys", "Undo Load State")), [this](bool pressed) { + if (pressed) + UndoLoadState(); + }); + for (u32 slot = 1; slot <= PER_GAME_SAVE_STATE_SLOTS; slot++) { RegisterHotkey(StaticString(TRANSLATABLE("Hotkeys", "Save States")), diff --git a/src/frontend-common/common_host_interface.h b/src/frontend-common/common_host_interface.h index a7fc6c21d..3daf1864f 100644 --- a/src/frontend-common/common_host_interface.h +++ b/src/frontend-common/common_host_interface.h @@ -155,6 +155,9 @@ public: /// Returns true if the fullscreen UI is enabled. ALWAYS_INLINE bool IsFullscreenUIEnabled() const { return m_fullscreen_ui_enabled; } + /// Returns true if an undo load state exists. + ALWAYS_INLINE bool CanUndoLoadState() const { return static_cast(m_undo_load_state); } + /// Parses command line parameters for all frontends. bool ParseCommandLineParameters(int argc, char* argv[], std::unique_ptr* out_boot_params); @@ -176,6 +179,9 @@ public: /// Powers off the system, optionally saving the resume state. void PowerOffSystem(bool save_resume_state); + /// Undoes a load state, i.e. restores the state prior to the load. + bool UndoLoadState(); + /// Loads state from the specified filename. bool LoadState(const char* filename); @@ -352,6 +358,9 @@ protected: /// Executes per-frame tasks such as controller polling. virtual void PollAndUpdate(); + /// Saves the undo load state, so it can be restored. + bool SaveUndoLoadState(); + virtual std::unique_ptr CreateAudioStream(AudioBackend backend) override; virtual s32 GetAudioOutputVolume() const override; virtual void UpdateControllerInterface(); @@ -548,6 +557,9 @@ private: }; std::vector m_controller_autofires; + // temporary save state, created when loading, used to undo load state + std::unique_ptr m_undo_load_state; + #ifdef WITH_DISCORD_PRESENCE // discord rich presence bool m_discord_presence_enabled = false;