Make StreamingPreferences a proper singleton

This removes the need for several hacks in SettingsView to force updates and improves performance by not reloading preferences all over the place.
This commit is contained in:
Cameron Gutman 2024-04-14 13:01:30 -05:00
parent ebe270bec5
commit d1ccd19fcc
9 changed files with 68 additions and 56 deletions

View file

@ -1,7 +1,7 @@
#include "computermanager.h" #include "computermanager.h"
#include "boxartmanager.h" #include "boxartmanager.h"
#include "nvhttp.h" #include "nvhttp.h"
#include "settings/streamingpreferences.h" #include "nvpairingmanager.h"
#include <Limelight.h> #include <Limelight.h>
#include <QtEndian> #include <QtEndian>
@ -144,8 +144,8 @@ private:
NvComputer* m_Computer; NvComputer* m_Computer;
}; };
ComputerManager::ComputerManager(QObject *parent) ComputerManager::ComputerManager(StreamingPreferences* prefs)
: QObject(parent), : m_Prefs(prefs),
m_PollingRef(0), m_PollingRef(0),
m_MdnsBrowser(nullptr), m_MdnsBrowser(nullptr),
m_CompatFetcher(nullptr), m_CompatFetcher(nullptr),
@ -352,9 +352,7 @@ void ComputerManager::startPolling()
return; return;
} }
StreamingPreferences prefs; if (m_Prefs->enableMdns) {
if (prefs.enableMdns) {
// Start an MDNS query for GameStream hosts // Start an MDNS query for GameStream hosts
m_MdnsServer.reset(new QMdnsEngine::Server()); m_MdnsServer.reset(new QMdnsEngine::Server());
m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local."); m_MdnsBrowser = new QMdnsEngine::Browser(m_MdnsServer.data(), "_nvstream._tcp.local.");
@ -784,10 +782,9 @@ private:
return serverInfo; return serverInfo;
} catch (...) { } catch (...) {
if (!m_Mdns) { if (!m_Mdns) {
StreamingPreferences prefs;
unsigned int portTestResult; unsigned int portTestResult;
if (prefs.detectNetworkBlocking) { if (m_ComputerManager->m_Prefs->detectNetworkBlocking) {
// We failed to connect to the specified PC. Let's test to make sure this network // We failed to connect to the specified PC. Let's test to make sure this network
// isn't blocking Moonlight, so we can tell the user about it. // isn't blocking Moonlight, so we can tell the user about it.
portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443, portTestResult = LiTestClientConnectivity("qt.conntest.moonlight-stream.org", 443,

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "nvcomputer.h" #include "nvcomputer.h"
#include "nvpairingmanager.h" #include "settings/streamingpreferences.h"
#include "settings/compatfetcher.h" #include "settings/compatfetcher.h"
#include <qmdnsengine/server.h> #include <qmdnsengine/server.h>
@ -218,7 +218,7 @@ class ComputerManager : public QObject
friend class DelayedFlushThread; friend class DelayedFlushThread;
public: public:
explicit ComputerManager(QObject *parent = nullptr); explicit ComputerManager(StreamingPreferences* prefs);
virtual ~ComputerManager(); virtual ~ComputerManager();
@ -270,6 +270,7 @@ private:
void startPollingComputer(NvComputer* computer); void startPollingComputer(NvComputer* computer);
StreamingPreferences* m_Prefs;
int m_PollingRef; int m_PollingRef;
QReadWriteLock m_Lock; QReadWriteLock m_Lock;
QMap<QString, NvComputer*> m_KnownHosts; QMap<QString, NvComputer*> m_KnownHosts;

View file

@ -1349,15 +1349,7 @@ Flickable {
font.pointSize: 12 font.pointSize: 12
checked: StreamingPreferences.swapFaceButtons checked: StreamingPreferences.swapFaceButtons
onCheckedChanged: { onCheckedChanged: {
// Check if the value changed (this is called on init too) StreamingPreferences.swapFaceButtons = checked
if (StreamingPreferences.swapFaceButtons !== checked) {
StreamingPreferences.swapFaceButtons = checked
// Save and restart SdlGamepadKeyNavigation so it can pull the new value
StreamingPreferences.save()
SdlGamepadKeyNavigation.disable()
SdlGamepadKeyNavigation.enable()
}
} }
ToolTip.delay: 1000 ToolTip.delay: 1000
@ -1621,10 +1613,6 @@ Flickable {
if (StreamingPreferences.enableMdns != checked) { if (StreamingPreferences.enableMdns != checked) {
StreamingPreferences.enableMdns = checked StreamingPreferences.enableMdns = checked
// We must save the updated preference to ensure
// ComputerManager can observe the change internally.
StreamingPreferences.save()
// Restart polling so the mDNS change takes effect // Restart polling so the mDNS change takes effect
if (window.pollingActive) { if (window.pollingActive) {
ComputerManager.stopPollingAsync() ComputerManager.stopPollingAsync()
@ -1641,15 +1629,7 @@ Flickable {
font.pointSize: 12 font.pointSize: 12
checked: StreamingPreferences.detectNetworkBlocking checked: StreamingPreferences.detectNetworkBlocking
onCheckedChanged: { onCheckedChanged: {
// This is called on init, so only do the work if we've StreamingPreferences.detectNetworkBlocking = checked
// actually changed the value.
if (StreamingPreferences.detectNetworkBlocking != checked) {
StreamingPreferences.detectNetworkBlocking = checked
// We must save the updated preference to ensure
// ComputerManager can observe the change internally.
StreamingPreferences.save()
}
} }
} }
} }

View file

@ -8,8 +8,9 @@
#define AXIS_NAVIGATION_REPEAT_DELAY 150 #define AXIS_NAVIGATION_REPEAT_DELAY 150
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation() SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
: m_Enabled(false), : m_Prefs(prefs),
m_Enabled(false),
m_UiNavMode(false), m_UiNavMode(false),
m_FirstPoll(false), m_FirstPoll(false),
m_LastAxisNavigationEventTime(0) m_LastAxisNavigationEventTime(0)
@ -119,7 +120,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
QEvent::Type::KeyPress : QEvent::Type::KeyRelease; QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
// Swap face buttons if needed // Swap face buttons if needed
if (m_Prefs.swapFaceButtons) { if (m_Prefs->swapFaceButtons) {
switch (event.cbutton.button) { switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_A: case SDL_CONTROLLER_BUTTON_A:
event.cbutton.button = SDL_CONTROLLER_BUTTON_B; event.cbutton.button = SDL_CONTROLLER_BUTTON_B;

View file

@ -12,7 +12,7 @@ class SdlGamepadKeyNavigation : public QObject
Q_OBJECT Q_OBJECT
public: public:
SdlGamepadKeyNavigation(); SdlGamepadKeyNavigation(StreamingPreferences* prefs);
~SdlGamepadKeyNavigation(); ~SdlGamepadKeyNavigation();
@ -31,11 +31,11 @@ private slots:
void onPollingTimerFired(); void onPollingTimerFired();
private: private:
StreamingPreferences* m_Prefs;
QTimer* m_PollingTimer; QTimer* m_PollingTimer;
QList<SDL_GameController*> m_Gamepads; QList<SDL_GameController*> m_Gamepads;
bool m_Enabled; bool m_Enabled;
bool m_UiNavMode; bool m_UiNavMode;
bool m_FirstPoll; bool m_FirstPoll;
Uint32 m_LastAxisNavigationEventTime; Uint32 m_LastAxisNavigationEventTime;
StreamingPreferences m_Prefs;
}; };

View file

@ -570,8 +570,7 @@ int main(int argc, char *argv[])
runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch); runtimeVersion.major, runtimeVersion.minor, runtimeVersion.patch);
// Apply the initial translation based on user preference // Apply the initial translation based on user preference
StreamingPreferences prefs; StreamingPreferences::get()->retranslate();
prefs.retranslate();
// Trickily declare the translation for dialog buttons // Trickily declare the translation for dialog buttons
QCoreApplication::translate("QPlatformTheme", "&Yes"); QCoreApplication::translate("QPlatformTheme", "&Yes");
@ -636,8 +635,8 @@ int main(int argc, char *argv[])
qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML"); qmlRegisterUncreatableType<Session>("Session", 1, 0, "Session", "Session cannot be created from QML");
qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0, qmlRegisterSingletonType<ComputerManager>("ComputerManager", 1, 0,
"ComputerManager", "ComputerManager",
[](QQmlEngine*, QJSEngine*) -> QObject* { [](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new ComputerManager(); return new ComputerManager(StreamingPreferences::get(qmlEngine));
}); });
qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0, qmlRegisterSingletonType<AutoUpdateChecker>("AutoUpdateChecker", 1, 0,
"AutoUpdateChecker", "AutoUpdateChecker",
@ -651,13 +650,13 @@ int main(int argc, char *argv[])
}); });
qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0, qmlRegisterSingletonType<SdlGamepadKeyNavigation>("SdlGamepadKeyNavigation", 1, 0,
"SdlGamepadKeyNavigation", "SdlGamepadKeyNavigation",
[](QQmlEngine*, QJSEngine*) -> QObject* { [](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new SdlGamepadKeyNavigation(); return new SdlGamepadKeyNavigation(StreamingPreferences::get(qmlEngine));
}); });
qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0, qmlRegisterSingletonType<StreamingPreferences>("StreamingPreferences", 1, 0,
"StreamingPreferences", "StreamingPreferences",
[](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* { [](QQmlEngine* qmlEngine, QJSEngine*) -> QObject* {
return new StreamingPreferences(qmlEngine); return StreamingPreferences::get(qmlEngine);
}); });
// Create the identity manager on the main thread // Create the identity manager on the main thread
@ -688,7 +687,7 @@ int main(int argc, char *argv[])
case GlobalCommandLineParser::StreamRequested: case GlobalCommandLineParser::StreamRequested:
{ {
initialView = "qrc:/gui/CliStartStreamSegue.qml"; initialView = "qrc:/gui/CliStartStreamSegue.qml";
StreamingPreferences* preferences = new StreamingPreferences(&app); StreamingPreferences* preferences = StreamingPreferences::get();
StreamCommandLineParser streamParser; StreamCommandLineParser streamParser;
streamParser.parse(app.arguments(), preferences); streamParser.parse(app.arguments(), preferences);
QString host = streamParser.getHost(); QString host = streamParser.getHost();
@ -720,7 +719,7 @@ int main(int argc, char *argv[])
ListCommandLineParser listParser; ListCommandLineParser listParser;
listParser.parse(app.arguments()); listParser.parse(app.arguments());
auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app); auto launcher = new CliListApps::Launcher(listParser.getHost(), listParser, &app);
launcher->execute(new ComputerManager(&app)); launcher->execute(new ComputerManager(StreamingPreferences::get()));
hasGUI = false; hasGUI = false;
break; break;
} }

View file

@ -5,6 +5,7 @@
#include <QTranslator> #include <QTranslator>
#include <QCoreApplication> #include <QCoreApplication>
#include <QLocale> #include <QLocale>
#include <QReadWriteLock>
#include <QtMath> #include <QtMath>
#include <QtDebug> #include <QtDebug>
@ -48,18 +49,50 @@
#define CURRENT_DEFAULT_VER 2 #define CURRENT_DEFAULT_VER 2
StreamingPreferences::StreamingPreferences(QObject *parent) static StreamingPreferences* s_GlobalPrefs;
: QObject(parent), static QReadWriteLock s_GlobalPrefsLock;
m_QmlEngine(nullptr)
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine)
: m_QmlEngine(qmlEngine)
{ {
reload(); reload();
} }
StreamingPreferences::StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent) StreamingPreferences* StreamingPreferences::get(QQmlEngine *qmlEngine)
: QObject(parent),
m_QmlEngine(qmlEngine)
{ {
reload(); {
QReadLocker readGuard(&s_GlobalPrefsLock);
// If we have a preference object and it's associated with a QML engine or
// if the caller didn't specify a QML engine, return the existing object.
if (s_GlobalPrefs && (s_GlobalPrefs->m_QmlEngine || !qmlEngine)) {
// The lifetime logic here relies on the QML engine also being a singleton.
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
return s_GlobalPrefs;
}
}
{
QWriteLocker writeGuard(&s_GlobalPrefsLock);
// If we already have an preference object but the QML engine is now available,
// associate the QML engine with the preferences.
if (s_GlobalPrefs) {
if (!s_GlobalPrefs->m_QmlEngine) {
s_GlobalPrefs->m_QmlEngine = qmlEngine;
}
else {
// We could reach this codepath if another thread raced with us
// and created the object while we were outside the pref lock.
Q_ASSERT(!qmlEngine || s_GlobalPrefs->m_QmlEngine == qmlEngine);
}
}
else {
s_GlobalPrefs = new StreamingPreferences(qmlEngine);
}
return s_GlobalPrefs;
}
} }
void StreamingPreferences::reload() void StreamingPreferences::reload()

View file

@ -9,8 +9,7 @@ class StreamingPreferences : public QObject
Q_OBJECT Q_OBJECT
public: public:
StreamingPreferences(QObject *parent = nullptr); static StreamingPreferences* get(QQmlEngine *qmlEngine = nullptr);
StreamingPreferences(QQmlEngine *qmlEngine, QObject *parent = nullptr);
Q_INVOKABLE static int Q_INVOKABLE static int
getDefaultBitrate(int width, int height, int fps); getDefaultBitrate(int width, int height, int fps);
@ -206,6 +205,8 @@ signals:
void languageChanged(); void languageChanged();
private: private:
explicit StreamingPreferences(QQmlEngine *qmlEngine);
QString getSuffixFromLanguage(Language lang); QString getSuffixFromLanguage(Language lang);
QQmlEngine* m_QmlEngine; QQmlEngine* m_QmlEngine;

View file

@ -541,7 +541,7 @@ bool Session::populateDecoderProperties(SDL_Window* window)
} }
Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences) Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *preferences)
: m_Preferences(preferences ? preferences : new StreamingPreferences(this)), : m_Preferences(preferences ? preferences : StreamingPreferences::get()),
m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()), m_IsFullScreen(m_Preferences->windowMode != StreamingPreferences::WM_WINDOWED || !WMUtils::isRunningDesktopEnvironment()),
m_Computer(computer), m_Computer(computer),
m_App(app), m_App(app),