Memcard/Qt/Big Picture: Make shutdowns, resets, disc swaps, and savestates aware of memcard busy status

This commit is contained in:
RedPanda4552 2023-12-08 01:44:50 -05:00 committed by Connor McLaughlin
parent da22df5f5d
commit feb9d7b2a9
8 changed files with 207 additions and 12 deletions

View File

@ -46,6 +46,7 @@
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/Recording/InputRecording.h"
#include "pcsx2/Recording/InputRecordingControls.h"
#include "pcsx2/SIO/Sio.h"
#include "common/Assertions.h"
#include "common/CocoaTools.h"
@ -433,10 +434,10 @@ void MainWindow::connectVMThreadSignals(EmuThread* thread)
connect(thread, &EmuThread::onAchievementsHardcoreModeChanged, this, &MainWindow::onAchievementsHardcoreModeChanged);
connect(thread, &EmuThread::onCoverDownloaderOpenRequested, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(m_ui.actionReset, &QAction::triggered, thread, &EmuThread::resetVM);
connect(m_ui.actionReset, &QAction::triggered, this, &MainWindow::requestReset);
connect(m_ui.actionPause, &QAction::toggled, thread, &EmuThread::setVMPaused);
connect(m_ui.actionFullscreen, &QAction::triggered, thread, &EmuThread::toggleFullscreen);
connect(m_ui.actionToolbarReset, &QAction::triggered, thread, &EmuThread::resetVM);
connect(m_ui.actionToolbarReset, &QAction::triggered, this, &MainWindow::requestReset);
connect(m_ui.actionToolbarPause, &QAction::toggled, thread, &EmuThread::setVMPaused);
connect(m_ui.actionToolbarFullscreen, &QAction::triggered, thread, &EmuThread::toggleFullscreen);
connect(m_ui.actionToggleSoftwareRendering, &QAction::triggered, thread, &EmuThread::toggleSoftwareRendering);
@ -977,6 +978,25 @@ bool MainWindow::shouldHideMainWindow() const
QtHost::InNoGUIMode();
}
bool MainWindow::shouldAbortForMemcardBusy(const VMLock& lock)
{
if (MemcardBusy::IsBusy() && !GSDumpReplayer::IsReplayingDump())
{
const QMessageBox::StandardButton res = QMessageBox::question(
lock.getDialogParent(),
tr("WARNING: Memory Card Busy"),
tr("WARNING: Your memory card is still writing data. Shutting down now will IRREVERSIBLY DESTROY YOUR MEMORY CARD. It is strongly recommended to resume your game and let it finish writing to your memory card.\n\nDo you wish to shutdown anyways and IRREVERSIBLY DESTROY YOUR MEMORY CARD?")
);
if (res != QMessageBox::Yes)
{
return true;
}
}
return false;
}
void MainWindow::switchToGameListView()
{
if (isShowingGameList())
@ -1044,6 +1064,22 @@ void MainWindow::runOnUIThread(const std::function<void()>& func)
func();
}
void MainWindow::requestReset()
{
if (!s_vm_valid)
return;
const auto lock = pauseAndLockVM();
// Check if memcard is busy, deny request if so
if (shouldAbortForMemcardBusy(lock))
{
return;
}
g_emu_thread->resetVM();
}
bool MainWindow::requestShutdown(bool allow_confirm, bool allow_save_to_state, bool default_save_to_state)
{
if (!s_vm_valid)
@ -1052,12 +1088,17 @@ bool MainWindow::requestShutdown(bool allow_confirm, bool allow_save_to_state, b
// If we don't have a crc, we can't save state.
allow_save_to_state &= (m_current_disc_crc != 0);
bool save_state = allow_save_to_state && default_save_to_state;
VMLock lock(pauseAndLockVM());
// Check if memcard is busy, deny request if so.
if (shouldAbortForMemcardBusy(lock))
{
return false;
}
// Only confirm on UI thread because we need to display a msgbox.
if (!m_is_closing && allow_confirm && !GSDumpReplayer::IsReplayingDump() && Host::GetBoolSettingValue("UI", "ConfirmShutdown", true))
{
VMLock lock(pauseAndLockVM());
QMessageBox msgbox(lock.getDialogParent());
msgbox.setIcon(QMessageBox::Question);
msgbox.setWindowTitle(tr("Confirm Shutdown"));
@ -1899,6 +1940,14 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event)
void MainWindow::dropEvent(QDropEvent* event)
{
const auto mcLock = pauseAndLockVM();
// Check if memcard is busy, deny request if so
if (shouldAbortForMemcardBusy(mcLock))
{
return;
}
const QString filename(getFilenameFromMimeData(event->mimeData()));
const std::string filename_str(filename.toStdString());
if (VMManager::IsSaveStateFileName(filename_str))
@ -2730,6 +2779,12 @@ void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path)
{
const auto lock = pauseAndLockVM();
// Check if memcard is busy, deny request if so
if (shouldAbortForMemcardBusy(lock))
{
return;
}
bool reset_system = false;
if (!m_was_disc_change_request)
{

View File

@ -119,6 +119,7 @@ public Q_SLOTS:
void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message);
void runOnUIThread(const std::function<void()>& func);
void requestReset();
bool requestShutdown(bool allow_confirm = true, bool allow_save_to_state = true, bool default_save_to_state = true);
void requestExit(bool allow_confirm = true);
void checkForSettingChanges();
@ -243,6 +244,8 @@ private:
void switchToGameListView();
void switchToEmulationView();
bool shouldAbortForMemcardBusy(const VMLock& lock);
QWidget* getContentParent();
QWidget* getDisplayContainer() const;
void saveDisplayWindowGeometryToConfig();

View File

@ -517,6 +517,9 @@ static __fi void VSyncStart(u32 sCycle)
// Memcard auto ejection - Uses a tick system timed off of real time, decrementing one tick per frame.
AutoEject::CountDownTicks();
// Memcard IO detection - Uses a tick system to determine when memcards are no longer being written.
MemcardBusy::Decrement();
if (gates)
rcntStartGate(true, sCycle); // Counters Start Gate code

View File

@ -274,13 +274,19 @@ namespace FullscreenUI
static void DoStartDisc();
static void DoToggleFrameLimit();
static void DoToggleSoftwareRenderer();
static void RequestShutdown(bool save_state);
static void DoShutdown(bool save_state);
static void RequestReset();
static void DoReset();
static void RequestChangeDiscFromFile();
static void DoChangeDiscFromFile();
static void RequestChangeDisc();
static void DoChangeDisc();
static void DoRequestExit();
static void DoToggleFullscreen();
static void ConfirmShutdownIfMemcardBusy(std::function<void(bool)> callback);
//////////////////////////////////////////////////////////////////////////
// Settings
//////////////////////////////////////////////////////////////////////////
@ -958,11 +964,31 @@ void FullscreenUI::DoToggleSoftwareRenderer()
});
}
void FullscreenUI::RequestShutdown(bool save_state)
{
ConfirmShutdownIfMemcardBusy([save_state](bool result) {
if (result)
DoShutdown(save_state);
ClosePauseMenu();
});
}
void FullscreenUI::DoShutdown(bool save_state)
{
Host::RunOnCPUThread([save_state]() { Host::RequestVMShutdown(false, save_state, save_state); });
}
void FullscreenUI::RequestReset()
{
ConfirmShutdownIfMemcardBusy([](bool result) {
if (result)
DoReset();
ClosePauseMenu();
});
}
void FullscreenUI::DoReset()
{
Host::RunOnCPUThread([]() {
@ -973,6 +999,16 @@ void FullscreenUI::DoReset()
});
}
void FullscreenUI::RequestChangeDiscFromFile()
{
ConfirmShutdownIfMemcardBusy([](bool result) {
if (result)
DoChangeDiscFromFile();
else
ClosePauseMenu();
});
}
void FullscreenUI::DoChangeDiscFromFile()
{
auto callback = [](const std::string& path) {
@ -991,12 +1027,23 @@ void FullscreenUI::DoChangeDiscFromFile()
QueueResetFocus();
CloseFileSelector();
ReturnToPreviousWindow();
ClosePauseMenu();
};
OpenFileSelector(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Select Disc Image"), false, std::move(callback), GetDiscImageFilters(),
std::string(Path::GetDirectory(s_current_disc_path)));
}
void FullscreenUI::RequestChangeDisc()
{
ConfirmShutdownIfMemcardBusy([](bool result) {
if (result)
DoChangeDisc();
else
ClosePauseMenu();
});
}
void FullscreenUI::DoChangeDisc()
{
DoChangeDiscFromFile();
@ -1012,6 +1059,20 @@ void FullscreenUI::DoToggleFullscreen()
Host::RunOnCPUThread([]() { Host::SetFullscreen(!Host::IsFullscreen()); });
}
void FullscreenUI::ConfirmShutdownIfMemcardBusy(std::function<void(bool)> callback)
{
if (!MemcardBusy::IsBusy())
{
callback(true);
return;
}
OpenConfirmMessageDialog(FSUI_ICONSTR(ICON_FA_SD_CARD, "WARNING: Memory Card Busy"),
FSUI_STR("WARNING: Your memory card is still writing data. Shutting down now will IRREVERSIBLY DESTROY YOUR MEMORY CARD. It is strongly recommended to resume your game and let it finish writing to your memory card.\n\nDo you wish to shutdown anyways and IRREVERSIBLY DESTROY YOUR MEMORY CARD?"),
std::move(callback)
);
}
//////////////////////////////////////////////////////////////////////////
// Landing Window
//////////////////////////////////////////////////////////////////////////
@ -4807,7 +4868,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
if (ActiveButton(FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Change Disc"), false))
{
s_current_main_window = MainWindowType::None;
DoChangeDisc();
RequestChangeDisc();
}
if (ActiveButton(FSUI_ICONSTR(ICON_FA_SLIDERS_H, "Settings"), false))
@ -4817,7 +4878,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
{
// skip submenu when we can't save anyway
if (!can_load_or_save_state)
DoShutdown(false);
RequestShutdown(false);
else
OpenPauseSubMenu(PauseSubMenu::Exit);
}
@ -4836,15 +4897,14 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
if (ActiveButton(FSUI_ICONSTR(ICON_FA_SYNC, "Reset System"), false))
{
ClosePauseMenu();
DoReset();
RequestReset();
}
if (ActiveButton(FSUI_ICONSTR(ICON_FA_SAVE, "Exit And Save State"), false))
DoShutdown(true);
RequestShutdown(true);
if (ActiveButton(FSUI_ICONSTR(ICON_FA_POWER_OFF, "Exit Without Saving"), false))
DoShutdown(false);
RequestShutdown(false);
}
break;
@ -6369,7 +6429,7 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
return;
if (reset)
DoReset();
RequestReset();
});
}
}

