SaveState: Rework error handling when saving states

This commit is contained in:
chaoticgd 2025-11-30 22:24:00 +00:00
parent bfdb435573
commit afe6dac472
No known key found for this signature in database
GPG Key ID: FFFC3F38B3A0E6D8
13 changed files with 194 additions and 65 deletions

View File

@ -1266,6 +1266,51 @@ void MainWindow::reportStateLoadError(const QString& message, std::optional<s32>
delete message_box; delete message_box;
} }
void MainWindow::reportStateSaveError(const QString& message, std::optional<s32> slot)
{
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error)
{
SaveState_ReportSaveErrorOSD(message.toStdString(), slot);
return;
}
QString title;
if (slot.has_value())
title = tr("Failed to Save State To Slot %1").arg(*slot);
else
title = tr("Failed to Save State");
VMLock lock(pauseAndLockVM());
QCheckBox* do_not_show_again = new QCheckBox(tr("Do not show again"));
QPointer<QMessageBox> message_box = new QMessageBox(this);
message_box->setWindowTitle(title);
message_box->setText(message);
message_box->setIcon(QMessageBox::Critical);
message_box->addButton(QMessageBox::Ok);
message_box->setDefaultButton(QMessageBox::Ok);
message_box->setCheckBox(do_not_show_again);
message_box->exec();
if (message_box.isNull())
return;
if (do_not_show_again->isChecked())
{
Host::SetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", false);
Host::CommitBaseSettingChanges();
if (m_settings_window)
{
InterfaceSettingsWidget* interface_settings = m_settings_window->getInterfaceSettingsWidget();
interface_settings->updatePromptOnStateLoadSaveFailureCheckbox(Qt::Unchecked);
}
}
delete message_box;
}
void MainWindow::runOnUIThread(const std::function<void()>& func) void MainWindow::runOnUIThread(const std::function<void()>& func)
{ {
func(); func();

View File

@ -121,6 +121,7 @@ public Q_SLOTS:
bool confirmMessage(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message); void onStatusMessage(const QString& message);
void reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup); void reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup);
void reportStateSaveError(const QString& message, std::optional<s32> slot);
void runOnUIThread(const std::function<void()>& func); void runOnUIThread(const std::function<void()>& func);
void requestReset(); void requestReset();

View File

@ -337,11 +337,11 @@ void EmuThread::saveState(const QString& filename)
if (!VMManager::HasValidVM()) if (!VMManager::HasValidVM())
return; return;
if (!VMManager::SaveState(filename.toUtf8().constData())) VMManager::SaveState(filename.toUtf8().constData(), true, false, [](const std::string& error) {
{ QtHost::RunOnUIThread([message = QString::fromStdString(error)]() {
// this one is usually the result of a user-chosen path, so we can display a message box safely here g_main_window->reportStateSaveError(message, std::nullopt);
Console.Error("Failed to save state"); });
} });
} }
void EmuThread::saveStateToSlot(qint32 slot) void EmuThread::saveStateToSlot(qint32 slot)
@ -355,7 +355,11 @@ void EmuThread::saveStateToSlot(qint32 slot)
if (!VMManager::HasValidVM()) if (!VMManager::HasValidVM())
return; return;
VMManager::SaveStateToSlot(slot); VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
QtHost::RunOnUIThread([message = QString::fromStdString(error), slot]() {
g_main_window->reportStateSaveError(message, slot);
});
});
} }
void EmuThread::run() void EmuThread::run()

View File

@ -112,7 +112,9 @@ static void HotkeyLoadStateSlot(s32 slot)
static void HotkeySaveStateSlot(s32 slot) static void HotkeySaveStateSlot(s32 slot)
{ {
VMManager::SaveStateToSlot(slot); VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
FullscreenUI::ReportStateSaveError(error, slot);
});
} }
static bool CanPause() static bool CanPause()

View File

