Qt: Thread-safe QSettings access and updating

This commit is contained in:
Connor McLaughlin 2020-01-24 14:49:49 +10:00
parent 9562cbea56
commit c5282b99e1
10 changed files with 144 additions and 93 deletions

View file

@ -6,12 +6,14 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(QtHostInterface* host_interface, QW
{
m_ui.setupUi(this);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.region, &Settings::region);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.biosPath, &Settings::bios_path);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.enableTTYOutput, &Settings::bios_patch_tty_enable);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.fastBoot, &Settings::bios_patch_fast_boot);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.enableSpeedLimiter, &Settings::speed_limiter_enabled);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.pauseOnStart, &Settings::start_paused);
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.region, "Console/Region",
&Settings::ParseConsoleRegionName, &Settings::GetConsoleRegionName);
SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_ui.biosPath, "BIOS/Path");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableTTYOutput, "BIOS/PatchTTYEnable");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.fastBoot, "BIOS/PatchFastBoot");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.enableSpeedLimiter,
"General/SpeedLimiterEnabled");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.pauseOnStart, "General/StartPaused");
}
ConsoleSettingsWidget::~ConsoleSettingsWidget() = default;

View file

@ -138,13 +138,11 @@ public:
void loadFromSettings()
{
const QSettings& qsettings = m_host_interface->getQSettings();
QStringList path_list = qsettings.value(QStringLiteral("GameList/Paths")).toStringList();
QStringList path_list = m_host_interface->getSettingValue(QStringLiteral("GameList/Paths")).toStringList();
for (QString& entry : path_list)
m_entries.push_back({std::move(entry), false});
path_list = qsettings.value(QStringLiteral("GameList/RecursivePaths")).toStringList();
path_list = m_host_interface->getSettingValue(QStringLiteral("GameList/RecursivePaths")).toStringList();
for (QString& entry : path_list)
m_entries.push_back({std::move(entry), true});
}
@ -162,17 +160,15 @@ public:
paths.push_back(entry.path);
}
QSettings& qsettings = m_host_interface->getQSettings();
if (paths.empty())
qsettings.remove(QStringLiteral("GameList/Paths"));
m_host_interface->removeSettingValue(QStringLiteral("GameList/Paths"));
else
qsettings.setValue(QStringLiteral("GameList/Paths"), paths);
m_host_interface->putSettingValue(QStringLiteral("GameList/Paths"), paths);
if (recursive_paths.empty())
qsettings.remove(QStringLiteral("GameList/RecursivePaths"));
m_host_interface->removeSettingValue(QStringLiteral("GameList/RecursivePaths"));
else
qsettings.setValue(QStringLiteral("GameList/RecursivePaths"), recursive_paths);
m_host_interface->putSettingValue(QStringLiteral("GameList/RecursivePaths"), recursive_paths);
}
private:
@ -191,10 +187,8 @@ GameListSettingsWidget::GameListSettingsWidget(QtHostInterface* host_interface,
{
m_ui.setupUi(this);
QSettings& qsettings = host_interface->getQSettings();
m_search_directories_model = new GameListSearchDirectoriesModel(host_interface);
m_ui.redumpDatabasePath->setText(qsettings.value("GameList/RedumpDatabasePath").toString());
m_ui.redumpDatabasePath->setText(host_interface->getSettingValue("GameList/RedumpDatabasePath").toString());
m_ui.searchDirectoryList->setModel(m_search_directories_model);
m_ui.searchDirectoryList->setSelectionMode(QAbstractItemView::SingleSelection);
m_ui.searchDirectoryList->setSelectionBehavior(QAbstractItemView::SelectRows);
@ -280,7 +274,7 @@ void GameListSettingsWidget::onBrowseRedumpPathButtonPressed()
return;
m_ui.redumpDatabasePath->setText(filename);
m_host_interface->getQSettings().setValue("GameList/RedumpDatabasePath", filename);
m_host_interface->putSettingValue(QStringLiteral("GameList/RedumpDatabasePath"), filename);
m_host_interface->refreshGameList(true, true);
}

View file

@ -9,17 +9,16 @@ GPUSettingsWidget::GPUSettingsWidget(QtHostInterface* host_interface, QWidget* p
m_ui.setupUi(this);
setupAdditionalUi();
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.renderer, &Settings::gpu_renderer);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.fullscreen, &Settings::display_fullscreen);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.displayLinearFiltering,
&Settings::display_linear_filtering);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.vsync, &Settings::video_sync_enabled);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.resolutionScale, &Settings::gpu_resolution_scale);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.trueColor, &Settings::gpu_true_color);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.linearTextureFiltering,
&Settings::gpu_texture_filtering);
SettingWidgetBinder::BindWidgetToSetting(m_host_interface, m_ui.forceProgressiveScan,
&Settings::gpu_force_progressive_scan);
SettingWidgetBinder::BindWidgetToEnumSetting(m_host_interface, m_ui.renderer, "GPU/Renderer",
&Settings::ParseRendererName, &Settings::GetRendererName);
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.fullscreen, "Display/Fullscreen");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.displayLinearFiltering,
"Display/LinearFiltering");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.vsync, "Display/VSync");
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.resolutionScale, "GPU/ResolutionScale");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.trueColor, "GPU/TrueColor");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.linearTextureFiltering, "GPU/TextureFiltering");
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.forceProgressiveScan, "GPU/ForceProgressiveScan");
}
GPUSettingsWidget::~GPUSettingsWidget() = default;

