CDVD: Synchronise access from different threads
Some checks failed
🐧 Linux Builds / AppImage (push) Has been cancelled
🐧 Linux Builds / Flatpak (push) Has been cancelled
🍎 MacOS Builds / Defaults (push) Has been cancelled
🖥️ Windows Builds / Lint VS Project Files (push) Has been cancelled
🖥️ Windows Builds / CMake (push) Has been cancelled
🖥️ Windows Builds / SSE4 (push) Has been cancelled
🖥️ Windows Builds / AVX2 (push) Has been cancelled
🏭 Update Controller Database / update-controller-db (push) Has been cancelled

This commit is contained in:
chaoticgd 2025-11-06 14:36:16 +00:00 committed by Ty
parent d02f30ee62
commit a98cfcf28c
16 changed files with 129 additions and 57 deletions

View File

@ -4,14 +4,17 @@
#include "GameListRefreshThread.h"
#include "pcsx2/GameList.h"
#include "pcsx2/Host.h"
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/ProgressCallback.h"
#include "common/Timer.h"
#include <QtWidgets/QMessageBox>
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent)
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(bool popup_on_error, GameListRefreshThread* parent)
: m_parent(parent)
, m_popup_on_error(popup_on_error)
{
}
@ -57,17 +60,20 @@ void AsyncRefreshProgressCallback::SetTitle(const char* title)
void AsyncRefreshProgressCallback::DisplayError(const char* message)
{
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
if (m_popup_on_error)
Host::ReportErrorAsync(TRANSLATE_SV("GameListRefreshThread", "Error"), message);
else
ERROR_LOG("{}", message);
}
void AsyncRefreshProgressCallback::DisplayWarning(const char* message)
{
QMessageBox::warning(nullptr, QStringLiteral("Warning"), QString::fromUtf8(message));
pxFailRel("Not implemented.");
}
void AsyncRefreshProgressCallback::DisplayInformation(const char* message)
{
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
pxFailRel("Not implemented.");
}
void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
@ -77,17 +83,18 @@ void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
void AsyncRefreshProgressCallback::ModalError(const char* message)
{
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
pxFailRel("Not implemented.");
}
bool AsyncRefreshProgressCallback::ModalConfirmation(const char* message)
{
return QMessageBox::question(nullptr, QStringLiteral("Question"), QString::fromUtf8(message)) == QMessageBox::Yes;
pxFailRel("Not implemented.");
return false;
}
void AsyncRefreshProgressCallback::ModalInformation(const char* message)
{
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
pxFailRel("Not implemented.");
}
void AsyncRefreshProgressCallback::fireUpdate()
@ -95,9 +102,9 @@ void AsyncRefreshProgressCallback::fireUpdate()
m_parent->refreshProgress(m_status_text, m_last_value, m_last_range);
}
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache)
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache, bool popup_on_error)
: QThread()
, m_progress(this)
, m_progress(popup_on_error, this)
, m_invalidate_cache(invalidate_cache)
{
}

View File

@ -14,7 +14,7 @@ class GameListRefreshThread;
class AsyncRefreshProgressCallback : public BaseProgressCallback
{
public:
AsyncRefreshProgressCallback(GameListRefreshThread* parent);
AsyncRefreshProgressCallback(bool popup_on_error, GameListRefreshThread* parent);
void Cancel();
@ -38,6 +38,7 @@ private:
QString m_status_text;
int m_last_range = 1;
int m_last_value = 0;
bool m_popup_on_error = false;
};
class GameListRefreshThread final : public QThread
@ -45,7 +46,7 @@ class GameListRefreshThread final : public QThread
Q_OBJECT
public:
GameListRefreshThread(bool invalidate_cache);
GameListRefreshThread(bool invalidate_cache, bool popup_on_error);
~GameListRefreshThread();
void cancel();
@ -60,4 +61,5 @@ protected:
private:
AsyncRefreshProgressCallback m_progress;
bool m_invalidate_cache;
bool m_popup_on_error;
};

View File