@ -671,6 +671,7 @@ namespace FullscreenUI
static bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry); static bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry);
static void DrawResumeStateSelector(); static void DrawResumeStateSelector();
static void DoLoadState(std::string path, std::optional<s32> slot, bool backup); static void DoLoadState(std::string path, std::optional<s32> slot, bool backup);
static void DoSaveState(s32 slot);
static std::vector<SaveStateListEntry> s_save_state_selector_slots; static std::vector<SaveStateListEntry> s_save_state_selector_slots;
static std::string s_save_state_selector_game_path; static std::string s_save_state_selector_game_path;
@ -7347,7 +7348,7 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
if (is_loading) if (is_loading)
DoLoadState(std::move(entry.path), entry.slot, false); DoLoadState(std::move(entry.path), entry.slot, false);
else else
Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); }); DoSaveState(entry.slot);
CloseSaveStateSelector(); CloseSaveStateSelector();
ReturnToMainWindow(); ReturnToMainWindow();
@ -7485,7 +7486,7 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
if (is_loading) if (is_loading)
DoLoadState(entry.path, entry.slot, false); DoLoadState(entry.path, entry.slot, false);
else else
Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); }); DoSaveState(entry.slot);
CloseSaveStateSelector(); CloseSaveStateSelector();
ReturnToMainWindow(); ReturnToMainWindow();
@ -7665,6 +7666,15 @@ void FullscreenUI::DoLoadState(std::string path, std::optional<s32> slot, bool b
}); });
} }
void FullscreenUI::DoSaveState(s32 slot)
{
Host::RunOnCPUThread([slot]() {
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
ReportStateSaveError(error, slot);
});
});
}
void FullscreenUI::PopulateGameListEntryList() void FullscreenUI::PopulateGameListEntryList()
{ {
const int sort = Host::GetBaseIntSettingValue("UI", "FullscreenUIGameSort", 0); const int sort = Host::GetBaseIntSettingValue("UI", "FullscreenUIGameSort", 0);
@ -9166,9 +9176,9 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
EndMenuButtons(); EndMenuButtons();
} }
void FullscreenUI::ReportStateLoadError(std::string message, std::optional<s32> slot, bool backup) void FullscreenUI::ReportStateLoadError(const std::string& message, std::optional<s32> slot, bool backup)
{ {
MTGS::RunOnGSThread([message = std::move(message), slot, backup]() { MTGS::RunOnGSThread([message, slot, backup]() {
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true); const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error || !ImGuiManager::InitializeFullscreenUI()) if (!prompt_on_error || !ImGuiManager::InitializeFullscreenUI())
{ {
@ -9204,6 +9214,37 @@ void FullscreenUI::ReportStateLoadError(std::string message, std::optional<s32>
}); });
} }
void FullscreenUI::ReportStateSaveError(const std::string& message, std::optional<s32> slot)
{
MTGS::RunOnGSThread([message, slot]() {
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error || !ImGuiManager::InitializeFullscreenUI())
{
SaveState_ReportSaveErrorOSD(message, slot);
return;
}
std::string title;
if (slot.has_value())
title = fmt::format(FSUI_FSTR("Failed to Save State To Slot {}"), *slot);
else
title = FSUI_STR("Failed to Save State");
ImGuiFullscreen::InfoMessageDialogCallback callback;
if (VMManager::GetState() == VMState::Running)
{
Host::RunOnCPUThread([]() { VMManager::SetPaused(true); });
callback = []() {
Host::RunOnCPUThread([]() { VMManager::SetPaused(false); });
};
}
ImGuiFullscreen::OpenInfoMessageDialog(
fmt::format("{} {}", ICON_FA_TRIANGLE_EXCLAMATION, title),
std::move(message), std::move(callback));
});
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Translation String Area // Translation String Area
// To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top // To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top

View File

@ -27,7 +27,8 @@ namespace FullscreenUI
void OpenPauseMenu(); void OpenPauseMenu();
bool OpenAchievementsWindow(); bool OpenAchievementsWindow();
bool OpenLeaderboardsWindow(); bool OpenLeaderboardsWindow();
void ReportStateLoadError(std::string message, std::optional<s32> slot, bool backup); void ReportStateLoadError(const std::string& message, std::optional<s32> slot, bool backup);
void ReportStateSaveError(const std::string& message, std::optional<s32> slot);
// NOTE: Only call from GS thread. // NOTE: Only call from GS thread.
bool IsAchievementsWindowOpen(); bool IsAchievementsWindowOpen();

View File

@ -1400,7 +1400,9 @@ void SaveStateSelectorUI::LoadCurrentBackupSlot()
void SaveStateSelectorUI::SaveCurrentSlot() void SaveStateSelectorUI::SaveCurrentSlot()
{ {
Host::RunOnCPUThread([slot = GetCurrentSlot()]() { Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
VMManager::SaveStateToSlot(slot); VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
FullscreenUI::ReportStateSaveError(error, slot);
});
}); });
Close(); Close();
} }

