From 059600e8d4e23f2ea17d2f178c46e08410281ca2 Mon Sep 17 00:00:00 2001 From: Dale Glass Date: Sat, 11 Nov 2023 12:32:17 +0100 Subject: [PATCH] Initial effort to reorganize the startup code. This is intended to make things like plugin initialization more sane, and make Qt happier. Qt wants QApplication to start as soon as possible, but our code's attempt to load the entire world in the Application constructor doesn't quite mesh with this. This is an effort to first create Application much earlier, and do as little as possible in the constructor, moving almost all initialization to a later init function. A later stage would be to split the giant mess of init code into more digestible sections that run in some sort of logical order. --- interface/src/Application.cpp | 37 +++++++++++++++++------ interface/src/Application.h | 20 +++++++++++-- interface/src/main.cpp | 55 ++++++++++++++++++++++++++--------- 3 files changed, 87 insertions(+), 25 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 37204b0f9b..042a065a79 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -724,7 +724,7 @@ extern DisplayPluginList getDisplayPlugins(); extern InputPluginList getInputPlugins(); extern void saveInputPluginSettings(const InputPluginList& plugins); -bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, bool runningMarkerExisted) { +bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted) { qInstallMessageHandler(messageHandler); @@ -743,6 +743,7 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b bool previousSessionCrashed { false }; if (!inTestMode) { + // TODO: FIX previousSessionCrashed = CrashRecoveryHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt); } @@ -764,6 +765,7 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b } // Tell the plugin manager about our statically linked plugins + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); auto pluginManager = PluginManager::getInstance(); @@ -777,6 +779,7 @@ bool setupEssentials(int& argc, char** argv, const QCommandLineParser& parser, b oculusPlatform->init(); } + PROFILE_SET_THREAD_NAME("Main Thread"); #if defined(Q_OS_WIN) @@ -993,8 +996,7 @@ bool Application::initMenu() { Application::Application( int& argc, char** argv, const QCommandLineParser& parser, - QElapsedTimer& startupTimer, - bool runningMarkerExisted + QElapsedTimer& startupTimer ) : QApplication(argc, argv), _window(new MainWindow(desktop())), @@ -1004,7 +1006,7 @@ Application::Application( #ifndef Q_OS_ANDROID _logger(new FileLogger(this)), #endif - _previousSessionCrashed(setupEssentials(argc, argv, parser, runningMarkerExisted)), + _previousSessionCrashed(setupEssentials(parser, false)), _entitySimulation(std::make_shared()), _physicsEngine(std::make_shared(Vectors::ZERO)), _entityClipboard(std::make_shared()), @@ -1032,12 +1034,22 @@ Application::Application( _snapshotSound(nullptr), _sampleSound(nullptr) { - auto steamClient = PluginManager::getInstance()->getSteamClientPlugin(); - setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning())); setProperty(hifi::properties::CRASHED, _previousSessionCrashed); LogHandler::getInstance().moveToThread(thread()); LogHandler::getInstance().setupRepeatedMessageFlusher(); +} + +void Application::initialize(const QCommandLineParser &parser) { + + //qCDebug(interfaceapp) << "Setting up essentials"; + //setupEssentials(parser, _previousSessionCrashed); + qCDebug(interfaceapp) << "Initializing application"; + + + auto steamClient = PluginManager::getInstance()->getSteamClientPlugin(); + setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning())); + { if (parser.isSet("testScript")) { @@ -2375,7 +2387,7 @@ Application::Application( connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); connect(_window, SIGNAL(windowMinimizedChanged(bool)), this, SLOT(windowMinimizedChanged(bool))); - qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); + qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)_sessionRunTimer.elapsed() / 1000.0); EntityTreeRenderer::setEntitiesShouldFadeFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); @@ -4201,7 +4213,9 @@ bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) { } bool Application::notify(QObject * object, QEvent * event) { - if (thread() == QThread::currentThread()) { + if (thread() == QThread::currentThread() && _profilingInitialized ) { + // _profilingInitialized gets set once we're reading for profiling. + // this prevents a deadlock due to profiling not working yet PROFILE_RANGE_IF_LONGER(app, "notify", 2) return QApplication::notify(object, event); } @@ -8762,7 +8776,11 @@ void Application::sendLambdaEvent(const std::function& f) { } } -void Application::initPlugins(const QCommandLineParser& parser) { +void Application::configurePlugins(const QCommandLineParser& parser) { + // This must be a member function -- PluginManager must exist, and for that + // QApplication must exist, or it can't find the plugin path, as QCoreApplication:applicationDirPath + // won't work yet. + if (parser.isSet("display")) { auto preferredDisplays = parser.value("display").split(',', Qt::SkipEmptyParts); qInfo() << "Setting prefered display plugins:" << preferredDisplays; @@ -8780,6 +8798,7 @@ void Application::initPlugins(const QCommandLineParser& parser) { qInfo() << "Disabling following input plugins:" << disabledInputs; PluginManager::getInstance()->disableInputs(disabledInputs); } + } void Application::shutdownPlugins() { diff --git a/interface/src/Application.h b/interface/src/Application.h index 82b39e868b..9984855740 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -123,6 +123,19 @@ class Application : public QApplication, friend class OctreePacketProcessor; public: + /** + * @brief Initialize everything + * + * This is a QApplication, and for Qt reasons it's desirable to create this object + * as early as possible. Without that some Qt functions don't work, like QCoreApplication::applicationDirPath() + * + * So we keep the constructor as minimal as possible, and do the rest of the work in + * this function. + */ + void initialize(const QCommandLineParser &parser); + + void setPreviousSessionCrashed(bool value) { _previousSessionCrashed = value; } + // virtual functions required for PluginContainer virtual ui::Menu* getPrimaryMenu() override; virtual void requestReset() override { resetSensors(false); } @@ -136,14 +149,13 @@ public: virtual DisplayPluginPointer getActiveDisplayPlugin() const override; // FIXME? Empty methods, do we still need them? - static void initPlugins(const QCommandLineParser& parser); + void configurePlugins(const QCommandLineParser& parser); static void shutdownPlugins(); Application( int& argc, char** argv, const QCommandLineParser& parser, - QElapsedTimer& startup_time, - bool runningMarkerExisted + QElapsedTimer& startup_time ); ~Application(); @@ -860,5 +872,7 @@ private: bool _crashOnShutdown { false }; DiscordPresence* _discordPresence{ nullptr }; + + bool _profilingInitialized { false }; }; #endif // hifi_Application_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 18da2c2e00..b9501419f1 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -68,6 +68,18 @@ int main(int argc, const char* argv[]) { // Journald by default in user applications is probably a bit too modern still. LogHandler::getInstance().setShouldUseJournald(false); + + // Extend argv to enable WebGL rendering + std::vector argvExtended(&argv[0], &argv[argc]); + argvExtended.push_back("--ignore-gpu-blocklist"); +#ifdef Q_OS_ANDROID + argvExtended.push_back("--suppress-settings-reset"); +#endif + int argcExtended = (int)argvExtended.size(); + + QElapsedTimer startupTime; + startupTime.start(); + QCommandLineParser parser; parser.setApplicationDescription("Overte -- A free/libre and open-source virtual worlds client"); QCommandLineOption helpOption = parser.addHelpOption(); @@ -246,6 +258,15 @@ int main(int argc, const char* argv[]) { "Logging options, comma separated: color,nocolor,process_id,thread_id,milliseconds,keep_repeats,journald,nojournald", "options" ); + QCommandLineOption abortAfterStartupOption( + "abortAfterStartup", + "Debug option. Aborts right after startup." + ); + QCommandLineOption abortAfterInitOption( + "abortAfterInit", + "Debug option. Aborts after initialization, right before the program starts running the event loop." + ); + // "--qmljsdebugger", which appears in output from "--help-all". // Those below don't seem to be optional. // --ignore-gpu-blacklist @@ -288,6 +309,9 @@ int main(int argc, const char* argv[]) { parser.addOption(quitWhenFinishedOption); parser.addOption(fastHeartbeatOption); parser.addOption(logOption); + parser.addOption(abortAfterStartupOption); + parser.addOption(abortAfterInitOption); + QString applicationPath; // A temporary application instance is needed to get the location of the running executable @@ -310,6 +334,16 @@ int main(int argc, const char* argv[]) { #endif } + // TODO: We need settings for Application, but Settings needs an Application + // to handle events. Needs splitting into two parts: enough initialization + // for Application to work, and then thread start afterwards. + Setting::init(); + Application app(argcExtended, const_cast(argvExtended.data()), parser, startupTime); + + if (parser.isSet("abortAfterStartup")) { + return 99; + } + // We want to configure the logging system as early as possible auto& logHandler = LogHandler::getInstance(); @@ -407,10 +441,9 @@ int main(int argc, const char* argv[]) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); #endif - QElapsedTimer startupTime; - startupTime.start(); - Setting::init(); + + // Instance UserActivityLogger now that the settings are loaded auto& ual = UserActivityLogger::getInstance(); @@ -548,7 +581,7 @@ int main(int argc, const char* argv[]) { // Oculus initialization MUST PRECEDE OpenGL context creation. // The nature of the Application constructor means this has to be either here, // or in the main window ctor, before GL startup. - Application::initPlugins(parser); + app.configurePlugins(parser); #ifdef Q_OS_WIN // If we're running in steam mode, we need to do an explicit check to ensure we're up to the required min spec @@ -586,17 +619,10 @@ int main(int argc, const char* argv[]) { SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater); } - // Extend argv to enable WebGL rendering - std::vector argvExtended(&argv[0], &argv[argc]); - argvExtended.push_back("--ignore-gpu-blocklist"); -#ifdef Q_OS_ANDROID - argvExtended.push_back("--suppress-settings-reset"); -#endif - int argcExtended = (int)argvExtended.size(); - PROFILE_SYNC_END(startup, "main startup", ""); PROFILE_SYNC_BEGIN(startup, "app full ctor", ""); - Application app(argcExtended, const_cast(argvExtended.data()), parser, startupTime, runningMarkerExisted); + app.setPreviousSessionCrashed(runningMarkerExisted); + app.initialize(parser); PROFILE_SYNC_END(startup, "app full ctor", ""); #if defined(Q_OS_LINUX) @@ -664,6 +690,9 @@ int main(int argc, const char* argv[]) { translator.load("i18n/interface_en"); app.installTranslator(&translator); qCDebug(interfaceapp, "Created QT Application."); + if (parser.isSet("abortAfterInit")) { + return 99; + } exitCode = app.exec(); server.close();