View file

@ -9,7 +9,7 @@ InputButtonBindingWidget::InputButtonBindingWidget(QtHostInterface* host_interfa
QWidget* parent)
: QPushButton(parent), m_host_interface(host_interface), m_setting_name(std::move(setting_name))
{
m_current_binding_value = m_host_interface->getQSettings().value(m_setting_name).toString();
m_current_binding_value = m_host_interface->getSettingValue(m_setting_name).toString();
setText(m_current_binding_value);
connect(this, &QPushButton::pressed, this, &InputButtonBindingWidget::onPressed);
@ -54,7 +54,7 @@ void InputButtonBindingWidget::setNewBinding()
if (m_new_binding_value.isEmpty())
return;
m_host_interface->getQSettings().setValue(m_setting_name, m_new_binding_value);
m_host_interface->putSettingValue(m_setting_name, m_new_binding_value);
m_host_interface->updateInputMap();
m_current_binding_value = std::move(m_new_binding_value);

View file

@ -216,9 +216,8 @@ void MainWindow::setupAdditionalUi()
action->setCheckable(true);
action->setChecked(m_host_interface->GetCoreSettings().gpu_renderer == renderer);
connect(action, &QAction::triggered, [this, action, renderer]() {
m_host_interface->getQSettings().setValue(QStringLiteral("GPU/Renderer"),
QString(Settings::GetRendererName(renderer)));
m_host_interface->GetCoreSettings().gpu_renderer = renderer;
m_host_interface->putSettingValue(QStringLiteral("GPU/Renderer"), QString(Settings::GetRendererName(renderer)));
m_host_interface->applySettings();
action->setChecked(true);
switchRenderer();
});

View file

@ -30,13 +30,12 @@ void PortSettingsWidget::createUi()
void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui)
{
const Settings& settings = m_host_interface->GetCoreSettings();
ui->widget = new QWidget(m_tab_widget);
ui->layout = new QVBoxLayout(ui->widget);
QHBoxLayout* memory_card_layout = new QHBoxLayout();
ui->memory_card_path = new QLineEdit(QString::fromStdString(settings.memory_card_paths[index]), ui->widget);
ui->memory_card_path = new QLineEdit(
m_host_interface->getSettingValue(QStringLiteral("MemoryCards/Card%1Path").arg(index + 1)).toString(), ui->widget);
memory_card_layout->addWidget(ui->memory_card_path);
ui->memory_card_path_browse = new QPushButton(tr("Browse..."), ui->widget);
memory_card_layout->addWidget(ui->memory_card_path_browse);
@ -49,13 +48,19 @@ void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui)
ui->controller_type->addItem(
QString::fromLocal8Bit(Settings::GetControllerTypeDisplayName(static_cast<ControllerType>(i))));
}
ui->controller_type->setCurrentIndex(static_cast<int>(settings.controller_types[index]));
ControllerType ctype = Settings::ParseControllerTypeName(
m_host_interface->getSettingValue(QStringLiteral("Controller%1/Type").arg(index + 1))
.toString()
.toStdString()
.c_str())
.value_or(ControllerType::None);
ui->controller_type->setCurrentIndex(static_cast<int>(ctype));
connect(ui->controller_type, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
[this, index]() { onControllerTypeChanged(index); });
ui->layout->addWidget(new QLabel(tr("Controller Type:"), ui->widget));
ui->layout->addWidget(ui->controller_type);
createPortBindingSettingsUi(index, ui);
createPortBindingSettingsUi(index, ui, ctype);
ui->layout->addStretch(1);
@ -64,12 +69,11 @@ void PortSettingsWidget::createPortSettingsUi(int index, PortSettingsUI* ui)
m_tab_widget->addTab(ui->widget, tr("Port %1").arg(index + 1));
}
void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* ui)
void PortSettingsWidget::createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype)
{
QWidget* container = new QWidget(ui->widget);
QGridLayout* layout = new QGridLayout(container);
layout->setContentsMargins(0, 0, 0, 0);
const ControllerType ctype = m_host_interface->GetCoreSettings().controller_types[index];
const auto buttons = Controller::GetButtonNames(ctype);
if (!buttons.empty())
@ -124,9 +128,9 @@ void PortSettingsWidget::onControllerTypeChanged(int index)
if (type_index < 0 || type_index >= static_cast<int>(ControllerType::Count))
return;
m_host_interface->GetCoreSettings().controller_types[index] = static_cast<ControllerType>(type_index);
m_host_interface->getQSettings().setValue(
m_host_interface->putSettingValue(
QStringLiteral("Controller%1/Type").arg(index + 1),
QString::fromStdString(Settings::GetControllerTypeName(static_cast<ControllerType>(type_index))));
createPortBindingSettingsUi(index, &m_port_ui[index]);
m_host_interface->applySettings();
createPortBindingSettingsUi(index, &m_port_ui[index], static_cast<ControllerType>(type_index));
}

View file

@ -40,7 +40,7 @@ private:
void createUi();
void createPortSettingsUi(int index, PortSettingsUI* ui);
void createPortBindingSettingsUi(int index, PortSettingsUI* ui);
void createPortBindingSettingsUi(int index, PortSettingsUI* ui, ControllerType ctype);
void onControllerTypeChanged(int index);
std::array<PortSettingsUI, 2> m_port_ui = {};

View file

@ -48,6 +48,8 @@ void QtHostInterface::ReportMessage(const char* message)
void QtHostInterface::setDefaultSettings()
{
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
m_settings.SetDefaults();
// default input settings for Qt
@ -67,20 +69,57 @@ void QtHostInterface::setDefaultSettings()
m_qsettings.setValue(QStringLiteral("Controller1/ButtonR1"), QStringLiteral("Keyboard/E"));
m_qsettings.setValue(QStringLiteral("Controller1/ButtonR2"), QStringLiteral("Keyboard/3"));
updateQSettings();
updateQSettingsFromCoreSettings();
}
void QtHostInterface::updateQSettings()
QVariant QtHostInterface::getSettingValue(const QString& name)
{
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
return m_qsettings.value(name);
}
void QtHostInterface::putSettingValue(const QString& name, const QVariant& value)
{
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
m_qsettings.setValue(name, value);
}
void QtHostInterface::removeSettingValue(const QString& name)
{
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
m_qsettings.remove(name);
}
void QtHostInterface::updateQSettingsFromCoreSettings()
{
QtSettingsInterface si(m_qsettings);
m_settings.Save(si);
// m_qsettings.sync();
}
void QtHostInterface::applySettings()
{
QtSettingsInterface si(m_qsettings);
m_settings.Load(si);
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "applySettings", Qt::QueuedConnection);
return;
}
// TODO: Should we move this to the base class?
const bool old_vsync_enabled = m_settings.video_sync_enabled;
const bool old_audio_sync_enabled = m_settings.audio_sync_enabled;
const bool old_speed_limiter_enabled = m_settings.speed_limiter_enabled;
{
std::lock_guard<std::mutex> guard(m_qsettings_mutex);
QtSettingsInterface si(m_qsettings);
m_settings.Load(si);
}
if (m_settings.video_sync_enabled != old_vsync_enabled || m_settings.audio_sync_enabled != old_audio_sync_enabled ||
m_settings.speed_limiter_enabled != old_speed_limiter_enabled)
{
UpdateSpeedLimiterState();
}
}
void QtHostInterface::checkSettings()
@ -105,7 +144,9 @@ void QtHostInterface::checkSettings()
setDefaultSettings();
}
applySettings();
// initial setting init - we don't do this locked since the thread hasn't been created yet
QtSettingsInterface si(m_qsettings);
m_settings.Load(si);
}
void QtHostInterface::createGameList()
@ -446,6 +487,7 @@ void QtHostInterface::doBootSystem(QString initial_filename, QString initial_sav
wakeThread();
m_audio_stream->PauseOutput(false);
UpdateSpeedLimiterState();
emit emulationStarted();
}