View File

@ -646,7 +646,11 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
goto error; goto error;
if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]] if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]]
goto error; goto error;
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] { VMManager::SaveStateToSlot(slot); }); Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] {
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
SaveState_ReportSaveErrorOSD(error, slot);
});
});
buf_cnt += 1; buf_cnt += 1;
break; break;
} }

View File

@ -56,8 +56,9 @@ bool InputRecording::create(const std::string& fileName, const bool fromSaveStat
m_initial_load_complete = true; m_initial_load_complete = true;
m_watching_for_rerecords = true; m_watching_for_rerecords = true;
setStartingFrame(g_FrameCount); setStartingFrame(g_FrameCount);
// TODO - error handling VMManager::SaveState(savestatePath.c_str(), true, false, [](const std::string& error) {
VMManager::SaveState(savestatePath.c_str()); SaveState_ReportSaveErrorOSD(error, std::nullopt);
});
} }
else else
{ {
@ -395,8 +396,7 @@ void InputRecording::InformGSThread()
TinyString frame_data_message = TinyString::from_format(TRANSLATE_FS("InputRecording", "Frame: {}/{} ({})"), g_InputRecording.getFrameCounter(), g_InputRecording.getData().getTotalFrames(), g_InputRecording.getFrameCounterStateless()); TinyString frame_data_message = TinyString::from_format(TRANSLATE_FS("InputRecording", "Frame: {}/{} ({})"), g_InputRecording.getFrameCounter(), g_InputRecording.getData().getTotalFrames(), g_InputRecording.getFrameCounterStateless());
TinyString undo_count_message = TinyString::from_format(TRANSLATE_FS("InputRecording", "Undo Count: {}"), g_InputRecording.getData().getUndoCount()); TinyString undo_count_message = TinyString::from_format(TRANSLATE_FS("InputRecording", "Undo Count: {}"), g_InputRecording.getData().getUndoCount());
MTGS::RunOnGSThread([recording_active_message, frame_data_message, undo_count_message](bool is_recording = g_InputRecording.getControls().isRecording()) MTGS::RunOnGSThread([recording_active_message, frame_data_message, undo_count_message](bool is_recording = g_InputRecording.getControls().isRecording()) {
{
g_InputRecordingData.is_recording = is_recording; g_InputRecordingData.is_recording = is_recording;
g_InputRecordingData.recording_active_message = recording_active_message; g_InputRecordingData.recording_active_message = recording_active_message;
g_InputRecordingData.frame_data_message = frame_data_message; g_InputRecordingData.frame_data_message = frame_data_message;

View File

@ -1253,3 +1253,16 @@ void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32>
Host::AddIconOSDMessage("LoadState", ICON_FA_TRIANGLE_EXCLAMATION, Host::AddIconOSDMessage("LoadState", ICON_FA_TRIANGLE_EXCLAMATION,
full_message, Host::OSD_WARNING_DURATION); full_message, Host::OSD_WARNING_DURATION);
} }
void SaveState_ReportSaveErrorOSD(const std::string& message, std::optional<s32> slot)
{
std::string full_message;
if (slot.has_value())
full_message = fmt::format(
TRANSLATE_FS("SaveState", "Failed to save state to slot {}: {}"), *slot, message);
else
full_message = fmt::format(TRANSLATE_FS("SaveState", "Failed to save state: {}"), message);
Host::AddIconOSDMessage("SaveState", ICON_FA_TRIANGLE_EXCLAMATION,
full_message, Host::OSD_WARNING_DURATION);
}

View File

@ -356,3 +356,4 @@ public:
}; };
void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32> slot, bool backup); void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32> slot, bool backup);
void SaveState_ReportSaveErrorOSD(const std::string& message, std::optional<s32> slot);