View File

@ -234,6 +234,8 @@ void MemoryCardProtocol::WriteData()
g_Sio2FifoOut.push_back(mcd->term);
ReadWriteIncrement(writeLength);
MemcardBusy::SetBusy();
}
void MemoryCardProtocol::ReadData()
@ -396,8 +398,9 @@ u8 MemoryCardProtocol::PS1Write(u8 data)
}
g_Sio0.SetAcknowledge(sendAck);
ps1McState.currentByte++;
MemcardBusy::SetBusy();
return ret;
}
@ -421,6 +424,8 @@ void MemoryCardProtocol::EraseBlock()
PS1_FAIL();
mcd->EraseBlock();
The2bTerminator(4);
MemcardBusy::SetBusy();
}
void MemoryCardProtocol::UnknownBoot()

View File

@ -134,3 +134,31 @@ void AutoEject::ClearAll()
}
}
}
// Decremented once per frame if nonzero, indicates how many more frames must pass before
// memcards are considered "no longer being written to". Used as a way to detect if it is
// unsafe to shutdown the VM due to memcard access.
static std::atomic_uint32_t currentBusyTicks = 0;
void MemcardBusy::Decrement()
{
if (currentBusyTicks.load(std::memory_order_relaxed) == 0)
return;
currentBusyTicks.fetch_sub(1, std::memory_order_release);
}
void MemcardBusy::SetBusy()
{
currentBusyTicks.store(300, std::memory_order_release);
}
bool MemcardBusy::IsBusy()
{
return (currentBusyTicks.load(std::memory_order_acquire) > 0);
}
void MemcardBusy::ClearBusy()
{
currentBusyTicks.store(0, std::memory_order_release);
}