@ -294,7 +294,7 @@ void GameListWidget::initialize()
m_empty_ui.setupUi(m_empty_widget);
m_empty_ui.supportedFormats->setText(qApp->translate("GameListWidget", SUPPORTED_FORMATS_STRING));
connect(m_empty_ui.addGameDirectory, &QPushButton::clicked, this, [this]() { emit addGameDirectoryRequested(); });
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false); });
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false, true); });
connect(qApp, &QGuiApplication::applicationStateChanged, this, [this]() { GameListWidget::updateCustomBackgroundState(); });
m_ui.stack->insertWidget(2, m_empty_widget);
@ -453,11 +453,11 @@ bool GameListWidget::getShowGridCoverTitles() const
return m_model->getShowCoverTitles();
}
void GameListWidget::refresh(bool invalidate_cache)
void GameListWidget::refresh(bool invalidate_cache, bool popup_on_error)
{
cancelRefresh();
m_refresh_thread = new GameListRefreshThread(invalidate_cache);
m_refresh_thread = new GameListRefreshThread(invalidate_cache, popup_on_error);
connect(m_refresh_thread, &GameListRefreshThread::refreshProgress, this, &GameListWidget::onRefreshProgress,
Qt::QueuedConnection);
connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete,

View File

@ -46,7 +46,7 @@ public:
void initialize();
void resizeTableViewColumnsToFit();
void refresh(bool invalidate_cache);
void refresh(bool invalidate_cache, bool popup_on_error);
void cancelRefresh();
void reloadThemeSpecificImages();
void setCustomBackground(bool force = false);

View File

@ -357,8 +357,8 @@ void MainWindow::connectSignals()
[this]() { doControllerSettings(ControllerSettingsWindow::Category::HotkeySettings); });
connect(m_ui.actionAddGameDirectory, &QAction::triggered,
[this]() { getSettingsWindow()->getGameListSettingsWidget()->addSearchDirectory(this); });
connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false); });
connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true); });
connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false, true); });
connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true, true); });
connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled);
connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
@ -547,7 +547,7 @@ void MainWindow::recreate()
MainWindow* new_main_window = new MainWindow();
pxAssert(g_main_window == new_main_window);
new_main_window->initialize();
new_main_window->refreshGameList(false);
new_main_window->refreshGameList(false, false);
new_main_window->show();
deleteLater();
@ -1137,7 +1137,7 @@ bool MainWindow::shouldMouseLock() const
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
return false;
if(m_display_created == false || m_display_widget == nullptr && !isRenderingToMain())
if (m_display_created == false || m_display_widget == nullptr && !isRenderingToMain())
return false;
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
@ -1146,7 +1146,7 @@ bool MainWindow::shouldMouseLock() const
auto* displayWindow = isRenderingToMain() ? window() : m_display_widget->window();
if(displayWindow == nullptr)
if (displayWindow == nullptr)
return false;
return windowsHidden && (displayWindow->isActiveWindow() || displayWindow->isFullScreen());
@ -1215,13 +1215,9 @@ void MainWindow::switchToEmulationView()
m_display_widget->setFocus();
}
void MainWindow::refreshGameList(bool invalidate_cache)
void MainWindow::refreshGameList(bool invalidate_cache, bool popup_on_error)
{
// can't do this while the VM is running because of CDVD
if (s_vm_valid)
return;
m_game_list_widget->refresh(invalidate_cache);
m_game_list_widget->refresh(invalidate_cache, popup_on_error);
}
void MainWindow::cancelGameListRefresh()
@ -1466,8 +1462,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
const time_t entry_played_time = GameList::GetCachedPlayedTimeForSerial(entry->serial);
// Best two options given zero play time are to grey this out or to not show it at all.
if (entry_played_time)
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]()
{ clearGameListEntryPlayTime(entry, entry_played_time); });
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]() { clearGameListEntryPlayTime(entry, entry_played_time); });
// Check Wiki Page functionality is based on a serial redirect.
if (!entry->serial.empty())
@ -2102,7 +2097,7 @@ void MainWindow::onVMStopped()
// reload played time
if (m_game_list_widget->isShowingGameList())
m_game_list_widget->refresh(false);
m_game_list_widget->refresh(false, false);
}
void MainWindow::onGameChanged(const QString& title, const QString& elf_override, const QString& disc_path,
@ -2932,12 +2927,12 @@ void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry, const
if (QMessageBox::question(this, tr("Confirm Reset"),
tr("Are you sure you want to reset the play time for '%1' (%2)?\n\nYour current play time is %3.\n\nThis action cannot be undone.")
.arg(entry->title.empty() ? tr("empty title") : QString::fromStdString(entry->title),
entry->serial.empty() ? tr("no serial") : QString::fromStdString(entry->serial),
QString::fromStdString(GameList::FormatTimespan(entry_played_time, true))),
(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes)
entry->serial.empty() ? tr("no serial") : QString::fromStdString(entry->serial),
QString::fromStdString(GameList::FormatTimespan(entry_played_time, true))),
(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes)
{
GameList::ClearPlayedTimeForSerial(entry->serial);
m_game_list_widget->refresh(false);
m_game_list_widget->refresh(false, false);
}
}

View File

@ -113,7 +113,7 @@ public:
void checkMousePosition(int x, int y);
public Q_SLOTS:
void checkForUpdates(bool display_message, bool force_check);
void refreshGameList(bool invalidate_cache);
void refreshGameList(bool invalidate_cache, bool popup_on_error);
void cancelGameListRefresh();
void reportInfo(const QString& title, const QString& message);
void reportError(const QString& title, const QString& message);

View File

@ -1152,7 +1152,7 @@ void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directo
if (!filters.empty())
{
filters_str.append(QStringLiteral("All File Types (%1)")
.arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " "))));
.arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " "))));
for (const std::string& filter : filters)
{
filters_str.append(
@ -2385,7 +2385,7 @@ int main(int argc, char* argv[])
// When running in batch mode, ensure game list is loaded, but don't scan for any new files.
if (!s_batch_mode)
g_main_window->refreshGameList(false);
g_main_window->refreshGameList(false, false);
else
GameList::Refresh(false, true);

View File

@ -63,7 +63,7 @@ bool GameListSettingsWidget::addExcludedPath(const std::string& path)
Host::CommitBaseSettingChanges();
m_ui.excludedPaths->addItem(QString::fromStdString(path));
g_main_window->refreshGameList(false);
g_main_window->refreshGameList(false, true);
return true;
}
@ -152,7 +152,7 @@ void GameListSettingsWidget::addSearchDirectory(const QString& path, bool recurs
Host::AddBaseValueToStringList("GameList", recursive ? "RecursivePaths" : "Paths", spath.c_str());
Host::CommitBaseSettingChanges();
refreshDirectoryList();
g_main_window->refreshGameList(false);
g_main_window->refreshGameList(false, true);
}
void GameListSettingsWidget::removeSearchDirectory(const QString& path)
@ -166,7 +166,7 @@ void GameListSettingsWidget::removeSearchDirectory(const QString& path)
Host::CommitBaseSettingChanges();
refreshDirectoryList();
g_main_window->refreshGameList(false);
g_main_window->refreshGameList(false, true);
}
void GameListSettingsWidget::onDirectoryListContextMenuRequested(const QPoint& point)
@ -261,7 +261,7 @@ void GameListSettingsWidget::onRemoveExcludedPathButtonClicked()
delete item;
g_main_window->refreshGameList(false);
g_main_window->refreshGameList(false, true);
}
void GameListSettingsWidget::onExcludedPathsSelectionChanged()
@ -271,10 +271,10 @@ void GameListSettingsWidget::onExcludedPathsSelectionChanged()
void GameListSettingsWidget::onRescanAllGamesClicked()
{
g_main_window->refreshGameList(true);
g_main_window->refreshGameList(true, true);
}
void GameListSettingsWidget::onScanForNewGamesClicked()
{
g_main_window->refreshGameList(false);
g_main_window->refreshGameList(false, true);
}