View File

@ -115,13 +115,13 @@ namespace VMManager
static std::string GetCurrentSaveStateFileName(s32 slot, bool backup = false); static std::string GetCurrentSaveStateFileName(s32 slot, bool backup = false);
static bool DoLoadState(const char* filename, Error* error = nullptr); static bool DoLoadState(const char* filename, Error* error = nullptr);
static bool DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state); static void DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state, std::function<void(const std::string&)> error_callback);
static void ZipSaveState(std::unique_ptr<ArchiveEntryList> elist, static void ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, const char* filename, std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename,
s32 slot_for_message); s32 slot_for_message, std::function<void(const std::string&)> error_callback);
static void ZipSaveStateOnThread(std::unique_ptr<ArchiveEntryList> elist, static void ZipSaveStateOnThread(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, std::string filename, std::unique_ptr<SaveStateScreenshotData> screenshot, std::string filename,
s32 slot_for_message); s32 slot_for_message, std::function<void(const std::string&)> error_callback);
static void LoadSettings(); static void LoadSettings();
static void LoadCoreSettings(SettingsInterface& si); static void LoadCoreSettings(SettingsInterface& si);
@ -1617,8 +1617,13 @@ void VMManager::Shutdown(bool save_resume_state)
if (!GSDumpReplayer::IsReplayingDump() && save_resume_state) if (!GSDumpReplayer::IsReplayingDump() && save_resume_state)
{ {
std::string resume_file_name(GetCurrentSaveStateFileName(-1)); std::string resume_file_name(GetCurrentSaveStateFileName(-1));
if (!resume_file_name.empty() && !DoSaveState(resume_file_name.c_str(), -1, true, false)) if (!resume_file_name.empty())
Console.Error("Failed to save resume state"); {
DoSaveState(resume_file_name.c_str(), -1, true, false, [](const std::string& error) {
Host::AddIconOSDMessage("SaveResumeState", ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save resume state: {}"), error), Host::OSD_QUICK_DURATION);
});
}
} }
// end input recording before clearing state // end input recording before clearing state
@ -1829,7 +1834,7 @@ bool VMManager::DoLoadState(const char* filename, Error* error)
{ {
if (GSDumpReplayer::IsReplayingDump()) if (GSDumpReplayer::IsReplayingDump())
{ {
Error::SetString(error, TRANSLATE_STR("VMManager", "Cannot load save state while replaying GS dump.")); Error::SetString(error, TRANSLATE_STR("VMManager", "Cannot load state while replaying GS dump."));
return false; return false;
} }
@ -1849,21 +1854,21 @@ bool VMManager::DoLoadState(const char* filename, Error* error)
return true; return true;
} }
bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state) void VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state, std::function<void(const std::string&)> error_callback)
{ {
if (GSDumpReplayer::IsReplayingDump()) if (GSDumpReplayer::IsReplayingDump())
return false; {
error_callback(TRANSLATE_STR("VMManager", "Cannot save state while replaying GS dump."));
return;
}
std::string osd_key(fmt::format("SaveStateSlot{}", slot_for_message));
Error error; Error error;
std::unique_ptr<ArchiveEntryList> elist = SaveState_DownloadState(&error); std::unique_ptr<ArchiveEntryList> elist = SaveState_DownloadState(&error);
if (!elist) if (!elist)
{ {
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_TRIANGLE_EXCLAMATION, error_callback(fmt::format(
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state: {}."), error.GetDescription()), TRANSLATE_FS("VMManager", "Failed to save state: {}."), error.GetDescription()));
Host::OSD_ERROR_DURATION); return;
return false;
} }
std::unique_ptr<SaveStateScreenshotData> screenshot = SaveState_SaveScreenshot(); std::unique_ptr<SaveStateScreenshotData> screenshot = SaveState_SaveScreenshot();
@ -1874,10 +1879,10 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip
Console.WriteLn(fmt::format("Creating save state backup {}...", backup_filename)); Console.WriteLn(fmt::format("Creating save state backup {}...", backup_filename));
if (!FileSystem::RenamePath(filename, backup_filename.c_str())) if (!FileSystem::RenamePath(filename, backup_filename.c_str()))
{ {
Host::AddIconOSDMessage(osd_key, ICON_FA_TRIANGLE_EXCLAMATION, error_callback(fmt::format(
fmt::format( TRANSLATE_FS("VMManager", "Cannot back up old save state '{}'."),
TRANSLATE_FS("VMManager", "Failed to back up old save state {}."), Path::GetFileName(filename)), Path::GetFileName(filename)));
Host::OSD_ERROR_DURATION); return;
} }
} }
@ -1886,21 +1891,22 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip
// lock order here is important; the thread could exit before we resume here. // lock order here is important; the thread could exit before we resume here.
std::unique_lock lock(s_save_state_threads_mutex); std::unique_lock lock(s_save_state_threads_mutex);
s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, std::move(elist), std::move(screenshot), s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, std::move(elist), std::move(screenshot),
std::move(osd_key), std::string(filename), slot_for_message); std::string(filename), slot_for_message, std::move(error_callback));
} }
else else
{ {
ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename, slot_for_message); ZipSaveState(
std::move(elist), std::move(screenshot), filename, slot_for_message, std::move(error_callback));
} }
Host::OnSaveStateSaved(filename); Host::OnSaveStateSaved(filename);
MemcardBusy::CheckSaveStateDependency(); MemcardBusy::CheckSaveStateDependency();
return true; return;
} }
void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist, void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, const char* filename, std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename,
s32 slot_for_message) s32 slot_for_message, std::function<void(const std::string&)> error_callback)
{ {
Common::Timer timer; Common::Timer timer;
@ -1908,26 +1914,26 @@ void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
{ {
if (slot_for_message >= 0 && VMManager::HasValidVM()) if (slot_for_message >= 0 && VMManager::HasValidVM())
{ {
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_FLOPPY_DISK, Host::AddIconOSDMessage("SaveState", ICON_FA_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("VMManager", "State saved to slot {}."), slot_for_message), fmt::format(TRANSLATE_FS("VMManager", "State saved to slot {}."), slot_for_message),
Host::OSD_QUICK_DURATION); Host::OSD_QUICK_DURATION);
} }
} }
else else
{ {
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_TRIANGLE_EXCLAMATION, error_callback(fmt::format(
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state to slot {}."), slot_for_message, TRANSLATE_FS("VMManager", "Failed to save state to slot {}."), slot_for_message));
Host::OSD_ERROR_DURATION));
} }
DevCon.WriteLn("Zipping save state to '%s' took %.2f ms", filename, timer.GetTimeMilliseconds()); DevCon.WriteLn("Zipping save state to '%s' took %.2f ms", filename, timer.GetTimeMilliseconds());
} }
void VMManager::ZipSaveStateOnThread(std::unique_ptr<ArchiveEntryList> elist, void VMManager::ZipSaveStateOnThread(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, std::string filename, std::unique_ptr<SaveStateScreenshotData> screenshot, std::string filename,
s32 slot_for_message) s32 slot_for_message, std::function<void(const std::string&)> error_callback)
{ {
ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename.c_str(), slot_for_message); ZipSaveState(
std::move(elist), std::move(screenshot), filename.c_str(), slot_for_message, std::move(error_callback));
// remove ourselves from the thread list. if we're joining, we might not be in there. // remove ourselves from the thread list. if we're joining, we might not be in there.
const auto this_id = std::this_thread::get_id(); const auto this_id = std::this_thread::get_id();
@ -2046,37 +2052,45 @@ bool VMManager::LoadStateFromSlot(s32 slot, bool backup, Error* error)
return DoLoadState(filename.c_str(), error); return DoLoadState(filename.c_str(), error);
} }
bool VMManager::SaveState(const char* filename, bool zip_on_thread, bool backup_old_state) void VMManager::SaveState(
const char* filename, bool zip_on_thread, bool backup_old_state, std::function<void(const std::string&)> error_callback)
{ {
if (MemcardBusy::IsBusy()) if (MemcardBusy::IsBusy())
{ {
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION, error_callback(TRANSLATE_STR("VMManager", "Failed to save state (memory card is busy)."));
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state (Memory card is busy)")), return;
Host::OSD_QUICK_DURATION);
return false;
} }
return DoSaveState(filename, -1, zip_on_thread, backup_old_state); DoSaveState(filename, -1, zip_on_thread, backup_old_state, std::move(error_callback));
} }
bool VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread) void VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread, std::function<void(const std::string&)> error_callback)
{ {
const std::string filename(GetCurrentSaveStateFileName(slot)); const std::string filename(GetCurrentSaveStateFileName(slot));
if (filename.empty()) if (filename.empty())
return false; {
error_callback(TRANSLATE_STR("VMManager", "Failed to generate filename for save state."));
return;
}
if (MemcardBusy::IsBusy()) if (MemcardBusy::IsBusy())
{ {
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION, error_callback(fmt::format(
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state to slot {} (Memory card is busy)"), slot), TRANSLATE_FS("VMManager", "Failed to save state to slot {} (Memory card is busy)"), slot));
Host::OSD_QUICK_DURATION); return;
return false;
} }
// if it takes more than a minute.. well.. wtf. // if it takes more than a minute.. well.. wtf.
Host::AddIconOSDMessage(fmt::format("SaveStateSlot{}", slot), ICON_FA_FLOPPY_DISK, Host::AddIconOSDMessage(fmt::format("SaveStateSlot{}", slot), ICON_FA_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("VMManager", "Saving state to slot {}..."), slot), 60.0f); fmt::format(TRANSLATE_FS("VMManager", "Saving state to slot {}..."), slot), 60.0f);
return DoSaveState(filename.c_str(), slot, zip_on_thread, EmuConfig.BackupSavestate);
auto callback = [error_callback = std::move(error_callback), slot](const std::string& error) {
Host::RemoveKeyedOSDMessage(fmt::format("SaveStateSlot{}", slot));
error_callback(error);
};
return DoSaveState(
filename.c_str(), slot, zip_on_thread, EmuConfig.BackupSavestate, std::move(callback));
} }
LimiterModeType VMManager::GetLimiterMode() LimiterModeType VMManager::GetLimiterMode()

View File

@ -165,10 +165,11 @@ namespace VMManager
bool LoadStateFromSlot(s32 slot, bool backup = false, Error* error = nullptr); bool LoadStateFromSlot(s32 slot, bool backup = false, Error* error = nullptr);
/// Saves state to the specified filename. /// Saves state to the specified filename.
bool SaveState(const char* filename, bool zip_on_thread = true, bool backup_old_state = false); void SaveState(const char* filename, bool zip_on_thread, bool backup_old_state,
std::function<void(const std::string&)> error_callback);
/// Saves state to the specified slot. /// Saves state to the specified slot.
bool SaveStateToSlot(s32 slot, bool zip_on_thread = true); void SaveStateToSlot(s32 slot, bool zip_on_thread, std::function<void(const std::string&)> error_callback);
/// Waits until all compressing save states have finished saving to disk. /// Waits until all compressing save states have finished saving to disk.
void WaitForSaveStateFlush(); void WaitForSaveStateFlush();