View file

@ -9,6 +9,7 @@
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
@ -30,11 +31,12 @@ public:
void ReportError(const char* message) override;
void ReportMessage(const char* message) override;
const QSettings& getQSettings() const { return m_qsettings; }
QSettings& getQSettings() { return m_qsettings; }
void setDefaultSettings();
void updateQSettings();
void applySettings();
/// Thread-safe QSettings access.
QVariant getSettingValue(const QString& name);
void putSettingValue(const QString& name, const QVariant& value);
void removeSettingValue(const QString& name);
const Settings& GetCoreSettings() const { return m_settings; }
Settings& GetCoreSettings() { return m_settings; }
@ -74,6 +76,7 @@ Q_SIGNALS:
void performanceCountersUpdated(float speed, float fps, float vps, float avg_frame_time, float worst_frame_time);
public Q_SLOTS:
void applySettings();
void powerOffSystem();
void resetSystem();
void pauseSystem(bool paused);
@ -113,6 +116,8 @@ private:
};
void checkSettings();
void updateQSettingsFromCoreSettings();
void createGameList();
void updateControllerInputMap();
void updateHotkeyInputMap();
@ -124,6 +129,7 @@ private:
void wakeThread();
QSettings m_qsettings;
std::mutex m_qsettings_mutex;
std::unique_ptr<GameList> m_game_list;