View File

@ -280,7 +280,8 @@ void GameSummaryWidget::onVerifyClicked()
Error error;
if (!hasher.Open(m_entry_path, &error))
{
setVerifyResult(QString::fromStdString(error.GetDescription()));
QString message(QString::fromStdString(error.GetDescription()));
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), message);
return;
}

View File

@ -21,6 +21,7 @@
#include <ctype.h>
#include <exception>
#include <memory>
#include <mutex>
#include <time.h>
#include "fmt/format.h"
@ -270,6 +271,23 @@ static void DetectDiskType()
static std::string m_SourceFilename[3];
static CDVD_SourceType m_CurrentSourceType = CDVD_SourceType::NoDisc;
static std::mutex s_cdvd_lock;
bool cdvdLock(Error* error)
{
if (!s_cdvd_lock.try_lock())
{
Error::SetString(error, TRANSLATE_STR("CDVD", "The CDVD system is currently in use."));
return false;
}
return true;
}
void cdvdUnlock()
{
s_cdvd_lock.unlock();
}
void CDVDsys_SetFile(CDVD_SourceType srctype, std::string newfile)
{
@ -579,7 +597,7 @@ static s32 NODISCdummyS32()
return 0;
}
static void NODISCnewDiskCB(void (*/* callback */)())
static void NODISCnewDiskCB(void (* /* callback */)())
{
}