View File

@ -129,3 +129,11 @@ namespace AutoEject
extern void SetAll();
extern void ClearAll();
} // namespace AutoEject
namespace MemcardBusy
{
extern void Decrement();
extern void SetBusy();
extern bool IsBusy();
extern void ClearBusy();
}

View File

@ -1421,6 +1421,7 @@ void VMManager::Shutdown(bool save_resume_state)
Pad::Shutdown();
g_Sio2.Shutdown();
g_Sio0.Shutdown();
MemcardBusy::ClearBusy();
DEV9close();
DoCDVDclose();
FWclose();
@ -1744,6 +1745,14 @@ bool VMManager::LoadState(const char* filename)
return false;
}
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to load state (Memory card is busy)")),
Host::OSD_QUICK_DURATION);
return false;
}
// TODO: Save the current state so we don't need to reset.
if (DoLoadState(filename))
return true;
@ -1773,6 +1782,14 @@ bool VMManager::LoadStateFromSlot(s32 slot)
return false;
}
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to load state from slot {} (Memory card is busy)"), slot),
Host::OSD_QUICK_DURATION);
return false;
}
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN,
fmt::format(TRANSLATE_FS("VMManager", "Loading state from slot {}..."), slot), Host::OSD_QUICK_DURATION);
return DoLoadState(filename.c_str());
@ -1780,6 +1797,14 @@ bool VMManager::LoadStateFromSlot(s32 slot)
bool VMManager::SaveState(const char* filename, bool zip_on_thread, bool backup_old_state)
{
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state (Memory card is busy)")),
Host::OSD_QUICK_DURATION);
return false;
}
return DoSaveState(filename, -1, zip_on_thread, backup_old_state);
}
@ -1789,6 +1814,14 @@ bool VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread)
if (filename.empty())
return false;
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_EXCLAMATION_TRIANGLE,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state to slot {} (Memory card is busy)"), slot),
Host::OSD_QUICK_DURATION);
return false;
}
// if it takes more than a minute.. well.. wtf.
Host::AddIconOSDMessage(fmt::format("SaveStateSlot{}", slot), ICON_FA_SAVE,
fmt::format(TRANSLATE_FS("VMManager", "Saving state to slot {}..."), slot), 60.0f);