View file

@ -1,4 +1,5 @@
#pragma once
#include <optional>
#include <type_traits>
#include "core/settings.h"
@ -63,7 +64,7 @@ struct SettingAccessor<QComboBox>
template<typename F>
static void connectValueChanged(QComboBox* widget, F func)
{
widget->connect(widget, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), func);
widget->connect(widget, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), func);
}
};
@ -90,64 +91,68 @@ struct SettingAccessor<QCheckBox>
};
/// Binds a widget's value to a setting, updating it when the value changes.
// template<typename WidgetType, typename DataType, typename = void>
// void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, DataType Settings::*settings_ptr);
template<typename WidgetType>
void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, bool Settings::*settings_ptr)
void BindWidgetToBoolSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name)
{
using Accessor = SettingAccessor<WidgetType>;
Accessor::setBoolValue(widget, hi->GetCoreSettings().*settings_ptr);
Accessor::setBoolValue(widget, hi->getSettingValue(setting_name).toBool());
Accessor::connectValueChanged(widget, [hi, widget, settings_ptr]() {
(hi->GetCoreSettings().*settings_ptr) = Accessor::getBoolValue(widget);
hi->updateQSettings();
Accessor::connectValueChanged(widget, [hi, widget, setting_name]() {
const bool new_value = Accessor::getBoolValue(widget);
hi->putSettingValue(setting_name, new_value);
hi->applySettings();
});
}
template<typename WidgetType>
void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, std::string Settings::*settings_ptr)
void BindWidgetToIntSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name)
{
using Accessor = SettingAccessor<WidgetType>;
Accessor::setStringValue(widget, QString::fromStdString(hi->GetCoreSettings().*settings_ptr));
Accessor::setIntValue(widget, hi->getSettingValue(setting_name).toInt());
Accessor::connectValueChanged(widget, [hi, widget, settings_ptr]() {
const QString value = Accessor::getStringValue(widget);
(hi->GetCoreSettings().*settings_ptr) = value.toStdString();
hi->updateQSettings();
Accessor::connectValueChanged(widget, [hi, widget, setting_name]() {
const int new_value = Accessor::getIntValue(widget);
hi->putSettingValue(setting_name, new_value);
hi->applySettings();
});
}
template<typename WidgetType>
void BindWidgetToStringSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name)
{
using Accessor = SettingAccessor<WidgetType>;
Accessor::setStringValue(widget, hi->getSettingValue(setting_name).toString());
Accessor::connectValueChanged(widget, [hi, widget, setting_name]() {
const QString new_value = Accessor::getStringValue(widget);
hi->putSettingValue(setting_name, new_value);
hi->applySettings();
});
}
template<typename WidgetType, typename DataType>
void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, DataType Settings::*settings_ptr,
std::enable_if_t<std::is_enum_v<DataType>, int>* v = nullptr)
void BindWidgetToEnumSetting(QtHostInterface* hi, WidgetType* widget, const char* setting_name,
std::optional<DataType> (*from_string_function)(const char* str),
const char* (*to_string_function)(DataType value))
{
using Accessor = SettingAccessor<WidgetType>;
using UnderlyingType = std::underlying_type_t<DataType>;
Accessor::setIntValue(widget, static_cast<int>(static_cast<UnderlyingType>(hi->GetCoreSettings().*settings_ptr)));
const QString old_setting_string_value = hi->getSettingValue(setting_name).toString();
const std::optional<DataType> old_setting_value =
from_string_function(old_setting_string_value.toStdString().c_str());
if (old_setting_value.has_value())
Accessor::setIntValue(widget, static_cast<int>(static_cast<UnderlyingType>(old_setting_value.value())));
Accessor::connectValueChanged(widget, [hi, widget, settings_ptr](int) {
const int value = Accessor::getIntValue(widget);
(hi->GetCoreSettings().*settings_ptr) = static_cast<DataType>(static_cast<UnderlyingType>(value));
hi->updateQSettings();
});
}
template<typename WidgetType, typename DataType>
void BindWidgetToSetting(QtHostInterface* hi, WidgetType* widget, DataType Settings::*settings_ptr,
std::enable_if_t<std::is_integral_v<DataType>, int>* v = nullptr)
{
using Accessor = SettingAccessor<WidgetType>;
Accessor::setIntValue(widget, static_cast<int>(hi->GetCoreSettings().*settings_ptr));
Accessor::connectValueChanged(widget, [hi, widget, settings_ptr](int) {
const int value = Accessor::getIntValue(widget);
(hi->GetCoreSettings().*settings_ptr) = static_cast<DataType>(value);
hi->updateQSettings();
Accessor::connectValueChanged(widget, [hi, widget, setting_name, to_string_function]() {
const DataType value = static_cast<DataType>(static_cast<UnderlyingType>(Accessor::getIntValue(widget)));
const char* string_value = to_string_function(value);
hi->putSettingValue(setting_name, QString::fromLocal8Bit(string_value));
hi->applySettings();
});
}