View File

@ -20,7 +20,6 @@ struct cdvdTrackIndex
u8 discM; // current minute location on the disc (BCD encoded)
u8 discS; // current sector location on the disc (BCD encoded)
u8 discF; // current frame location on the disc (BCD encoded)
};
struct cdvdTrack
@ -188,6 +187,13 @@ extern u8 strack;
extern u8 etrack;
extern std::array<cdvdTrack, 100> tracks;
/// Try to take the CDVD lock, return false if it's already in use.
/// Must be called before your first CDVD call.
extern bool cdvdLock(Error* error = nullptr);
/// Release the CDVD lock. Must be called after you're done with CDVD.
extern void cdvdUnlock();
extern void CDVDsys_ChangeSource(CDVD_SourceType type);
extern void CDVDsys_SetFile(CDVD_SourceType srctype, std::string newfile);
extern const std::string& CDVDsys_GetFile(CDVD_SourceType srctype);

View File

@ -7,7 +7,6 @@
#include "common/Error.h"
#include "common/MD5Digest.h"
#include "common/StringUtil.h"
#include "fmt/format.h"
@ -39,6 +38,10 @@ bool IsoHasher::Open(std::string iso_path, Error* error)
{
Close();
m_is_locked = cdvdLock(error);
if (!m_is_locked)
return false;
CDVDsys_SetFile(CDVD_SourceType::Iso, std::move(iso_path));
CDVDsys_ChangeSource(CDVD_SourceType::Iso);
@ -103,6 +106,12 @@ bool IsoHasher::Open(std::string iso_path, Error* error)
void IsoHasher::Close()
{
if (!m_is_locked)
return;
cdvdUnlock();
m_is_locked = false;
if (!m_is_open)
return;
@ -151,7 +160,7 @@ bool IsoHasher::ComputeTrackHash(Track& track, ProgressCallback* callback)
const u32 update_interval = std::max<u32>(track.sectors / 100u, 1u);
callback->SetStatusText(
fmt::format(TRANSLATE_FS("CDVD", "Calculating checksum for track {}..."), track.number).c_str());
callback->SetProgressRange(track.sectors);
callback->SetProgressRange(track.sectors);
MD5Digest md5;
for (u32 i = 0; i < track.sectors; i++)

View File

@ -28,6 +28,9 @@ public:
IsoHasher();
~IsoHasher();
IsoHasher(const IsoHasher&) = delete;
IsoHasher& operator=(const IsoHasher&) = delete;
static std::string_view GetTrackTypeString(u32 type);
u32 GetTrackCount() const { return static_cast<u32>(m_tracks.size()); }
@ -44,6 +47,7 @@ private:
bool ComputeTrackHash(Track& track, ProgressCallback* callback);
std::vector<Track> m_tracks;
bool m_is_locked = false;
bool m_is_open = false;
bool m_is_cd = false;
};

View File

@ -163,6 +163,9 @@ void SymbolImporter::Reset()
void SymbolImporter::LoadAndAnalyseElf(Pcsx2Config::DebugAnalysisOptions options)
{
if (!VMManager::HasValidVM())
return;
const std::string& elf_path = VMManager::GetCurrentELF();
Error error;
@ -192,6 +195,9 @@ void SymbolImporter::AnalyseElf(
Pcsx2Config::DebugAnalysisOptions options,
bool wait_until_elf_is_loaded)
{
if (!VMManager::HasValidVM())
return;
// Search for a .sym file to load symbols from.
std::string nocash_path;
CDVD_SourceType source_type = CDVDsys_GetSourceType();

View File

@ -16,6 +16,7 @@
#include "common/HeterogeneousContainers.h"
#include "common/Path.h"
#include "common/ProgressCallback.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include <algorithm>
@ -826,6 +827,15 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback*
if (!progress)
progress = ProgressCallback::NullProgressCallback;
Error cdvd_lock_error;
if (!cdvdLock(&cdvd_lock_error))
{
progress->DisplayError(cdvd_lock_error.GetDescription().c_str());
return;
}
ScopedGuard unlock_cdvd = &cdvdUnlock;
if (invalidate_cache)
DeleteCacheFile();
else

View File

@ -485,7 +485,7 @@ void VMManager::UpdateLoggingSettings(SettingsInterface& si)
if (system_console_enabled != Log::IsConsoleOutputEnabled())
Log::SetConsoleOutputLevel(system_console_enabled ? level : LOGLEVEL_NONE);
// Debug console only exists on Windows.
// Debug console only exists on Windows.
#ifdef _WIN32
const bool debug_console_enabled = IsDebuggerPresent() && si.GetBoolValue("Logging", "EnableDebugConsole", false);
Log::SetDebugOutputLevel(debug_console_enabled ? level : LOGLEVEL_NONE);
@ -776,8 +776,8 @@ std::string VMManager::GetGameSettingsPath(const std::string_view game_serial, u
std::string sanitized_serial(Path::SanitizeFileName(game_serial));
return game_serial.empty() ?
Path::Combine(EmuFolders::GameSettings, fmt::format("{:08X}.ini", game_crc)) :
Path::Combine(EmuFolders::GameSettings, fmt::format("{}_{:08X}.ini", sanitized_serial, game_crc));
Path::Combine(EmuFolders::GameSettings, fmt::format("{:08X}.ini", game_crc)) :
Path::Combine(EmuFolders::GameSettings, fmt::format("{}_{:08X}.ini", sanitized_serial, game_crc));
}
std::string VMManager::GetDiscOverrideFromGameSettings(const std::string& elf_path)
@ -1324,6 +1324,15 @@ bool VMManager::Initialize(VMBootParameters boot_params)
}
}
Error cdvd_lock_error;
if (!cdvdLock(&cdvd_lock_error))
{
Host::ReportErrorAsync("Startup Error", cdvd_lock_error.GetDescription());
return false;
}
ScopedGuard unlock_cdvd = &cdvdUnlock;
// resolve source type
if (boot_params.source_type.has_value())
{
@ -1355,12 +1364,14 @@ bool VMManager::Initialize(VMBootParameters boot_params)
if (!LoadBIOS())
{
Host::ReportErrorAsync(TRANSLATE_SV("VMManager", "Error No BIOS Present"),
fmt::format(TRANSLATE_FS("VMManager",
"PCSX2 requires a PlayStation 2 BIOS in order to run.\n\n"
"For legal reasons, you will need to obtain this BIOS from a PlayStation 2 unit which you own.\n\n"
"For step-by-step help with this process, please consult the setup guide at {}.\n\n"
"PCSX2 will be able to run once you've placed your BIOS image inside the folder named \"bios\" within the data directory "
"(Tools Menu -> Open Data Directory)."), PCSX2_DOCUMENTATION_BIOS_URL_SHORTENED));
fmt::format(
TRANSLATE_FS("VMManager",
"PCSX2 requires a PlayStation 2 BIOS in order to run.\n\n"
"For legal reasons, you will need to obtain this BIOS from a PlayStation 2 unit which you own.\n\n"
"For step-by-step help with this process, please consult the setup guide at {}.\n\n"
"PCSX2 will be able to run once you've placed your BIOS image inside the folder named \"bios\" within the data directory "
"(Tools Menu -> Open Data Directory)."),
PCSX2_DOCUMENTATION_BIOS_URL_SHORTENED));
return false;
}
@ -1559,6 +1570,7 @@ bool VMManager::Initialize(VMBootParameters boot_params)
close_memcards.Cancel();
close_cdvd.Cancel();
close_cdvd_files.Cancel();
unlock_cdvd.Cancel();
close_state.Cancel();
if (EmuConfig.CdvdPrecache)
@ -1667,6 +1679,8 @@ void VMManager::Shutdown(bool save_resume_state)
else
cdvdSaveNVRAM();
cdvdUnlock();
s_state.store(VMState::Shutdown, std::memory_order_release);
FullscreenUI::OnVMDestroyed();
SaveStateSelectorUI::Clear();