mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-07 09:42:23 +02:00
Merge pull request #236 from daleglass-overte/optimize_settings
Optimize settings system
This commit is contained in:
commit
502a805b89
14 changed files with 847 additions and 247 deletions
|
@ -1933,29 +1933,14 @@ Application::Application(
|
|||
|
||||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
QTimer* settingsTimer = new QTimer();
|
||||
moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
|
||||
// This needs to run on the settings thread, so we need to pass the `settingsTimer` as the
|
||||
// receiver object, otherwise it will run on the application thread and trigger a warning
|
||||
// about trying to kill the timer on the main thread.
|
||||
connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{
|
||||
// Disconnect the signal from the save settings
|
||||
QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
||||
// Stop the settings timer
|
||||
settingsTimer->stop();
|
||||
// Delete it (this will trigger the thread destruction
|
||||
settingsTimer->deleteLater();
|
||||
// Mark the settings thread as finished, so we know we can safely save in the main application
|
||||
// shutdown code
|
||||
_settingsGuard.trigger();
|
||||
});
|
||||
|
||||
int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now
|
||||
settingsTimer->setSingleShot(false);
|
||||
settingsTimer->setInterval(SAVE_SETTINGS_INTERVAL); // 10s, Qt::CoarseTimer acceptable
|
||||
QObject::connect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
||||
settingsTimer->start();
|
||||
}, QThread::LowestPriority);
|
||||
|
||||
QTimer* settingsTimer = new QTimer();
|
||||
int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now
|
||||
settingsTimer->setSingleShot(false);
|
||||
settingsTimer->setInterval(SAVE_SETTINGS_INTERVAL); // 10s, Qt::CoarseTimer acceptable
|
||||
QObject::connect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
|
||||
settingsTimer->start();
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPersonLookAt)) {
|
||||
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); // So that camera doesn't auto-switch to third person.
|
||||
|
@ -2845,13 +2830,6 @@ void Application::cleanupBeforeQuit() {
|
|||
locationUpdateTimer.stop();
|
||||
identityPacketTimer.stop();
|
||||
pingTimer.stop();
|
||||
|
||||
// Wait for the settings thread to shut down, and save the settings one last time when it's safe
|
||||
if (_settingsGuard.wait()) {
|
||||
// save state
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
_window->saveGeometry();
|
||||
|
||||
// Destroy third party processes after scripts have finished using them.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Created by Andrzej Kapolka on 5/10/13.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
// Copyright 2020 Vircadia contributors.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -481,7 +482,7 @@ public slots:
|
|||
void setIsInterstitialMode(bool interstitialMode);
|
||||
|
||||
void updateVerboseLogging();
|
||||
|
||||
|
||||
void setCachebustRequire();
|
||||
|
||||
void changeViewAsNeeded(float boomLength);
|
||||
|
@ -719,8 +720,6 @@ private:
|
|||
|
||||
bool _notifiedPacketVersionMismatchThisDomain;
|
||||
|
||||
ConditionalGuard _settingsGuard;
|
||||
|
||||
GLCanvas* _glWidget{ nullptr };
|
||||
|
||||
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Brad Hefta-Gaub on 2/25/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Brad Hefta-Gaub on 3/22/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -37,7 +38,7 @@ public slots:
|
|||
* @function Settings.getValue
|
||||
* @param {string} key - The name of the setting.
|
||||
* @param {string|number|boolean|object} [defaultValue=""] - The value to return if the setting doesn't exist.
|
||||
* @returns {string|number|boolean|object} The value stored in the named setting if it exists, otherwise the
|
||||
* @returns {string|number|boolean|object} The value stored in the named setting if it exists, otherwise the
|
||||
* <code>defaultValue</code>.
|
||||
* @example <caption>Retrieve non-existent setting values.</caption>
|
||||
* var value1 = Settings.getValue("Script Example/Nonexistent Key");
|
||||
|
@ -50,11 +51,11 @@ public slots:
|
|||
QVariant getValue(const QString& setting, const QVariant& defaultValue);
|
||||
|
||||
/*@jsdoc
|
||||
* Stores a value in a named setting. If the setting already exists, its value is overwritten. If the value is
|
||||
* Stores a value in a named setting. If the setting already exists, its value is overwritten. If the value is
|
||||
* <code>null</code> or <code>undefined</code>, the setting is deleted.
|
||||
* @function Settings.setValue
|
||||
* @param {string} key - The name of the setting. Be sure to use a unique name if creating a new setting.
|
||||
* @param {string|number|boolean|object|undefined} value - The value to store in the setting. If <code>null</code> or
|
||||
* @param {string|number|boolean|object|undefined} value - The value to store in the setting. If <code>null</code> or
|
||||
* <code>undefined</code> is specified, the setting is deleted.
|
||||
* @example <caption>Store and retrieve an object value.</caption>
|
||||
* Settings.setValue("Script Example/My Key", { x: 0, y: 10, z: 0 });
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by AndrewMeadows 2015.10.05
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -15,6 +16,7 @@
|
|||
#include <math.h>
|
||||
|
||||
|
||||
Q_LOGGING_CATEGORY(settings_handle, "settings.handle")
|
||||
|
||||
const QString Settings::firstRun { "firstRun" };
|
||||
|
||||
|
@ -34,13 +36,11 @@ void Settings::remove(const QString& key) {
|
|||
_manager->remove(key);
|
||||
}
|
||||
|
||||
QStringList Settings::childGroups() const {
|
||||
return _manager->childGroups();
|
||||
}
|
||||
// QStringList Settings::childGroups() const {
|
||||
// }
|
||||
|
||||
QStringList Settings::childKeys() const {
|
||||
return _manager->childKeys();
|
||||
}
|
||||
// QStringList Settings::childKeys() const {
|
||||
// }
|
||||
|
||||
QStringList Settings::allKeys() const {
|
||||
return _manager->allKeys();
|
||||
|
@ -52,37 +52,56 @@ bool Settings::contains(const QString& key) const {
|
|||
}
|
||||
|
||||
int Settings::beginReadArray(const QString& prefix) {
|
||||
return _manager->beginReadArray(prefix);
|
||||
_groups.push(Group(prefix));
|
||||
_groupPrefix = getGroupPrefix();
|
||||
int size = _manager->value(_groupPrefix + "/size", -1).toInt();
|
||||
_groups.top().setSize(size);
|
||||
return size;
|
||||
}
|
||||
|
||||
void Settings::beginWriteArray(const QString& prefix, int size) {
|
||||
_manager->beginWriteArray(prefix, size);
|
||||
_groups.push(Group(prefix));
|
||||
_groupPrefix = getGroupPrefix();
|
||||
_manager->setValue(_groupPrefix + "/size", size);
|
||||
|
||||
_groups.top().setSize(size);
|
||||
_groups.top().setIndex(0);
|
||||
|
||||
_groupPrefix = getGroupPrefix();
|
||||
}
|
||||
|
||||
void Settings::endArray() {
|
||||
_manager->endArray();
|
||||
}
|
||||
|
||||
void Settings::setArrayIndex(int i) {
|
||||
_manager->setArrayIndex(i);
|
||||
}
|
||||
|
||||
void Settings::beginGroup(const QString& prefix) {
|
||||
_manager->beginGroup(prefix);
|
||||
}
|
||||
|
||||
void Settings::endGroup() {
|
||||
_manager->endGroup();
|
||||
}
|
||||
|
||||
void Settings::setValue(const QString& name, const QVariant& value) {
|
||||
if (_manager->value(name) != value) {
|
||||
_manager->setValue(name, value);
|
||||
if (!_groups.empty()) {
|
||||
_groups.pop();
|
||||
_groupPrefix = getGroupPrefix();
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::setArrayIndex(int i) {
|
||||
if (!_groups.empty()) {
|
||||
_groups.top().setIndex(i);
|
||||
_groupPrefix = getGroupPrefix();
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::beginGroup(const QString& prefix) {
|
||||
_groups.push(Group(prefix));
|
||||
_groupPrefix = getGroupPrefix();
|
||||
}
|
||||
|
||||
void Settings::endGroup() {
|
||||
_groups.pop();
|
||||
_groupPrefix = getGroupPrefix();
|
||||
}
|
||||
|
||||
void Settings::setValue(const QString& name, const QVariant& value) {
|
||||
QString fullPath = getPath(name);
|
||||
_manager->setValue(fullPath, value);
|
||||
}
|
||||
|
||||
QVariant Settings::value(const QString& name, const QVariant& defaultValue) const {
|
||||
return _manager->value(name, defaultValue);
|
||||
QString fullPath = getPath(name);
|
||||
return _manager->value(fullPath, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
|
@ -153,3 +172,31 @@ void Settings::getQuatValueIfValid(const QString& name, glm::quat& quatValue) {
|
|||
endGroup();
|
||||
}
|
||||
|
||||
QString Settings::getGroupPrefix() const {
|
||||
QString ret;
|
||||
|
||||
for(Group g : _groups) {
|
||||
if (!ret.isEmpty()) {
|
||||
ret.append("/");
|
||||
}
|
||||
|
||||
ret.append(g.name());
|
||||
|
||||
if ( g.isArray() ) {
|
||||
// QSettings indexes arrays from 1
|
||||
ret.append(QString("/%1").arg(g.index() + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString Settings::getPath(const QString &key) const {
|
||||
QString ret = _groupPrefix;
|
||||
if (!ret.isEmpty() ) {
|
||||
ret.append("/");
|
||||
}
|
||||
|
||||
ret.append(key);
|
||||
return ret;
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Clement on 1/18/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -20,6 +21,7 @@
|
|||
#include <QtCore/QReadWriteLock>
|
||||
#include <QtCore/QSharedPointer>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
@ -27,17 +29,65 @@
|
|||
#include "SettingInterface.h"
|
||||
|
||||
|
||||
// TODO: remove
|
||||
Q_DECLARE_LOGGING_CATEGORY(settings_handle)
|
||||
|
||||
/**
|
||||
* @brief QSettings analog
|
||||
*
|
||||
* This class emulates the interface of QSettings, and forwards all reads and writes to the global Setting::Manager.
|
||||
*
|
||||
* It should be used in the same manner as QSettings -- created only wherever it happens to be needed.
|
||||
* It's not thread safe, and each thread should have their own.
|
||||
*
|
||||
* Unlike QSettings, destruction doesn't cause the config to be saved, instead Config::Manager is in
|
||||
* charge of taking care of that.
|
||||
*
|
||||
* @note childGroups and childKeys are unimplemented because nothing in the code needs them so far.
|
||||
*/
|
||||
class Settings {
|
||||
public:
|
||||
|
||||
class Group {
|
||||
public:
|
||||
|
||||
Group(const QString &groupName = QString()) {
|
||||
_name = groupName;
|
||||
}
|
||||
|
||||
QString name() const { return _name; }
|
||||
|
||||
bool isArray() const { return _arraySize != -1; }
|
||||
|
||||
void setIndex(int i) {
|
||||
if ( _arraySize < i+1) {
|
||||
_arraySize = i+1;
|
||||
}
|
||||
|
||||
_arrayIndex = i;
|
||||
}
|
||||
|
||||
int index() const { return _arrayIndex; }
|
||||
int size() const { return _arraySize; }
|
||||
void setSize(int sz) { _arraySize = sz; }
|
||||
|
||||
private:
|
||||
|
||||
QString _name;
|
||||
int _arrayIndex{0};
|
||||
int _arraySize{-1};
|
||||
};
|
||||
|
||||
static const QString firstRun;
|
||||
Settings();
|
||||
|
||||
QString fileName() const;
|
||||
|
||||
void remove(const QString& key);
|
||||
QStringList childGroups() const;
|
||||
QStringList childKeys() const;
|
||||
|
||||
// These are not currently being used
|
||||
// QStringList childGroups() const;
|
||||
// QStringList childKeys() const;
|
||||
|
||||
QStringList allKeys() const;
|
||||
bool contains(const QString& key) const;
|
||||
int beginReadArray(const QString & prefix);
|
||||
|
@ -61,33 +111,108 @@ public:
|
|||
void getQuatValueIfValid(const QString& name, glm::quat& quatValue);
|
||||
|
||||
private:
|
||||
QString getGroupPrefix() const;
|
||||
QString getPath(const QString &value) const;
|
||||
|
||||
QSharedPointer<Setting::Manager> _manager;
|
||||
QStack<Group> _groups;
|
||||
QString _groupPrefix;
|
||||
};
|
||||
|
||||
namespace Setting {
|
||||
|
||||
/**
|
||||
* @brief Handle to a setting of type T.
|
||||
*
|
||||
* This creates an object that manipulates a setting in the settings system. Changes will be written
|
||||
* to the configuration file at some point controlled by Setting::Manager.
|
||||
*
|
||||
* @tparam T Type of the setting.
|
||||
*/
|
||||
template <typename T>
|
||||
class Handle : public Interface {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct handle to a setting
|
||||
*
|
||||
* @param key The key corresponding to the setting in the settings file. Eg, 'shadowsEnabled'
|
||||
*/
|
||||
Handle(const QString& key) : Interface(key) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a handle to a setting
|
||||
*
|
||||
* @param path Path to the key corresponding to the setting in the settings file. Eg, QStringList() << group << key
|
||||
*/
|
||||
Handle(const QStringList& path) : Interface(path.join("/")) {}
|
||||
|
||||
/**
|
||||
* @brief Construct handle to a setting with a default value
|
||||
*
|
||||
* @param key The key corresponding to the setting in the settings file. Eg, 'shadowsEnabled'
|
||||
* @param defaultValue Default value for this setting
|
||||
*/
|
||||
Handle(const QString& key, const T& defaultValue) : Interface(key), _defaultValue(defaultValue) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a handle to a setting with a default value
|
||||
*
|
||||
* @param path Path to the key corresponding to the setting in the settings file. Eg, QStringList() << group << key
|
||||
* @param defaultValue Default value for this setting
|
||||
*/
|
||||
Handle(const QStringList& path, const T& defaultValue) : Handle(path.join("/"), defaultValue) {}
|
||||
|
||||
/**
|
||||
* @brief Construct a handle to a deprecated setting
|
||||
*
|
||||
* If used, a warning will written to the log.
|
||||
*
|
||||
* @param key The key corresponding to the setting in the settings file. Eg, 'shadowsEnabled'
|
||||
* @return Handle The handle object
|
||||
*/
|
||||
static Handle Deprecated(const QString& key) {
|
||||
Handle handle = Handle(key);
|
||||
handle.deprecate();
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a handle to a deprecated setting
|
||||
*
|
||||
* If used, a warning will written to the log.
|
||||
*
|
||||
* @param path Path to the key corresponding to the setting in the settings file. Eg, QStringList() << group << key
|
||||
* @return Handle The handle object
|
||||
*/
|
||||
static Handle Deprecated(const QStringList& path) {
|
||||
return Deprecated(path.join("/"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a handle to a deprecated setting with a default value
|
||||
*
|
||||
* If used, a warning will written to the log.
|
||||
*
|
||||
* @param key The key corresponding to the setting in the settings file. Eg, 'shadowsEnabled'
|
||||
* @param defaultValue Default value for this setting
|
||||
* @return Handle The handle object
|
||||
*/
|
||||
static Handle Deprecated(const QString& key, const T& defaultValue) {
|
||||
Handle handle = Handle(key, defaultValue);
|
||||
handle.deprecate();
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a handle to a deprecated setting with a default value
|
||||
*
|
||||
* If used, a warning will written to the log.
|
||||
*
|
||||
* @param path Path to the key corresponding to the setting in the settings file. Eg, QStringList() << group << key
|
||||
* @param defaultValue Default value for this setting
|
||||
* @return Handle The handle object
|
||||
*/
|
||||
static Handle Deprecated(const QStringList& path, const T& defaultValue) {
|
||||
return Deprecated(path.join("/"), defaultValue);
|
||||
}
|
||||
|
@ -96,32 +221,66 @@ namespace Setting {
|
|||
deinit();
|
||||
}
|
||||
|
||||
// Returns setting value, returns its default value if not found
|
||||
/**
|
||||
* @brief Returns the value of the setting, or the default value if not found
|
||||
*
|
||||
* @return T Value of the associated setting
|
||||
*/
|
||||
T get() const {
|
||||
return get(_defaultValue);
|
||||
}
|
||||
|
||||
// Returns setting value, returns other if not found
|
||||
/**
|
||||
* @brief Returns the value of the setting, or 'other' if not found.
|
||||
*
|
||||
* @param other Value to return if the setting is not set
|
||||
* @return T Value of the associated setting
|
||||
*/
|
||||
T get(const T& other) const {
|
||||
maybeInit();
|
||||
return (_isSet) ? _value : other;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns whether the setting is set to a value
|
||||
*
|
||||
* @return true The setting has a value
|
||||
* @return false The setting has no value
|
||||
*/
|
||||
bool isSet() const {
|
||||
maybeInit();
|
||||
return _isSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the default value for this setting
|
||||
*
|
||||
* @return const T& Default value for this setting
|
||||
*/
|
||||
const T& getDefault() const {
|
||||
return _defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the value to the default
|
||||
*
|
||||
*/
|
||||
void reset() {
|
||||
set(_defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the setting to the specified value.
|
||||
*
|
||||
* The value will be stored in the configuration file.
|
||||
*
|
||||
* @param value Value to set the setting to.
|
||||
*/
|
||||
void set(const T& value) {
|
||||
maybeInit();
|
||||
|
||||
// qCDebug(settings_handle) << "Setting" << this->getKey() << "to" << value;
|
||||
|
||||
if ((!_isSet && (value != _defaultValue)) || _value != value) {
|
||||
_value = value;
|
||||
_isSet = true;
|
||||
|
@ -132,6 +291,12 @@ namespace Setting {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove the value from the setting
|
||||
*
|
||||
* This returns the setting to an unset state. If read, it will be read as the default value.
|
||||
*
|
||||
*/
|
||||
void remove() {
|
||||
maybeInit();
|
||||
if (_isSet) {
|
||||
|
@ -148,7 +313,7 @@ namespace Setting {
|
|||
void deprecate() {
|
||||
if (_isSet) {
|
||||
if (get() != getDefault()) {
|
||||
qInfo().nospace() << "[DEPRECATION NOTICE] " << _key << "(" << get() << ") has been deprecated, and has no effect";
|
||||
qCInfo(settings_handle).nospace() << "[DEPRECATION NOTICE] " << _key << "(" << get() << ") has been deprecated, and has no effect";
|
||||
} else {
|
||||
remove();
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Clement on 9/13/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Clement on 2/2/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -23,23 +24,15 @@
|
|||
#include "SharedUtil.h"
|
||||
#include "ThreadHelpers.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(settings_interface, "settings.interface")
|
||||
|
||||
namespace Setting {
|
||||
// This should only run as a post-routine in the QCoreApplication destructor
|
||||
void cleanupSettingsSaveThread() {
|
||||
auto globalManager = DependencyManager::get<Manager>();
|
||||
Q_ASSERT(qApp && globalManager);
|
||||
|
||||
// Grab the settings thread to shut it down
|
||||
QThread* settingsManagerThread = globalManager->thread();
|
||||
|
||||
// Quit the settings manager thread and wait for it so we
|
||||
// don't get concurrent accesses when we save all settings below
|
||||
settingsManagerThread->quit();
|
||||
settingsManagerThread->wait();
|
||||
|
||||
// [IMPORTANT] Save all settings when the QApplication goes down
|
||||
globalManager->saveAll();
|
||||
|
||||
globalManager->terminateThread();
|
||||
qCDebug(shared) << "Settings thread stopped.";
|
||||
}
|
||||
|
||||
|
@ -48,37 +41,6 @@ namespace Setting {
|
|||
auto globalManager = DependencyManager::get<Manager>();
|
||||
Q_ASSERT(qApp && globalManager);
|
||||
|
||||
// Let's set up the settings private instance on its own thread
|
||||
QThread* thread = new QThread(qApp);
|
||||
Q_CHECK_PTR(thread);
|
||||
thread->setObjectName("Settings Thread");
|
||||
|
||||
// Setup setting periodical save timer
|
||||
QObject::connect(thread, &QThread::started, globalManager.data(), [globalManager] {
|
||||
setThreadName("Settings Save Thread");
|
||||
globalManager->startTimer();
|
||||
});
|
||||
QObject::connect(thread, &QThread::finished, globalManager.data(), &Manager::stopTimer);
|
||||
|
||||
// Setup manager threading affinity
|
||||
// This makes the timer fire on the settings thread so we don't block the main
|
||||
// thread with a lot of file I/O.
|
||||
// We bring back the manager to the main thread when the QApplication goes down
|
||||
globalManager->moveToThread(thread);
|
||||
QObject::connect(thread, &QThread::finished, globalManager.data(), [] {
|
||||
auto globalManager = DependencyManager::get<Manager>();
|
||||
Q_ASSERT(qApp && globalManager);
|
||||
|
||||
// Move manager back to the main thread (has to be done on owning thread)
|
||||
globalManager->moveToThread(qApp->thread());
|
||||
});
|
||||
|
||||
// Start the settings save thread
|
||||
thread->start();
|
||||
qCDebug(shared) << "Settings thread started.";
|
||||
|
||||
// Register cleanupSettingsSaveThread to run inside QCoreApplication's destructor.
|
||||
// This will cleanup the settings thread and save all settings before shut down.
|
||||
qAddPostRoutine(cleanupSettingsSaveThread);
|
||||
}
|
||||
|
||||
|
@ -115,7 +77,7 @@ namespace Setting {
|
|||
// in an assignment-client - the QSettings backing we use for this means persistence of these
|
||||
// settings from an AC (when there can be multiple terminating at same time on one machine)
|
||||
// is currently not supported
|
||||
qWarning() << "Setting::Interface::init() for key" << _key << "- Manager not yet created." <<
|
||||
qCWarning(settings_interface) << "Setting::Interface::init() for key" << _key << "- Manager not yet created." <<
|
||||
"Settings persistence disabled.";
|
||||
} else {
|
||||
_manager = DependencyManager::get<Manager>();
|
||||
|
@ -125,11 +87,12 @@ namespace Setting {
|
|||
manager->registerHandle(this);
|
||||
_isInitialized = true;
|
||||
} else {
|
||||
qWarning() << "Settings interface used after manager destroyed";
|
||||
qCWarning(settings_interface) << "Settings interface used after manager destroyed";
|
||||
}
|
||||
|
||||
|
||||
// Load value from disk
|
||||
load();
|
||||
//qCDebug(settings_interface) << "Setting" << this->getKey() << "initialized to" << getVariant();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,20 +107,21 @@ namespace Setting {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Interface::maybeInit() const {
|
||||
if (!_isInitialized) {
|
||||
//qCDebug(settings_interface) << "Initializing setting" << this->getKey();
|
||||
const_cast<Interface*>(this)->init();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Interface::save() {
|
||||
auto manager = _manager.lock();
|
||||
if (manager) {
|
||||
manager->saveSetting(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Interface::load() {
|
||||
auto manager = _manager.lock();
|
||||
if (manager) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Clement on 2/2/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -16,6 +17,9 @@
|
|||
#include <QtCore/QWeakPointer>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(settings_interface)
|
||||
|
||||
namespace Setting {
|
||||
class Manager;
|
||||
|
@ -25,11 +29,11 @@ namespace Setting {
|
|||
class Interface {
|
||||
public:
|
||||
const QString& getKey() const { return _key; }
|
||||
bool isSet() const { return _isSet; }
|
||||
bool isSet() const { return _isSet; }
|
||||
|
||||
virtual void setVariant(const QVariant& variant) = 0;
|
||||
virtual QVariant getVariant() = 0;
|
||||
|
||||
|
||||
protected:
|
||||
Interface(const QString& key) : _key(key) {}
|
||||
virtual ~Interface() = default;
|
||||
|
@ -37,7 +41,7 @@ namespace Setting {
|
|||
void init();
|
||||
void maybeInit() const;
|
||||
void deinit();
|
||||
|
||||
|
||||
void save();
|
||||
void load();
|
||||
|
||||
|
@ -46,7 +50,7 @@ namespace Setting {
|
|||
|
||||
private:
|
||||
mutable bool _isInitialized = false;
|
||||
|
||||
|
||||
friend class Manager;
|
||||
mutable QWeakPointer<Manager> _manager;
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Clement on 2/2/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -17,13 +18,89 @@
|
|||
|
||||
#include "SettingInterface.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(settings_manager, "settings.manager")
|
||||
Q_LOGGING_CATEGORY(settings_writer, "settings.manager.writer")
|
||||
|
||||
namespace Setting {
|
||||
|
||||
|
||||
void WriteWorker::start() {
|
||||
// QSettings seems to have some issues with moving to a new thread.
|
||||
// Make sure the object is only created once the thread is already up and running.
|
||||
init();
|
||||
}
|
||||
|
||||
void WriteWorker::setValue(const QString key, const QVariant value) {
|
||||
//qCDebug(settings_writer) << "Setting config " << key << "to" << value;
|
||||
|
||||
init();
|
||||
|
||||
if (!_qSettings->contains(key) || _qSettings->value(key) != value) {
|
||||
_qSettings->setValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteWorker::removeKey(const QString key) {
|
||||
init();
|
||||
_qSettings->remove(key);
|
||||
}
|
||||
|
||||
void WriteWorker::sync() {
|
||||
//qCDebug(settings_writer) << "Forcing settings sync";
|
||||
init();
|
||||
_qSettings->sync();
|
||||
}
|
||||
|
||||
void WriteWorker::threadFinished() {
|
||||
qCDebug(settings_writer) << "Settings write worker syncing and terminating";
|
||||
sync();
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
void WriteWorker::terminate() {
|
||||
qCDebug(settings_writer) << "Settings write worker being asked to terminate. Syncing and terminating.";
|
||||
sync();
|
||||
this->deleteLater();
|
||||
QThread::currentThread()->exit(0);
|
||||
}
|
||||
|
||||
Manager::Manager(QObject *parent) {
|
||||
WriteWorker *worker = new WriteWorker();
|
||||
|
||||
// We operate purely from memory, and forward all changes to a thread that has writing the
|
||||
// settings as its only job.
|
||||
|
||||
qCDebug(settings_manager) << "Initializing settings write thread";
|
||||
|
||||
_workerThread.setObjectName("Settings Writer");
|
||||
worker->moveToThread(&_workerThread);
|
||||
// connect(&_workerThread, &QThread::started, worker, &WriteWorker::start, Qt::QueuedConnection);
|
||||
|
||||
// All normal connections are queued, so that we're sure they happen asynchronously.
|
||||
connect(&_workerThread, &QThread::finished, worker, &WriteWorker::threadFinished, Qt::QueuedConnection);
|
||||
connect(this, &Manager::valueChanged, worker, &WriteWorker::setValue, Qt::QueuedConnection);
|
||||
connect(this, &Manager::keyRemoved, worker, &WriteWorker::removeKey, Qt::QueuedConnection);
|
||||
connect(this, &Manager::syncRequested, worker, &WriteWorker::sync, Qt::QueuedConnection);
|
||||
|
||||
// This one is blocking because we want to wait until it's actually processed.
|
||||
connect(this, &Manager::terminationRequested, worker, &WriteWorker::terminate, Qt::BlockingQueuedConnection);
|
||||
|
||||
|
||||
_workerThread.start();
|
||||
|
||||
// Load all current settings
|
||||
QSettings settings;
|
||||
_fileName = settings.fileName();
|
||||
|
||||
for(QString key : settings.allKeys()) {
|
||||
//qCDebug(settings_manager) << "Loaded key" << key << "with value" << settings.value(key);
|
||||
_settings[key] = settings.value(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Manager::~Manager() {
|
||||
// Cleanup timer
|
||||
stopTimer();
|
||||
delete _saveTimer;
|
||||
_saveTimer = nullptr;
|
||||
|
||||
}
|
||||
|
||||
// Custom deleter does nothing, because we need to shutdown later than the dependency manager
|
||||
|
@ -33,7 +110,7 @@ namespace Setting {
|
|||
const QString& key = handle->getKey();
|
||||
withWriteLock([&] {
|
||||
if (_handles.contains(key)) {
|
||||
qWarning() << "Setting::Manager::registerHandle(): Key registered more than once, overriding: " << key;
|
||||
qCWarning(settings_manager) << "Setting::Manager::registerHandle(): Key registered more than once, overriding: " << key;
|
||||
}
|
||||
_handles.insert(key, handle);
|
||||
});
|
||||
|
@ -47,13 +124,10 @@ namespace Setting {
|
|||
|
||||
void Manager::loadSetting(Interface* handle) {
|
||||
const auto& key = handle->getKey();
|
||||
|
||||
withWriteLock([&] {
|
||||
QVariant loadedValue;
|
||||
if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) {
|
||||
loadedValue = _pendingChanges[key];
|
||||
} else {
|
||||
loadedValue = _qSettings.value(key);
|
||||
}
|
||||
QVariant loadedValue = _settings[key];
|
||||
|
||||
if (loadedValue.isValid()) {
|
||||
handle->setVariant(loadedValue);
|
||||
}
|
||||
|
@ -63,144 +137,76 @@ namespace Setting {
|
|||
|
||||
void Manager::saveSetting(Interface* handle) {
|
||||
const auto& key = handle->getKey();
|
||||
QVariant handleValue = UNSET_VALUE;
|
||||
|
||||
if (handle->isSet()) {
|
||||
handleValue = handle->getVariant();
|
||||
QVariant handleValue = handle->getVariant();
|
||||
|
||||
withWriteLock([&] {
|
||||
_settings[key] = handleValue;
|
||||
});
|
||||
|
||||
emit valueChanged(key, handleValue);
|
||||
} else {
|
||||
withWriteLock([&] {
|
||||
_settings.remove(key);
|
||||
});
|
||||
|
||||
emit keyRemoved(key);
|
||||
}
|
||||
|
||||
withWriteLock([&] {
|
||||
_pendingChanges[key] = handleValue;
|
||||
});
|
||||
}
|
||||
|
||||
static const int SAVE_INTERVAL_MSEC = 5 * 1000; // 5 sec
|
||||
void Manager::startTimer() {
|
||||
if (!_saveTimer) {
|
||||
_saveTimer = new QTimer(this);
|
||||
Q_CHECK_PTR(_saveTimer);
|
||||
_saveTimer->setSingleShot(true); // We will restart it once settings are saved.
|
||||
_saveTimer->setInterval(SAVE_INTERVAL_MSEC); // 5s, Qt::CoarseTimer acceptable
|
||||
connect(_saveTimer, SIGNAL(timeout()), this, SLOT(saveAll()));
|
||||
}
|
||||
_saveTimer->start();
|
||||
void Manager::forceSave() {
|
||||
emit syncRequested();
|
||||
}
|
||||
|
||||
void Manager::stopTimer() {
|
||||
if (_saveTimer) {
|
||||
_saveTimer->stop();
|
||||
}
|
||||
}
|
||||
void Manager::terminateThread() {
|
||||
qCDebug(settings_manager) << "Terminating settings writer thread";
|
||||
|
||||
void Manager::saveAll() {
|
||||
withWriteLock([&] {
|
||||
bool forceSync = false;
|
||||
for (auto key : _pendingChanges.keys()) {
|
||||
auto newValue = _pendingChanges[key];
|
||||
auto savedValue = _qSettings.value(key, UNSET_VALUE);
|
||||
if (newValue == savedValue) {
|
||||
continue;
|
||||
}
|
||||
forceSync = true;
|
||||
if (newValue == UNSET_VALUE || !newValue.isValid()) {
|
||||
_qSettings.remove(key);
|
||||
} else {
|
||||
_qSettings.setValue(key, newValue);
|
||||
}
|
||||
}
|
||||
_pendingChanges.clear();
|
||||
emit terminationRequested(); // This blocks
|
||||
|
||||
if (forceSync) {
|
||||
_qSettings.sync();
|
||||
}
|
||||
});
|
||||
|
||||
// Restart timer
|
||||
if (_saveTimer) {
|
||||
_saveTimer->start();
|
||||
}
|
||||
_workerThread.exit();
|
||||
_workerThread.wait(THREAD_TERMINATION_TIMEOUT);
|
||||
qCDebug(settings_manager) << "Settings writer terminated";
|
||||
}
|
||||
|
||||
QString Manager::fileName() const {
|
||||
return resultWithReadLock<QString>([&] {
|
||||
return _qSettings.fileName();
|
||||
return _fileName;
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::remove(const QString &key) {
|
||||
withWriteLock([&] {
|
||||
_qSettings.remove(key);
|
||||
_settings.remove(key);
|
||||
});
|
||||
}
|
||||
|
||||
QStringList Manager::childGroups() const {
|
||||
return resultWithReadLock<QStringList>([&] {
|
||||
return _qSettings.childGroups();
|
||||
});
|
||||
}
|
||||
|
||||
QStringList Manager::childKeys() const {
|
||||
return resultWithReadLock<QStringList>([&] {
|
||||
return _qSettings.childKeys();
|
||||
});
|
||||
emit keyRemoved(key);
|
||||
}
|
||||
|
||||
QStringList Manager::allKeys() const {
|
||||
return resultWithReadLock<QStringList>([&] {
|
||||
return _qSettings.allKeys();
|
||||
return _settings.keys();
|
||||
});
|
||||
}
|
||||
|
||||
bool Manager::contains(const QString &key) const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _qSettings.contains(key);
|
||||
});
|
||||
}
|
||||
|
||||
int Manager::beginReadArray(const QString &prefix) {
|
||||
return resultWithReadLock<int>([&] {
|
||||
return _qSettings.beginReadArray(prefix);
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::beginGroup(const QString &prefix) {
|
||||
withWriteLock([&] {
|
||||
_qSettings.beginGroup(prefix);
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::beginWriteArray(const QString &prefix, int size) {
|
||||
withWriteLock([&] {
|
||||
_qSettings.beginWriteArray(prefix, size);
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::endArray() {
|
||||
withWriteLock([&] {
|
||||
_qSettings.endArray();
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::endGroup() {
|
||||
withWriteLock([&] {
|
||||
_qSettings.endGroup();
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::setArrayIndex(int i) {
|
||||
withWriteLock([&] {
|
||||
_qSettings.setArrayIndex(i);
|
||||
return _settings.contains(key);
|
||||
});
|
||||
}
|
||||
|
||||
void Manager::setValue(const QString &key, const QVariant &value) {
|
||||
withWriteLock([&] {
|
||||
_qSettings.setValue(key, value);
|
||||
_settings[key] = value;
|
||||
});
|
||||
|
||||
emit valueChanged(key, value);
|
||||
}
|
||||
|
||||
QVariant Manager::value(const QString &key, const QVariant &defaultValue) const {
|
||||
return resultWithReadLock<QVariant>([&] {
|
||||
return _qSettings.value(key, defaultValue);
|
||||
return _settings.value(key, defaultValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
//
|
||||
// Created by Clement on 2/2/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -17,35 +18,169 @@
|
|||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include "DependencyManager.h"
|
||||
#include "shared/ReadWriteLockable.h"
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(settings_manager)
|
||||
Q_DECLARE_LOGGING_CATEGORY(settings_writer)
|
||||
|
||||
// This is for the testing system.
|
||||
class SettingsTests;
|
||||
class SettingsTestsWorker;
|
||||
|
||||
|
||||
|
||||
namespace Setting {
|
||||
class Interface;
|
||||
|
||||
/**
|
||||
* @brief Settings write worker
|
||||
*
|
||||
* This class is used by Setting::Manager to write settings to permanent storage
|
||||
* without blocking anything else. It receives setting updates, and writes them
|
||||
* to disk whenever convenient.
|
||||
*
|
||||
* All communication to this class must be done over queued connections.
|
||||
*
|
||||
* This class is purely an implementation detail and shouldn't be used outside of Setting::Manager.
|
||||
*
|
||||
*/
|
||||
class WriteWorker : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
* @brief Initialize anything that needs initializing, called on thread start.
|
||||
*
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Sets a configuration value
|
||||
*
|
||||
* @param key Configuration key
|
||||
* @param value Configuration value
|
||||
*/
|
||||
void setValue(const QString key, const QVariant value);
|
||||
|
||||
/**
|
||||
* @brief Remove a value from the configuration
|
||||
*
|
||||
* @param key Key to remove
|
||||
*/
|
||||
void removeKey(const QString key);
|
||||
|
||||
/**
|
||||
* @brief Force writing the config to disk
|
||||
*
|
||||
*/
|
||||
void sync();
|
||||
|
||||
/**
|
||||
* @brief Called when the thread is terminating
|
||||
*
|
||||
*/
|
||||
void threadFinished();
|
||||
|
||||
/**
|
||||
* @brief Thread is being asked to finish work and quit
|
||||
*
|
||||
*/
|
||||
void terminate();
|
||||
|
||||
private:
|
||||
|
||||
void init() {
|
||||
if (!_qSettings) {
|
||||
_qSettings = new QSettings();
|
||||
}
|
||||
}
|
||||
|
||||
QSettings* _qSettings = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Settings manager
|
||||
*
|
||||
* This class is the main implementation of the settings system, and the container
|
||||
* of the current configuration.
|
||||
*
|
||||
* Most users should either use the Setting::Handle or the Settings classes instead,
|
||||
* both of which talk to the single global instance of this class.
|
||||
*
|
||||
* The class is thread-safe, and delegates config writing to a separate thread. It
|
||||
* is safe to change settings as often as it might be needed.
|
||||
*
|
||||
*/
|
||||
class Manager : public QObject, public ReadWriteLockable, public Dependency {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Manager(QObject *parent = nullptr);
|
||||
|
||||
void customDeleter() override;
|
||||
|
||||
// thread-safe proxies into QSettings
|
||||
/**
|
||||
* @brief Returns the filename where the config file will be written
|
||||
*
|
||||
* @return QString Path to the config file
|
||||
*/
|
||||
QString fileName() const;
|
||||
|
||||
/**
|
||||
* @brief Remove a configuration key
|
||||
*
|
||||
* @param key Key to remove
|
||||
*/
|
||||
void remove(const QString &key);
|
||||
QStringList childGroups() const;
|
||||
QStringList childKeys() const;
|
||||
|
||||
/**
|
||||
* @brief Lists all keys in the configuration
|
||||
*
|
||||
* @return QStringList List of keys
|
||||
*/
|
||||
QStringList allKeys() const;
|
||||
|
||||
/**
|
||||
* @brief Returns whether a key is part of the configuration
|
||||
*
|
||||
* @param key Key to look for
|
||||
* @return true Key is in the configuration
|
||||
* @return false Key isn't in the configuration
|
||||
*/
|
||||
bool contains(const QString &key) const;
|
||||
int beginReadArray(const QString &prefix);
|
||||
void beginGroup(const QString &prefix);
|
||||
void beginWriteArray(const QString &prefix, int size = -1);
|
||||
void endArray();
|
||||
void endGroup();
|
||||
void setArrayIndex(int i);
|
||||
|
||||
/**
|
||||
* @brief Set a setting to a value
|
||||
*
|
||||
* @param key Setting to set
|
||||
* @param value Value
|
||||
*/
|
||||
void setValue(const QString &key, const QVariant &value);
|
||||
|
||||
/**
|
||||
* @brief Returns the value of a setting
|
||||
*
|
||||
* @param key Setting to look for
|
||||
* @param defaultValue Default value to return, if the setting has no value
|
||||
* @return QVariant Current value of the setting, of defaultValue.
|
||||
*/
|
||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief How long to wait for writer thread termination
|
||||
*
|
||||
* We probably want a timeout here since we don't want to block shutdown indefinitely in case of
|
||||
* any weirdness.
|
||||
*/
|
||||
const int THREAD_TERMINATION_TIMEOUT = 2000;
|
||||
|
||||
~Manager();
|
||||
void registerHandle(Interface* handle);
|
||||
void removeHandle(const QString& key);
|
||||
|
@ -53,24 +188,41 @@ namespace Setting {
|
|||
void loadSetting(Interface* handle);
|
||||
void saveSetting(Interface* handle);
|
||||
|
||||
private slots:
|
||||
void startTimer();
|
||||
void stopTimer();
|
||||
|
||||
void saveAll();
|
||||
/**
|
||||
* @brief Force saving the config to disk.
|
||||
*
|
||||
* Normally unnecessary to use. Asynchronous.
|
||||
*/
|
||||
void forceSave();
|
||||
|
||||
/**
|
||||
* @brief Write config to disk and terminate the writer thread
|
||||
*
|
||||
* This is part of the shutdown process.
|
||||
*/
|
||||
void terminateThread();
|
||||
|
||||
signals:
|
||||
void valueChanged(const QString key, QVariant value);
|
||||
void keyRemoved(const QString key);
|
||||
void syncRequested();
|
||||
void terminationRequested();
|
||||
|
||||
private:
|
||||
QHash<QString, Interface*> _handles;
|
||||
QPointer<QTimer> _saveTimer = nullptr;
|
||||
const QVariant UNSET_VALUE { QUuid::createUuid() };
|
||||
QHash<QString, QVariant> _pendingChanges;
|
||||
|
||||
friend class Interface;
|
||||
friend class ::SettingsTests;
|
||||
friend class ::SettingsTestsWorker;
|
||||
|
||||
friend void cleanupSettingsSaveThread();
|
||||
friend void setupSettingsSaveThread();
|
||||
|
||||
|
||||
QSettings _qSettings;
|
||||
QHash<QString, QVariant> _settings;
|
||||
QString _fileName;
|
||||
QThread _workerThread;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
|
||||
# Declare dependencies
|
||||
macro (setup_testcase_dependencies)
|
||||
setup_memory_debugger()
|
||||
setup_thread_debugger()
|
||||
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared test-utils)
|
||||
|
|
226
tests/shared/src/SettingsTests.cpp
Normal file
226
tests/shared/src/SettingsTests.cpp
Normal file
|
@ -0,0 +1,226 @@
|
|||
//
|
||||
// Created by Dale Glass on 2022/10/22
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "SettingsTests.h"
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
#include <SettingHandle.h>
|
||||
#include <SettingManager.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <QDebug>
|
||||
#include <QCoreApplication>
|
||||
#include "SettingInterface.h"
|
||||
#include "SettingHandle.h"
|
||||
|
||||
|
||||
|
||||
QTEST_MAIN(SettingsTests)
|
||||
|
||||
void SettingsTestsWorker::saveSettings() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
QThread *thread = QThread::currentThread();
|
||||
|
||||
while(! thread->isInterruptionRequested() ) {
|
||||
//qDebug() << "Thread is saving config";
|
||||
sm->forceSave();
|
||||
|
||||
// Not having any wait here for some reason locks up the benchmark.
|
||||
// Logging a message also does the trick.
|
||||
//
|
||||
// This looks like a bug somewhere and needs investigating.
|
||||
thread->yieldCurrentThread();
|
||||
}
|
||||
|
||||
thread->exit(0);
|
||||
}
|
||||
|
||||
void SettingsTests::initTestCase() {
|
||||
QCoreApplication::setOrganizationName("OverteTest");
|
||||
|
||||
DependencyManager::set<Setting::Manager>();
|
||||
|
||||
Setting::init();
|
||||
}
|
||||
|
||||
void SettingsTests::cleanupTestCase() {
|
||||
// Setting::cleanupSettingsSaveThread();
|
||||
}
|
||||
|
||||
void SettingsTests::loadSettings() {
|
||||
Settings s;
|
||||
qDebug() << "Loaded" << s.fileName();
|
||||
}
|
||||
|
||||
void SettingsTests::saveSettings() {
|
||||
Settings s;
|
||||
s.setValue("TestValue", "Hello");
|
||||
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
sm->setValue("TestValueSM", "Hello");
|
||||
|
||||
// There seems to be a bug here, data gets lost without this call here.
|
||||
sm->forceSave();
|
||||
qDebug() << "Wrote" << s.fileName();
|
||||
}
|
||||
|
||||
void SettingsTests::testSettings() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
Settings s;
|
||||
|
||||
s.setValue("settingsTest", 1);
|
||||
QVERIFY(sm->value("settingsTest") == 1);
|
||||
|
||||
QVERIFY(!sm->contains("nonExistingKey"));
|
||||
QVERIFY(sm->value("nonExistingKey") == QVariant());
|
||||
|
||||
}
|
||||
|
||||
void SettingsTests::testGroups() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
Settings s;
|
||||
|
||||
s.setValue("valueNotInGroupBefore", 1);
|
||||
s.beginGroup("testGroup");
|
||||
s.setValue("valueInGroup", 2);
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup("testGroupFirst");
|
||||
s.beginGroup("testGroupSecond");
|
||||
s.setValue("valueInGroup", 44);
|
||||
s.endGroup();
|
||||
s.endGroup();
|
||||
|
||||
s.setValue("valueNotInGroupAfter", 3);
|
||||
|
||||
QVERIFY(sm->value("valueNotInGroupBefore") == 1);
|
||||
QVERIFY(sm->value("testGroup/valueInGroup") == 2);
|
||||
QVERIFY(sm->value("testGroupFirst/testGroupSecond/valueInGroup") == 44);
|
||||
QVERIFY(sm->value("valueNotInGroupAfter") == 3);
|
||||
}
|
||||
|
||||
void SettingsTests::testArray() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
Settings s;
|
||||
|
||||
s.beginWriteArray("testArray", 2);
|
||||
s.setValue("A", 1);
|
||||
s.setValue("B", 2);
|
||||
s.setArrayIndex(1);
|
||||
s.setValue("A", 11);
|
||||
s.setValue("B", 22);
|
||||
s.endArray();
|
||||
|
||||
s.setValue("valueNotInArray", 6);
|
||||
|
||||
QVERIFY(sm->value("testArray/size") == 2);
|
||||
QVERIFY(sm->value("testArray/1/A") == 1);
|
||||
QVERIFY(sm->value("testArray/1/B") == 2);
|
||||
QVERIFY(sm->value("testArray/2/A") == 11);
|
||||
QVERIFY(sm->value("testArray/2/B") == 22);
|
||||
QVERIFY(sm->value("valueNotInArray") == 6);
|
||||
}
|
||||
|
||||
void SettingsTests::testArrayInGroup() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
Settings s;
|
||||
|
||||
s.beginGroup("groupWithArray");
|
||||
s.beginWriteArray("arrayInGroup", 2);
|
||||
s.setValue("X", 10);
|
||||
s.setArrayIndex(1);
|
||||
s.setValue("X", 20);
|
||||
s.endArray();
|
||||
s.endGroup();
|
||||
|
||||
s.setValue("valueNotInArrayOrGroup", 8);
|
||||
|
||||
QVERIFY(sm->value("groupWithArray/arrayInGroup/size") == 2);
|
||||
QVERIFY(sm->value("groupWithArray/arrayInGroup/1/X") == 10);
|
||||
QVERIFY(sm->value("groupWithArray/arrayInGroup/2/X") == 20);
|
||||
QVERIFY(sm->value("valueNotInArrayOrGroup") == 8);
|
||||
}
|
||||
|
||||
void SettingsTests::testHandleUnused() {
|
||||
|
||||
{
|
||||
Setting::Handle<int> testHandle("unused_handle", -1);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::testHandle() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
Setting::Handle<int> testHandle("integer_value", -1);
|
||||
|
||||
QVERIFY(!testHandle.isSet());
|
||||
QVERIFY(testHandle.get() == -1);
|
||||
QVERIFY(testHandle.get(-5) == -5);
|
||||
QVERIFY(testHandle.getDefault() == -1);
|
||||
|
||||
testHandle.set(42);
|
||||
QVERIFY(testHandle.get() == 42);
|
||||
QVERIFY(testHandle.isSet());
|
||||
QVERIFY(sm->value("integer_value") == 42);
|
||||
|
||||
testHandle.reset();
|
||||
QVERIFY(testHandle.get() == -1);
|
||||
QVERIFY(testHandle.isSet());
|
||||
QVERIFY(sm->value("integer_value") == -1);
|
||||
|
||||
testHandle.remove();
|
||||
QVERIFY(!testHandle.isSet());
|
||||
}
|
||||
|
||||
|
||||
void SettingsTests::benchmarkSetValue() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
int i = 0;
|
||||
|
||||
QBENCHMARK {
|
||||
sm->setValue("BenchmarkSetValue", ++i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SettingsTests::benchmarkSaveSettings() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
int i = 0;
|
||||
|
||||
QBENCHMARK {
|
||||
sm->setValue("BenchmarkSave", ++i);
|
||||
sm->forceSave();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void SettingsTests::benchmarkSetValueConcurrent() {
|
||||
auto sm = DependencyManager::get<Setting::Manager>();
|
||||
int i = 0;
|
||||
|
||||
_settingsThread = new QThread(qApp);
|
||||
_testWorker = new SettingsTestsWorker();
|
||||
|
||||
_settingsThread->setObjectName("Save thread");
|
||||
_testWorker->moveToThread(_settingsThread);
|
||||
|
||||
QObject::connect(_settingsThread, &QThread::started, _testWorker, &SettingsTestsWorker::saveSettings, Qt::QueuedConnection );
|
||||
|
||||
_settingsThread->start();
|
||||
QBENCHMARK {
|
||||
sm->setValue("BenchmarkSetValueConcurrent", ++i);
|
||||
}
|
||||
|
||||
_settingsThread->requestInterruption();
|
||||
_settingsThread->wait();
|
||||
|
||||
delete _testWorker;
|
||||
delete _settingsThread;
|
||||
}
|
||||
|
53
tests/shared/src/SettingsTests.h
Normal file
53
tests/shared/src/SettingsTests.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// Created by Dale Glass 2022/10/22
|
||||
// Copyright 2022 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef overte_SettingsTests_h
|
||||
#define overte_SettingsTests_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
|
||||
|
||||
|
||||
class SettingsTestsWorker : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
void saveSettings();
|
||||
};
|
||||
|
||||
class SettingsTests : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
void testSettings();
|
||||
void testGroups();
|
||||
void testArray();
|
||||
void testArrayInGroup();
|
||||
|
||||
void testHandleUnused();
|
||||
void testHandle();
|
||||
|
||||
|
||||
void benchmarkSetValue();
|
||||
void benchmarkSaveSettings();
|
||||
void benchmarkSetValueConcurrent();
|
||||
|
||||
void cleanupTestCase();
|
||||
|
||||
private:
|
||||
QThread *_settingsThread = nullptr;
|
||||
SettingsTestsWorker *_testWorker = nullptr;
|
||||
|
||||
};
|
||||
|
||||
#endif // overte_SettingsTests_h
|
Loading…
Reference in a new issue