mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 12:12:56 +02:00
2160 lines
105 KiB
C++
2160 lines
105 KiB
C++
//
|
|
// Application_Setup.cpp
|
|
// interface/src
|
|
//
|
|
// Split from Application.cpp by HifiExperiments on 3/30/24
|
|
// Created by Andrzej Kapolka on 5/10/13.
|
|
// Copyright 2013 High Fidelity, Inc.
|
|
// Copyright 2020 Vircadia contributors.
|
|
// Copyright 2022-2023 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
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
#include "Application.h"
|
|
|
|
#include <functional>
|
|
|
|
#include <QDesktopServices>
|
|
#include <QFontDatabase>
|
|
#include <QtCore/QCommandLineParser>
|
|
#include <QtCore/QResource>
|
|
#include <QtQml/QQmlContext>
|
|
#include <QtQuick/QQuickWindow>
|
|
|
|
#include <AccountManager.h>
|
|
#include <AddressManager.h>
|
|
#include <AnimationCacheScriptingInterface.h>
|
|
#include <AvatarBookmarks.h>
|
|
#include <avatar/AvatarPackager.h>
|
|
#include <avatar/GrabManager.h>
|
|
#include <audio/AudioScope.h>
|
|
#include <AudioScriptingInterface.h>
|
|
#include <AutoUpdater.h>
|
|
#include <avatar/AvatarManager.h>
|
|
#include <BuildInfo.h>
|
|
#include <crash-handler/CrashHandler.h>
|
|
#include <DebugDraw.h>
|
|
#include <DeferredLightingEffect.h>
|
|
#include <DesktopPreviewProvider.h>
|
|
#include <display-plugins/CompositorHelper.h>
|
|
#include <display-plugins/DisplayPlugin.h>
|
|
#include <DomainAccountManager.h>
|
|
#include <EntityScriptClient.h>
|
|
#include <EntityScriptServerLogClient.h>
|
|
#include <FingerprintUtils.h>
|
|
#include <FramebufferCache.h>
|
|
#include <gl/GLHelpers.h>
|
|
#include <GPUIdent.h>
|
|
#include <graphics-scripting/GraphicsScriptingInterface.h>
|
|
#include <hfm/ModelFormatRegistry.h>
|
|
#include <input-plugins/KeyboardMouseDevice.h>
|
|
#include <input-plugins/TouchscreenDevice.h>
|
|
#include <input-plugins/TouchscreenVirtualPadDevice.h>
|
|
#include <networking/CloseEventSender.h>
|
|
#include <MainWindow.h>
|
|
#include <material-networking/TextureCacheScriptingInterface.h>
|
|
#include <MessagesClient.h>
|
|
#include <Midi.h>
|
|
#include <model-networking/ModelCacheScriptingInterface.h>
|
|
#include <OffscreenUi.h>
|
|
#include <PickManager.h>
|
|
#include <plugins/InputConfiguration.h>
|
|
#include <plugins/OculusPlatformPlugin.h>
|
|
#include <plugins/PluginManager.h>
|
|
#include <plugins/SteamClientPlugin.h>
|
|
#include <PointerManager.h>
|
|
#include <Preferences.h>
|
|
#include <procedural/MaterialCacheScriptingInterface.h>
|
|
#include <procedural/ReferenceMaterial.h>
|
|
#include <raypick/MouseTransformNode.h>
|
|
#include <raypick/PickScriptingInterface.h>
|
|
#include <raypick/PointerScriptingInterface.h>
|
|
#include <raypick/RayPick.h>
|
|
#include <raypick/RayPickScriptingInterface.h>
|
|
#include <recording/ClipCache.h>
|
|
#include <recording/Deck.h>
|
|
#include <recording/Frame.h>
|
|
#include <recording/Recorder.h>
|
|
#include <recording/RecordingScriptingInterface.h>
|
|
#include <RenderableEntityItem.h>
|
|
#include <RenderableTextEntityItem.h>
|
|
#include <RenderableWebEntityItem.h>
|
|
#include <ResourceScriptingInterface.h>
|
|
#include <SceneScriptingInterface.h>
|
|
#include <ScriptEngines.h>
|
|
#include <scripting/Audio.h>
|
|
#include <scripting/AssetMappingsScriptingInterface.h>
|
|
#include <scripting/ControllerScriptingInterface.h>
|
|
#include <scripting/DesktopScriptingInterface.h>
|
|
#include <scripting/HMDScriptingInterface.h>
|
|
#include <scripting/KeyboardScriptingInterface.h>
|
|
#include <scripting/SelectionScriptingInterface.h>
|
|
#include <scripting/TestScriptingInterface.h>
|
|
#include <scripting/TTSScriptingInterface.h>
|
|
#include <scripting/WindowScriptingInterface.h>
|
|
#include <ShapeEntityItem.h>
|
|
#ifndef Q_OS_ANDROID
|
|
#include <shared/FileLogger.h>
|
|
#endif
|
|
#include <shared/GlobalAppProperties.h>
|
|
#include <shared/PlatformHelper.h>
|
|
#include <SoundCacheScriptingInterface.h>
|
|
#include <StatTracker.h>
|
|
#include <StencilMaskPass.h>
|
|
#include <ThreadHelpers.h>
|
|
#include <ui/DialogsManager.h>
|
|
#include <ui/DomainConnectionModel.h>
|
|
#include <ui/Keyboard.h>
|
|
#include <ui/LoginDialog.h>
|
|
#include <ui/OctreeStatsProvider.h>
|
|
#include <ui/OffscreenQmlSurfaceCache.h>
|
|
#include <ui/Snapshot.h>
|
|
#include <ui/StandAloneJSConsole.h>
|
|
#include <ui/TabletScriptingInterface.h>
|
|
#include <ui/ToolbarScriptingInterface.h>
|
|
#include <UserActivityLogger.h>
|
|
#include <UserActivityLoggerScriptingInterface.h>
|
|
#include <UsersScriptingInterface.h>
|
|
#include <VirtualPadManager.h>
|
|
|
|
#include "ApplicationEventHandler.h"
|
|
#include "ApplicationMeshProvider.h"
|
|
#include "AudioClient.h"
|
|
#include "CrashRecoveryHandler.h"
|
|
#include "DeadlockWatchdog.h"
|
|
#include "DiscordRichPresence.h"
|
|
#include "DiscoverabilityManager.h"
|
|
#include "FileDialogHelper.h"
|
|
#include "GLCanvas.h"
|
|
#include "InterfaceDynamicFactory.h"
|
|
#include "InterfaceLogging.h"
|
|
#include "InterfaceParentFinder.h"
|
|
#include "LocationBookmarks.h"
|
|
#include "LODManager.h"
|
|
#include "Menu.h"
|
|
#include "ResourceRequestObserver.h"
|
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
|
#include "SpeechRecognizer.h"
|
|
#endif
|
|
#include "Util.h"
|
|
|
|
#if defined(Q_OS_WIN)
|
|
#include <VersionHelpers.h>
|
|
#endif
|
|
|
|
#if defined(Q_OS_ANDROID)
|
|
#include "AndroidHelper.h"
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
/*@jsdoc
|
|
* <p>The <code>Controller.Hardware.Application</code> object has properties representing Interface's state. The property
|
|
* values are integer IDs, uniquely identifying each output. <em>Read-only.</em></p>
|
|
* <p>These states can be mapped to actions or functions or <code>Controller.Standard</code> items in a {@link RouteObject}
|
|
* mapping (e.g., using the {@link RouteObject#when} method). Each data value is either <code>1.0</code> for "true" or
|
|
* <code>0.0</code> for "false".</p>
|
|
* <table>
|
|
* <thead>
|
|
* <tr><th>Property</th><th>Type</th><th>Data</th><th>Description</th></tr>
|
|
* </thead>
|
|
* <tbody>
|
|
* <tr><td><code>CameraFirstPerson</code></td><td>number</td><td>number</td><td>The camera is in first-person mode.
|
|
* <em>Legacy first person camera mode.</em></td></tr>
|
|
* <tr><td><code>CameraFirstPersonLookAt</code></td><td>number</td><td>number</td><td>The camera is in first-person mode.
|
|
* <em>Default first person camera mode.</em></td></tr>
|
|
* <tr><td><code>CameraThirdPerson</code></td><td>number</td><td>number</td><td>The camera is in third-person mode.
|
|
* <em>Legacy third person camera mode.</em></td></tr>
|
|
* <tr><td><code>CameraLookAt</code></td><td>number</td><td>number</td><td>The camera is in third-person mode.
|
|
* <em>Default third person camera mode.</em></td></tr>
|
|
* <tr><td><code>CameraFSM</code></td><td>number</td><td>number</td><td>The camera is in full screen mirror mode.
|
|
* <em>Legacy "look at myself" behavior.</em></td></tr>
|
|
* <tr><td><code>CameraSelfie</code></td><td>number</td><td>number</td><td>The camera is in selfie mode.
|
|
* <em>Default "look at myself" camera mode.</em></td></tr>
|
|
* <tr><td><code>CameraIndependent</code></td><td>number</td><td>number</td><td>The camera is in independent mode.</td></tr>
|
|
* <tr><td><code>CameraEntity</code></td><td>number</td><td>number</td><td>The camera is in entity mode.</td></tr>
|
|
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
|
|
* <tr><td><code>CaptureMouse</code></td><td>number</td><td>number</td><td>The mouse is captured. In this mode,
|
|
* the mouse is invisible and cannot leave the bounds of Interface, as long as Interface is the active window and
|
|
* no menu item is selected.</td></tr>
|
|
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement (walking) controls are
|
|
* enabled.</td></tr>
|
|
* <tr><td><code>StrafeEnabled</code></td><td>number</td><td>number</td><td>Strafing is enabled</td></tr>
|
|
* <tr><td><code>LeftHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to left.</td></tr>
|
|
* <tr><td><code>RightHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to right.</td></tr>
|
|
* <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
|
|
* <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
|
|
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
|
|
* <tr><td><code>PlatformWindows</code></td><td>number</td><td>number</td><td>The operating system is Windows.</td></tr>
|
|
* <tr><td><code>PlatformMac</code></td><td>number</td><td>number</td><td>The operating system is Mac.</td></tr>
|
|
* <tr><td><code>PlatformAndroid</code></td><td>number</td><td>number</td><td>The operating system is Android.</td></tr>
|
|
* </tbody>
|
|
* </table>
|
|
* @typedef {object} Controller.Hardware-Application
|
|
*/
|
|
|
|
static const QString STATE_IN_HMD = "InHMD";
|
|
static const QString STATE_CAMERA_FULL_SCREEN_MIRROR = "CameraFSM";
|
|
static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
|
|
static const QString STATE_CAMERA_FIRST_PERSON_LOOK_AT = "CameraFirstPersonLookat";
|
|
static const QString STATE_CAMERA_THIRD_PERSON = "CameraThirdPerson";
|
|
static const QString STATE_CAMERA_ENTITY = "CameraEntity";
|
|
static const QString STATE_CAMERA_INDEPENDENT = "CameraIndependent";
|
|
static const QString STATE_CAMERA_LOOK_AT = "CameraLookAt";
|
|
static const QString STATE_CAMERA_SELFIE = "CameraSelfie";
|
|
static const QString STATE_SNAP_TURN = "SnapTurn";
|
|
static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
|
|
static const QString STATE_GROUNDED = "Grounded";
|
|
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
|
static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows";
|
|
static const QString STATE_PLATFORM_MAC = "PlatformMac";
|
|
static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid";
|
|
static const QString STATE_LEFT_HAND_DOMINANT = "LeftHandDominant";
|
|
static const QString STATE_RIGHT_HAND_DOMINANT = "RightHandDominant";
|
|
static const QString STATE_STRAFE_ENABLED = "StrafeEnabled";
|
|
static const QString STATE_CAPTURE_MOUSE = "CaptureMouse";
|
|
|
|
static const QUrl AVATAR_INPUTS_BAR_QML = PathUtils::qmlUrl("AvatarInputsBar.qml");
|
|
static const QUrl BUBBLE_ICON_QML = PathUtils::qmlUrl("BubbleIcon.qml");
|
|
|
|
static const int ENTITY_SERVER_ADDED_TIMEOUT = 5000;
|
|
|
|
// For processing on QThreadPool, we target a number of threads after reserving some
|
|
// based on how many are being consumed by the application and the display plugin. However,
|
|
// we will never drop below the 'min' value
|
|
static const int MIN_PROCESSING_THREAD_POOL_SIZE = 2;
|
|
|
|
#if !defined(Q_OS_ANDROID)
|
|
static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16;
|
|
#else
|
|
static const uint32_t MAX_CONCURRENT_RESOURCE_DOWNLOADS = 4;
|
|
#endif
|
|
|
|
#if defined(Q_OS_ANDROID)
|
|
static bool DISABLE_WATCHDOG = true;
|
|
#else
|
|
static const QString DISABLE_WATCHDOG_FLAG{ "HIFI_DISABLE_WATCHDOG" };
|
|
static bool DISABLE_WATCHDOG = nsightActive() || QProcessEnvironment::systemEnvironment().contains(DISABLE_WATCHDOG_FLAG);
|
|
#endif
|
|
static const int WATCHDOG_TIMER_TIMEOUT = 100;
|
|
|
|
#if defined(Q_OS_ANDROID)
|
|
static const QString TESTER_FILE = "/sdcard/_hifi_test_device.txt";
|
|
#endif
|
|
|
|
bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted) {
|
|
const int listenPort = parser.isSet("listenPort") ? parser.value("listenPort").toInt() : INVALID_PORT;
|
|
|
|
bool suppressPrompt = parser.isSet("suppress-settings-reset");
|
|
|
|
// set the OCULUS_STORE property so the oculus plugin can know if we ran from the Oculus Store
|
|
qApp->setProperty(hifi::properties::OCULUS_STORE, parser.isSet("oculus-store"));
|
|
|
|
// emulate standalone device
|
|
qApp->setProperty(hifi::properties::STANDALONE, parser.isSet("standalone"));
|
|
|
|
// Ignore any previous crashes if running from command line with a test script.
|
|
bool inTestMode = parser.isSet("testScript");
|
|
|
|
bool previousSessionCrashed { false };
|
|
if (!inTestMode) {
|
|
// TODO: FIX
|
|
previousSessionCrashed = CrashRecoveryHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt);
|
|
}
|
|
|
|
// get dir to use for cache
|
|
if (parser.isSet("cache")) {
|
|
qApp->setProperty(hifi::properties::APP_LOCAL_DATA_PATH, parser.value("cache"));
|
|
}
|
|
|
|
{
|
|
const QString resourcesBinaryFile = PathUtils::getRccPath();
|
|
qCInfo(interfaceapp) << "Loading primary resources from" << resourcesBinaryFile;
|
|
|
|
if (!QFile::exists(resourcesBinaryFile)) {
|
|
throw std::runtime_error(QString("Unable to find primary resources from '%1'").arg(resourcesBinaryFile).toStdString());
|
|
}
|
|
if (!QResource::registerResource(resourcesBinaryFile)) {
|
|
throw std::runtime_error(QString("Unable to load primary resources from '%1'").arg(resourcesBinaryFile).toStdString());
|
|
}
|
|
}
|
|
|
|
DependencyManager::set<ScriptInitializers>();
|
|
|
|
// Tell the plugin manager about our statically linked plugins
|
|
auto pluginManager = PluginManager::getInstance();
|
|
if (auto steamClient = pluginManager->getSteamClientPlugin()) {
|
|
steamClient->init();
|
|
}
|
|
if (auto oculusPlatform = pluginManager->getOculusPlatformPlugin()) {
|
|
oculusPlatform->init();
|
|
}
|
|
|
|
PROFILE_SET_THREAD_NAME("Main Thread");
|
|
|
|
#if defined(Q_OS_WIN)
|
|
// Select appropriate audio DLL
|
|
QString audioDLLPath = QCoreApplication::applicationDirPath();
|
|
if (IsWindows8OrGreater()) {
|
|
audioDLLPath += "/audioWin8";
|
|
} else {
|
|
audioDLLPath += "/audioWin7";
|
|
}
|
|
QCoreApplication::addLibraryPath(audioDLLPath);
|
|
#endif
|
|
|
|
QString defaultScriptsOverrideOption = parser.value("defaultScriptsOverride");
|
|
|
|
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
|
DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>();
|
|
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, InterfaceDynamicFactory>();
|
|
DependencyManager::registerInheritance<SpatialParentFinder, InterfaceParentFinder>();
|
|
|
|
// Set dependencies
|
|
DependencyManager::set<PickManager>();
|
|
DependencyManager::set<PointerManager>();
|
|
DependencyManager::set<RayPickScriptingInterface>();
|
|
DependencyManager::set<PointerScriptingInterface>();
|
|
DependencyManager::set<PickScriptingInterface>();
|
|
DependencyManager::set<Cursor::Manager>();
|
|
DependencyManager::set<VirtualPad::Manager>();
|
|
DependencyManager::set<DesktopPreviewProvider>();
|
|
#if defined(Q_OS_ANDROID)
|
|
DependencyManager::set<AccountManager>(true); // use the default user agent getter
|
|
#else
|
|
DependencyManager::set<AccountManager>(true, std::bind(&Application::getUserAgent, qApp));
|
|
#endif
|
|
DependencyManager::set<DomainAccountManager>();
|
|
DependencyManager::set<StatTracker>();
|
|
DependencyManager::set<ScriptEngines>(ScriptManager::CLIENT_SCRIPT, defaultScriptsOverrideOption);
|
|
DependencyManager::set<Preferences>();
|
|
DependencyManager::set<recording::Deck>();
|
|
DependencyManager::set<recording::Recorder>();
|
|
DependencyManager::set<AddressManager>();
|
|
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
|
|
DependencyManager::set<recording::ClipCache>();
|
|
DependencyManager::set<GeometryCache>();
|
|
DependencyManager::set<ModelFormatRegistry>(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor.
|
|
DependencyManager::set<ModelCache>();
|
|
DependencyManager::set<ModelCacheScriptingInterface>();
|
|
DependencyManager::set<ScriptCache>();
|
|
DependencyManager::set<SoundCache>();
|
|
DependencyManager::set<SoundCacheScriptingInterface>();
|
|
DependencyManager::set<AudioClient>();
|
|
DependencyManager::set<AudioScope>();
|
|
DependencyManager::set<DeferredLightingEffect>();
|
|
DependencyManager::set<TextureCache>();
|
|
DependencyManager::set<MaterialCache>();
|
|
DependencyManager::set<TextureCacheScriptingInterface>();
|
|
DependencyManager::set<MaterialCacheScriptingInterface>();
|
|
DependencyManager::set<FramebufferCache>();
|
|
DependencyManager::set<AnimationCache>();
|
|
DependencyManager::set<AnimationCacheScriptingInterface>();
|
|
DependencyManager::set<ModelBlender>();
|
|
DependencyManager::set<UsersScriptingInterface>();
|
|
DependencyManager::set<AvatarManager>();
|
|
DependencyManager::set<LODManager>();
|
|
DependencyManager::set<StandAloneJSConsole>();
|
|
DependencyManager::set<DialogsManager>();
|
|
DependencyManager::set<ResourceCacheSharedItems>();
|
|
DependencyManager::set<DesktopScriptingInterface>();
|
|
DependencyManager::set<EntityScriptingInterface>(true);
|
|
DependencyManager::set<GraphicsScriptingInterface>();
|
|
DependencyManager::registerInheritance<scriptable::ModelProviderFactory, ApplicationMeshProvider>();
|
|
DependencyManager::set<ApplicationMeshProvider>();
|
|
DependencyManager::set<RecordingScriptingInterface>();
|
|
DependencyManager::set<WindowScriptingInterface>();
|
|
DependencyManager::set<HMDScriptingInterface>();
|
|
DependencyManager::set<ResourceScriptingInterface>();
|
|
DependencyManager::set<TabletScriptingInterface>();
|
|
DependencyManager::set<InputConfiguration>();
|
|
DependencyManager::set<ToolbarScriptingInterface>();
|
|
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
|
DependencyManager::set<AssetMappingsScriptingInterface>();
|
|
DependencyManager::set<DomainConnectionModel>();
|
|
|
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
|
DependencyManager::set<SpeechRecognizer>();
|
|
#endif
|
|
DependencyManager::set<DiscoverabilityManager>();
|
|
DependencyManager::set<SceneScriptingInterface>();
|
|
#if !defined(DISABLE_QML)
|
|
DependencyManager::set<OffscreenUi>();
|
|
{
|
|
auto window = DependencyManager::get<OffscreenUi>()->getWindow();
|
|
auto desktopScriptingInterface = DependencyManager::get<DesktopScriptingInterface>();
|
|
QObject::connect(window, &QQuickWindow::focusObjectChanged, [desktopScriptingInterface](QObject *object) {
|
|
if (object) {
|
|
if (object->objectName() == QString("desktop")) {
|
|
emit desktopScriptingInterface->uiFocusChanged(false);
|
|
return;
|
|
}
|
|
// Signal with empty object name happens in addition to regular named ones and is not necessary here
|
|
if (!object->objectName().isEmpty()) {
|
|
emit desktopScriptingInterface->uiFocusChanged(true);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
#endif
|
|
DependencyManager::set<Midi>();
|
|
DependencyManager::set<PathUtils>();
|
|
DependencyManager::set<InterfaceDynamicFactory>();
|
|
DependencyManager::set<AudioInjectorManager>();
|
|
DependencyManager::set<MessagesClient>();
|
|
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
|
|
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_FIRST_PERSON_LOOK_AT, STATE_CAMERA_THIRD_PERSON,
|
|
STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_CAMERA_LOOK_AT, STATE_CAMERA_SELFIE, STATE_CAPTURE_MOUSE,
|
|
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
|
|
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } });
|
|
DependencyManager::set<UserInputMapper>();
|
|
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
|
DependencyManager::set<InterfaceParentFinder>();
|
|
DependencyManager::set<EntityTreeRenderer>(true, qApp, qApp);
|
|
DependencyManager::set<CompositorHelper>();
|
|
DependencyManager::set<OffscreenQmlSurfaceCache>();
|
|
DependencyManager::set<EntityScriptClient>();
|
|
|
|
DependencyManager::set<EntityScriptServerLogClient>();
|
|
|
|
DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
|
|
DependencyManager::set<AvatarBookmarks>();
|
|
DependencyManager::set<LocationBookmarks>();
|
|
DependencyManager::set<Snapshot>();
|
|
DependencyManager::set<CloseEventSender>();
|
|
DependencyManager::set<ResourceManager>();
|
|
DependencyManager::set<SelectionScriptingInterface>();
|
|
DependencyManager::set<TTSScriptingInterface>();
|
|
|
|
DependencyManager::set<ResourceRequestObserver>();
|
|
DependencyManager::set<Keyboard>();
|
|
DependencyManager::set<KeyboardScriptingInterface>();
|
|
DependencyManager::set<GrabManager>();
|
|
DependencyManager::set<AvatarPackager>();
|
|
PlatformHelper::setup();
|
|
|
|
QObject::connect(PlatformHelper::instance(), &PlatformHelper::systemWillWake, [] {
|
|
QMetaObject::invokeMethod(DependencyManager::get<NodeList>().data(), "noteAwakening", Qt::QueuedConnection);
|
|
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "noteAwakening", Qt::QueuedConnection);
|
|
});
|
|
|
|
QString setBookmarkValue = parser.value("setBookmark");
|
|
if (!setBookmarkValue.isEmpty()) {
|
|
// Bookmarks are expected to be in a name=url form.
|
|
// An `=` character in the name or url is unsupported.
|
|
auto parts = setBookmarkValue.split("=");
|
|
if (parts.length() != 2) {
|
|
qWarning() << "Malformed setBookmark argument: " << setBookmarkValue;
|
|
} else {
|
|
qDebug() << "Setting bookmark" << parts[0] << "to" << parts[1];
|
|
DependencyManager::get<LocationBookmarks>()->insert(parts[0], parts[1]);
|
|
}
|
|
}
|
|
|
|
return previousSessionCrashed;
|
|
}
|
|
|
|
void Application::initialize(const QCommandLineParser &parser) {
|
|
//qCDebug(interfaceapp) << "Setting up essentials";
|
|
setupEssentials(parser, _previousSessionCrashed);
|
|
qCDebug(interfaceapp) << "Initializing application";
|
|
|
|
_entitySimulation = std::make_shared<PhysicalEntitySimulation>();
|
|
_physicsEngine = std::make_shared<PhysicsEngine>(Vectors::ZERO);
|
|
_entityClipboard = std::make_shared<EntityTree>();
|
|
_octreeProcessor = std::make_shared<OctreePacketProcessor>();
|
|
_entityEditSender = std::make_shared<EntityEditPacketSender>();
|
|
_graphicsEngine = std::make_shared<GraphicsEngine>();
|
|
_applicationOverlay = std::make_shared<ApplicationOverlay>();
|
|
_dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface();
|
|
|
|
auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
|
|
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
|
|
|
|
constexpr auto INSTALLER_INI_NAME = "installer.ini";
|
|
auto iniPath = QDir(applicationDirPath()).filePath(INSTALLER_INI_NAME);
|
|
QFile installerFile { iniPath };
|
|
std::unordered_map<QString, QString> installerKeyValues;
|
|
if (installerFile.open(QIODevice::ReadOnly)) {
|
|
while (!installerFile.atEnd()) {
|
|
auto line = installerFile.readLine();
|
|
if (!line.isEmpty()) {
|
|
auto index = line.indexOf("=");
|
|
if (index >= 0) {
|
|
installerKeyValues[line.mid(0, index).trimmed()] = line.mid(index + 1).trimmed();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// In practice we shouldn't run across installs that don't have a known installer type.
|
|
// Client or Client+Server installs should always have the installer.ini next to their
|
|
// respective interface.exe, and Steam installs will be detected as such. If a user were
|
|
// to delete the installer.ini, though, and as an example, we won't know the context of the
|
|
// original install.
|
|
constexpr auto INSTALLER_KEY_TYPE = "type";
|
|
constexpr auto INSTALLER_KEY_CAMPAIGN = "campaign";
|
|
constexpr auto INSTALLER_TYPE_UNKNOWN = "unknown";
|
|
constexpr auto INSTALLER_TYPE_STEAM = "steam";
|
|
|
|
auto typeIt = installerKeyValues.find(INSTALLER_KEY_TYPE);
|
|
QString installerType = INSTALLER_TYPE_UNKNOWN;
|
|
if (typeIt == installerKeyValues.end()) {
|
|
if (property(hifi::properties::STEAM).toBool()) {
|
|
installerType = INSTALLER_TYPE_STEAM;
|
|
}
|
|
} else {
|
|
installerType = typeIt->second;
|
|
}
|
|
|
|
auto campaignIt = installerKeyValues.find(INSTALLER_KEY_CAMPAIGN);
|
|
QString installerCampaign = campaignIt != installerKeyValues.end() ? campaignIt->second : "";
|
|
|
|
qDebug() << "Detected installer type:" << installerType;
|
|
qDebug() << "Detected installer campaign:" << installerCampaign;
|
|
|
|
static int SEND_STATS_INTERVAL_MS;
|
|
{
|
|
if (parser.isSet("testScript")) {
|
|
QString testScriptPath = parser.value("testScript");
|
|
// If the URL scheme is http(s) or ftp, then use as is, else - treat it as a local file
|
|
// This is done so as not break previous command line scripts
|
|
if (testScriptPath.left(HIFI_URL_SCHEME_HTTP.length()) == HIFI_URL_SCHEME_HTTP ||
|
|
testScriptPath.left(HIFI_URL_SCHEME_FTP.length()) == HIFI_URL_SCHEME_FTP) {
|
|
|
|
setProperty(hifi::properties::TEST, QUrl::fromUserInput(testScriptPath));
|
|
} else if (QFileInfo(testScriptPath).exists()) {
|
|
setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
|
|
}
|
|
|
|
if (parser.isSet("quitWhenFinished")) {
|
|
_quitWhenFinished = true;
|
|
}
|
|
}
|
|
|
|
if (parser.isSet("testResultsLocation")) {
|
|
// Set test snapshot location only if it is a writeable directory
|
|
QString path = parser.value("testResultsLocation");
|
|
|
|
QFileInfo fileInfo(path);
|
|
if (fileInfo.isDir() && fileInfo.isWritable()) {
|
|
TestScriptingInterface::getInstance()->setTestResultsLocation(path);
|
|
}
|
|
}
|
|
_urlParam = parser.value("url");
|
|
|
|
if (parser.isSet("disableWatchdog")) {
|
|
DISABLE_WATCHDOG = true;
|
|
}
|
|
|
|
if (parser.isSet("system-cursor")) {
|
|
_preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM));
|
|
_useSystemCursor = true;
|
|
}
|
|
|
|
if (parser.isSet("concurrent-downloads")) {
|
|
bool success;
|
|
uint32_t concurrentDownloads = parser.value("concurrent-downloads").toUInt(&success);
|
|
if (!success) {
|
|
concurrentDownloads = MAX_CONCURRENT_RESOURCE_DOWNLOADS;
|
|
}
|
|
ResourceCache::setRequestLimit(concurrentDownloads);
|
|
}
|
|
|
|
// perhaps override the avatar url. Since we will test later for validity
|
|
// we don't need to do so here.
|
|
if (parser.isSet("avatarURL")) {
|
|
_avatarOverrideUrl = QUrl::fromUserInput(parser.value("avatarURL"));
|
|
}
|
|
|
|
// If someone specifies both --avatarURL and --replaceAvatarURL,
|
|
// the replaceAvatarURL wins. So only set the _overrideUrl if this
|
|
// does have a non-empty string.
|
|
if (parser.isSet("replaceAvatarURL")) {
|
|
QString replaceURL = parser.value("replaceAvatarURL");
|
|
_avatarOverrideUrl = QUrl::fromUserInput(replaceURL);
|
|
_saveAvatarOverrideUrl = true;
|
|
}
|
|
|
|
#if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML)
|
|
const bool DISABLE_LOGIN_SCREEN = true; // Login screen is currently disabled
|
|
// Do not show login dialog if requested not to on the command line
|
|
if (DISABLE_LOGIN_SCREEN || parser.isSet("no-login-suggestion")) {
|
|
_noLoginSuggestion = true;
|
|
}
|
|
#endif
|
|
|
|
if (parser.isSet("scripts")) {
|
|
_defaultScriptsLocation.setPath(parser.value("scripts")); // Might need to be done in "main.cpp".
|
|
_overrideDefaultScriptsLocation = true;
|
|
} else {
|
|
_overrideDefaultScriptsLocation = false;
|
|
}
|
|
|
|
// If launched from Steam, let it handle updates
|
|
bool buildCanUpdate = BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable
|
|
|| BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master;
|
|
if (!parser.isSet("no-updater") && buildCanUpdate) {
|
|
constexpr auto INSTALLER_TYPE_CLIENT_ONLY = "client_only";
|
|
|
|
auto applicationUpdater = DependencyManager::set<AutoUpdater>();
|
|
|
|
AutoUpdater::InstallerType type = installerType == INSTALLER_TYPE_CLIENT_ONLY
|
|
? AutoUpdater::InstallerType::CLIENT_ONLY : AutoUpdater::InstallerType::FULL;
|
|
|
|
applicationUpdater->setInstallerType(type);
|
|
applicationUpdater->setInstallerCampaign(installerCampaign);
|
|
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
|
connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
|
|
applicationUpdater->checkForUpdate();
|
|
}
|
|
|
|
// setup the stats interval depending on if the 1s faster hearbeat was requested
|
|
if (parser.isSet("fast-heartbeat")) {
|
|
SEND_STATS_INTERVAL_MS = 1000;
|
|
} else {
|
|
SEND_STATS_INTERVAL_MS = 10000;
|
|
}
|
|
}
|
|
|
|
{
|
|
// identify gpu as early as possible to help identify OpenGL initialization errors.
|
|
auto gpuIdent = GPUIdent::getInstance();
|
|
auto &ch = CrashHandler::getInstance();
|
|
|
|
ch.setAnnotation("sentry[contexts][gpu][name]", gpuIdent->getName().toStdString());
|
|
ch.setAnnotation("sentry[contexts][gpu][version]", gpuIdent->getDriver().toStdString());
|
|
ch.setAnnotation("gpu_memory", std::to_string(gpuIdent->getMemory()));
|
|
}
|
|
|
|
// make sure the debug draw singleton is initialized on the main thread.
|
|
DebugDraw::getInstance().removeMarker("");
|
|
|
|
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
|
PluginManager::getInstance()->setContainer(pluginContainer);
|
|
|
|
QThreadPool::globalInstance()->setMaxThreadCount(MIN_PROCESSING_THREAD_POOL_SIZE);
|
|
thread()->setPriority(QThread::HighPriority);
|
|
thread()->setObjectName("Main Thread");
|
|
|
|
setInstance(this);
|
|
|
|
auto controllerScriptingInterface = DependencyManager::get<controller::ScriptingInterface>().data();
|
|
_controllerScriptingInterface = dynamic_cast<ControllerScriptingInterface*>(controllerScriptingInterface);
|
|
|
|
_entityClipboard->createRootElement();
|
|
|
|
#ifdef Q_OS_WIN
|
|
installNativeEventFilter(&MyNativeEventFilter::getInstance());
|
|
#endif
|
|
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/fontawesome-webfont.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/hifi-glyphs.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/AnonymousPro-Regular.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Light.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/rawline-500.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-Regular.ttf");
|
|
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-Medium.ttf");
|
|
_window->setWindowTitle("Overte");
|
|
|
|
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
|
|
|
|
auto nodeList = DependencyManager::get<NodeList>();
|
|
nodeList->startThread();
|
|
nodeList->setFlagTimeForConnectionStep(true);
|
|
|
|
// move the AddressManager to the NodeList thread so that domain resets due to domain changes always occur
|
|
// before we tell MyAvatar to go to a new location in the new domain
|
|
auto addressManager = DependencyManager::get<AddressManager>();
|
|
addressManager->moveToThread(nodeList->thread());
|
|
|
|
// Set up a watchdog thread to intentionally crash the application on deadlocks
|
|
if (!DISABLE_WATCHDOG) {
|
|
auto deadlockWatchdogThread = new DeadlockWatchdogThread();
|
|
deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId());
|
|
connect(deadlockWatchdogThread, &QThread::started, [] { setThreadName("DeadlockWatchdogThread"); });
|
|
deadlockWatchdogThread->start();
|
|
|
|
// Pause the deadlock watchdog when we sleep, or it might
|
|
// trigger a false positive when we wake back up
|
|
auto platformHelper = PlatformHelper::instance();
|
|
|
|
connect(platformHelper, &PlatformHelper::systemWillSleep, [] {
|
|
DeadlockWatchdogThread::pause();
|
|
});
|
|
|
|
connect(platformHelper, &PlatformHelper::systemWillWake, [] {
|
|
DeadlockWatchdogThread::resume();
|
|
});
|
|
|
|
// Main thread timer to keep the watchdog updated
|
|
QTimer* watchdogUpdateTimer = new QTimer(this);
|
|
connect(watchdogUpdateTimer, &QTimer::timeout, [this] { updateHeartbeat(); });
|
|
connect(this, &QCoreApplication::aboutToQuit, [watchdogUpdateTimer] {
|
|
watchdogUpdateTimer->stop();
|
|
watchdogUpdateTimer->deleteLater();
|
|
});
|
|
watchdogUpdateTimer->setSingleShot(false);
|
|
watchdogUpdateTimer->setInterval(WATCHDOG_TIMER_TIMEOUT); // 100ms, Qt::CoarseTimer acceptable
|
|
watchdogUpdateTimer->start();
|
|
}
|
|
|
|
// Set File Logger Session UUID
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
if (avatarManager) {
|
|
workload::SpacePointer space = getEntities()->getWorkloadSpace();
|
|
avatarManager->setSpace(space);
|
|
}
|
|
|
|
auto accountManager = DependencyManager::get<AccountManager>();
|
|
// set the account manager's root URL and trigger a login request if we don't have the access token
|
|
accountManager->setIsAgent(true);
|
|
accountManager->setAuthURL(MetaverseAPI::getCurrentMetaverseServerURL());
|
|
if (!accountManager->hasKeyPair()) {
|
|
accountManager->generateNewUserKeypair();
|
|
}
|
|
|
|
#ifndef Q_OS_ANDROID
|
|
_logger->setSessionID(accountManager->getSessionID());
|
|
#endif
|
|
|
|
auto &ch = CrashHandler::getInstance();
|
|
ch.setAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString());
|
|
ch.setAnnotation("main_thread_id", std::to_string((size_t)QThread::currentThreadId()));
|
|
|
|
if (steamClient) {
|
|
qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID();
|
|
}
|
|
ch.setAnnotation("steam", property(hifi::properties::STEAM).toBool() ? "1" : "0");
|
|
|
|
qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
|
qCDebug(interfaceapp) << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
|
|
qCDebug(interfaceapp) << "[VERSION] VERSION:" << BuildInfo::VERSION;
|
|
qCDebug(interfaceapp) << "[VERSION] BUILD_TYPE_STRING:" << BuildInfo::BUILD_TYPE_STRING;
|
|
qCDebug(interfaceapp) << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
|
|
#if USE_STABLE_GLOBAL_SERVICES
|
|
qCDebug(interfaceapp) << "[VERSION] We will use STABLE global services.";
|
|
#else
|
|
qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services.";
|
|
#endif
|
|
|
|
updateHeartbeat();
|
|
|
|
// Setup MessagesClient
|
|
DependencyManager::get<MessagesClient>()->startThread();
|
|
|
|
nodeList->getDomainHandler().setErrorDomainURL(QUrl(REDIRECT_HIFI_ADDRESS));
|
|
|
|
// Inititalize sample before registering
|
|
_sampleSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/sample.wav"));
|
|
|
|
#ifdef _WIN32
|
|
WSADATA WsaData;
|
|
int wsaresult = WSAStartup(MAKEWORD(2, 2), &WsaData);
|
|
#endif
|
|
|
|
// tell the NodeList instance who to tell the domain server we care about
|
|
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
|
|
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer << NodeType::EntityScriptServer);
|
|
|
|
// setDefaultFormat has no effect after the platform window has been created, so call it here.
|
|
QSurfaceFormat::setDefaultFormat(getDefaultOpenGLSurfaceFormat());
|
|
|
|
_glWidget = new GLCanvas();
|
|
getApplicationCompositor().setRenderingWidget(_glWidget);
|
|
_window->setCentralWidget(_glWidget);
|
|
|
|
_window->restoreGeometry();
|
|
_window->setVisible(true);
|
|
|
|
_glWidget->setFocusPolicy(Qt::StrongFocus);
|
|
_glWidget->setFocus();
|
|
|
|
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
|
|
|
// enable mouse tracking; otherwise, we only get drag events
|
|
_glWidget->setMouseTracking(true);
|
|
// Make sure the window is set to the correct size by processing the pending events
|
|
QCoreApplication::processEvents();
|
|
|
|
// Create the main thread context, the GPU backend
|
|
initializeGL();
|
|
qCDebug(interfaceapp, "Initialized GL");
|
|
|
|
// Initialize the display plugin architecture
|
|
initializeDisplayPlugins();
|
|
qCDebug(interfaceapp, "Initialized Display");
|
|
|
|
if (_displayPlugin && !_displayPlugin->isHmd()) {
|
|
showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get()));
|
|
}
|
|
|
|
// An audio device changed signal received before the display plugins are set up will cause a crash,
|
|
// so we defer the setup of the `scripting::Audio` class until this point
|
|
DependencyManager::set<AudioScriptingInterface, scripting::Audio>();
|
|
|
|
// Create the rendering engine. This can be slow on some machines due to lots of
|
|
// GPU pipeline creation.
|
|
initializeRenderEngine();
|
|
qCDebug(interfaceapp, "Initialized Render Engine.");
|
|
|
|
_overlays.init(); // do this before scripts load
|
|
|
|
// Initialize the user interface and menu system
|
|
// Needs to happen AFTER the render engine initialization to access its configuration
|
|
initializeUi();
|
|
|
|
init();
|
|
setupSignalsAndOperators();
|
|
qCDebug(interfaceapp, "init() complete.");
|
|
|
|
// create thread for parsing of octree data independent of the main network and rendering threads
|
|
_octreeProcessor->initialize(_enableProcessOctreeThread);
|
|
_entityEditSender->initialize(_enableProcessOctreeThread);
|
|
|
|
// update before the first render
|
|
update(0);
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
static const QString TESTER = "HIFI_TESTER";
|
|
bool isTester = false;
|
|
#if defined (Q_OS_ANDROID)
|
|
// Since we cannot set environment variables in Android we use a file presence
|
|
// to denote that this is a testing device
|
|
QFileInfo check_tester_file(TESTER_FILE);
|
|
isTester = check_tester_file.exists() && check_tester_file.isFile();
|
|
#endif
|
|
|
|
auto& userActivityLogger = UserActivityLogger::getInstance();
|
|
if (userActivityLogger.isEnabled()) {
|
|
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
|
|
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
|
|
static const QString TESTER = "HIFI_TESTER";
|
|
auto gpuIdent = GPUIdent::getInstance();
|
|
auto glContextData = gl::ContextInfo::get();
|
|
QJsonObject properties = {
|
|
{ "version", applicationVersion() },
|
|
{ "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) || isTester },
|
|
{ "installer_campaign", installerCampaign },
|
|
{ "installer_type", installerType },
|
|
{ "build_type", BuildInfo::BUILD_TYPE_STRING },
|
|
{ "previousSessionCrashed", _previousSessionCrashed },
|
|
{ "previousSessionRuntime", (int)(_sessionRunTimer.elapsed() / MSECS_PER_SECOND) },
|
|
{ "cpu_architecture", QSysInfo::currentCpuArchitecture() },
|
|
{ "kernel_type", QSysInfo::kernelType() },
|
|
{ "kernel_version", QSysInfo::kernelVersion() },
|
|
{ "os_type", QSysInfo::productType() },
|
|
{ "os_version", QSysInfo::productVersion() },
|
|
{ "gpu_name", gpuIdent->getName() },
|
|
{ "gpu_driver", gpuIdent->getDriver() },
|
|
{ "gpu_memory", static_cast<qint64>(gpuIdent->getMemory()) },
|
|
{ "gl_version_int", glVersionToInteger(glContextData.version.c_str()) },
|
|
{ "gl_version", glContextData.version.c_str() },
|
|
{ "gl_vender", glContextData.vendor.c_str() },
|
|
{ "gl_sl_version", glContextData.shadingLanguageVersion.c_str() },
|
|
{ "gl_renderer", glContextData.renderer.c_str() },
|
|
{ "ideal_thread_count", QThread::idealThreadCount() }
|
|
};
|
|
auto macVersion = QSysInfo::macVersion();
|
|
if (macVersion != QSysInfo::MV_None) {
|
|
properties["os_osx_version"] = QSysInfo::macVersion();
|
|
}
|
|
auto windowsVersion = QSysInfo::windowsVersion();
|
|
if (windowsVersion != QSysInfo::WV_None) {
|
|
properties["os_win_version"] = QSysInfo::windowsVersion();
|
|
}
|
|
|
|
ProcessorInfo procInfo;
|
|
if (getProcessorInfo(procInfo)) {
|
|
properties["processor_core_count"] = procInfo.numProcessorCores;
|
|
properties["logical_processor_count"] = procInfo.numLogicalProcessors;
|
|
properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1;
|
|
properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2;
|
|
properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3;
|
|
}
|
|
|
|
properties["first_run"] = _firstRun.get();
|
|
|
|
// add the user's machine ID to the launch event
|
|
QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
|
|
properties["machine_fingerprint"] = machineFingerPrint;
|
|
|
|
userActivityLogger.logAction("launch", properties);
|
|
}
|
|
|
|
_entityEditSender->setMyAvatar(myAvatar.get());
|
|
|
|
// The entity octree will have to know about MyAvatar for the parentJointName import
|
|
getEntities()->getTree()->setMyAvatar(myAvatar);
|
|
_entityClipboard->setMyAvatar(myAvatar);
|
|
|
|
// For now we're going to set the PPS for outbound packets to be super high, this is
|
|
// probably not the right long term solution. But for now, we're going to do this to
|
|
// allow you to move an entity around in your hand
|
|
_entityEditSender->setPacketsPerSecond(3000); // super high!!
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
|
_applicationStateDevice = userInputMapper->getStateDevice();
|
|
|
|
_applicationStateDevice->setInputVariant(STATE_IN_HMD, []() -> float {
|
|
return qApp->isHMDMode() ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_FULL_SCREEN_MIRROR, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_MIRROR ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_FIRST_PERSON, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_FIRST_PERSON_LOOK_AT, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON_LOOK_AT ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_THIRD_PERSON, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_THIRD_PERSON ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_LOOK_AT, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_LOOK_AT ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_SELFIE, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_SELFIE ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_ENTITY, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_ENTITY ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAMERA_INDEPENDENT, []() -> float {
|
|
return qApp->getCamera().getMode() == CAMERA_MODE_INDEPENDENT ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_CAPTURE_MOUSE, []() -> float {
|
|
return qApp->getCamera().getCaptureMouse() ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float {
|
|
return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_ADVANCED_MOVEMENT_CONTROLS, []() -> float {
|
|
return qApp->getMyAvatar()->useAdvancedMovementControls() ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_LEFT_HAND_DOMINANT, []() -> float {
|
|
return qApp->getMyAvatar()->getDominantHand() == "left" ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_RIGHT_HAND_DOMINANT, []() -> float {
|
|
return qApp->getMyAvatar()->getDominantHand() == "right" ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_STRAFE_ENABLED, []() -> float {
|
|
return qApp->getMyAvatar()->getStrafeEnabled() ? 1 : 0;
|
|
});
|
|
|
|
_applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float {
|
|
return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, [this]() -> float {
|
|
auto offscreenUi = getOffscreenUI();
|
|
return offscreenUi ? (offscreenUi->navigationFocused() ? 1 : 0) : 0;
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float {
|
|
#if defined(Q_OS_WIN)
|
|
return 1;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float {
|
|
#if defined(Q_OS_MAC)
|
|
return 1;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
});
|
|
_applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float {
|
|
#if defined(Q_OS_ANDROID)
|
|
return 1 ;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
});
|
|
|
|
getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::STARTUP);
|
|
|
|
// Setup the _keyboardMouseDevice, _touchscreenDevice, _touchscreenVirtualPadDevice and the user input mapper with the default bindings
|
|
userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice());
|
|
// if the _touchscreenDevice is not supported it will not be registered
|
|
if (_touchscreenDevice) {
|
|
userInputMapper->registerDevice(_touchscreenDevice->getInputDevice());
|
|
}
|
|
if (_touchscreenVirtualPadDevice) {
|
|
userInputMapper->registerDevice(_touchscreenVirtualPadDevice->getInputDevice());
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
loadSettings(parser);
|
|
|
|
updateVerboseLogging();
|
|
|
|
setCachebustRequire();
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
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.
|
|
}
|
|
|
|
{
|
|
auto audioIO = DependencyManager::get<AudioClient>().data();
|
|
// set the local loopback interface for local sounds
|
|
AudioInjector::setLocalAudioInterface(audioIO);
|
|
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
|
|
audioScriptingInterface->setLocalAudioInterface(audioIO);
|
|
|
|
}
|
|
|
|
this->installEventFilter(this);
|
|
|
|
Menu::getInstance()->setIsOptionChecked(MenuOption::ActionMotorControl, true);
|
|
|
|
// FIXME spacemouse code still needs cleanup
|
|
#if 0
|
|
// the 3Dconnexion device wants to be initialized after a window is displayed.
|
|
SpacemouseManager::getInstance().init();
|
|
#endif
|
|
|
|
// Add periodic checks to send user activity data
|
|
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
|
|
static int NEARBY_AVATAR_RADIUS_METERS = 10;
|
|
|
|
static glm::vec3 lastAvatarPosition = myAvatar->getWorldPosition();
|
|
static glm::mat4 lastHMDHeadPose = getHMDSensorPose();
|
|
static controller::Pose lastLeftHandPose = myAvatar->getLeftHandPose();
|
|
static controller::Pose lastRightHandPose = myAvatar->getRightHandPose();
|
|
|
|
// Periodically send fps as a user activity event
|
|
QTimer* sendStatsTimer = new QTimer(this);
|
|
sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); // 10s, Qt::CoarseTimer acceptable
|
|
connect(sendStatsTimer, &QTimer::timeout, this, [this]() {
|
|
|
|
QJsonObject properties = {};
|
|
MemoryInfo memInfo;
|
|
if (getMemoryInfo(memInfo)) {
|
|
properties["system_memory_total"] = static_cast<qint64>(memInfo.totalMemoryBytes);
|
|
properties["system_memory_used"] = static_cast<qint64>(memInfo.usedMemoryBytes);
|
|
properties["process_memory_used"] = static_cast<qint64>(memInfo.processUsedMemoryBytes);
|
|
}
|
|
|
|
// content location and build info - useful for filtering stats
|
|
auto addressManager = DependencyManager::get<AddressManager>();
|
|
auto currentDomain = addressManager->currentShareableAddress(true).toString(); // domain only
|
|
auto currentPath = addressManager->currentPath(true); // with orientation
|
|
properties["current_domain"] = currentDomain;
|
|
properties["current_path"] = currentPath;
|
|
properties["build_version"] = BuildInfo::VERSION;
|
|
|
|
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
|
|
|
properties["render_rate"] = getRenderLoopRate();
|
|
properties["target_render_rate"] = getTargetRenderFrameRate();
|
|
properties["present_rate"] = displayPlugin->presentRate();
|
|
properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate();
|
|
properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate();
|
|
properties["stutter_rate"] = displayPlugin->stutterRate();
|
|
properties["game_rate"] = getGameLoopRate();
|
|
properties["has_async_reprojection"] = displayPlugin->hasAsyncReprojection();
|
|
properties["hardware_stats"] = displayPlugin->getHardwareStats();
|
|
|
|
// deadlock watchdog related stats
|
|
properties["deadlock_watchdog_maxElapsed"] = (int)DeadlockWatchdogThread::_maxElapsed;
|
|
properties["deadlock_watchdog_maxElapsedAverage"] = (int)DeadlockWatchdogThread::_maxElapsedAverage;
|
|
|
|
auto nodeList = DependencyManager::get<NodeList>();
|
|
properties["packet_rate_in"] = nodeList->getInboundPPS();
|
|
properties["packet_rate_out"] = nodeList->getOutboundPPS();
|
|
properties["kbps_in"] = nodeList->getInboundKbps();
|
|
properties["kbps_out"] = nodeList->getOutboundKbps();
|
|
|
|
SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer);
|
|
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
|
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
|
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
|
|
SharedNodePointer messagesMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
|
properties["entity_ping"] = entityServerNode ? entityServerNode->getPingMs() : -1;
|
|
properties["audio_ping"] = audioMixerNode ? audioMixerNode->getPingMs() : -1;
|
|
properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1;
|
|
properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1;
|
|
properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1;
|
|
properties["atp_in_kbps"] = assetServerNode ? assetServerNode->getInboundKbps() : 0.0f;
|
|
|
|
auto loadingRequests = ResourceCache::getLoadingRequests();
|
|
|
|
QJsonArray loadingRequestsStats;
|
|
for (const auto& requestPair : loadingRequests) {
|
|
QJsonObject requestStats;
|
|
requestStats["filename"] = requestPair.first->getURL().fileName();
|
|
requestStats["received"] = requestPair.first->getBytesReceived();
|
|
requestStats["total"] = requestPair.first->getBytesTotal();
|
|
requestStats["attempts"] = (int)requestPair.first->getDownloadAttempts();
|
|
loadingRequestsStats.append(requestStats);
|
|
}
|
|
|
|
properties["active_downloads"] = loadingRequests.size();
|
|
properties["pending_downloads"] = (int)ResourceCache::getPendingRequestCount();
|
|
properties["active_downloads_details"] = loadingRequestsStats;
|
|
|
|
auto statTracker = DependencyManager::get<StatTracker>();
|
|
|
|
properties["processing_resources"] = statTracker->getStat("Processing").toInt();
|
|
properties["pending_processing_resources"] = statTracker->getStat("PendingProcessing").toInt();
|
|
|
|
QJsonObject startedRequests;
|
|
startedRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_STARTED).toInt();
|
|
startedRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_STARTED).toInt();
|
|
startedRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_STARTED).toInt();
|
|
startedRequests["total"] = startedRequests["atp"].toInt() + startedRequests["http"].toInt()
|
|
+ startedRequests["file"].toInt();
|
|
properties["started_requests"] = startedRequests;
|
|
|
|
QJsonObject successfulRequests;
|
|
successfulRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_SUCCESS).toInt();
|
|
successfulRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_SUCCESS).toInt();
|
|
successfulRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_SUCCESS).toInt();
|
|
successfulRequests["total"] = successfulRequests["atp"].toInt() + successfulRequests["http"].toInt()
|
|
+ successfulRequests["file"].toInt();
|
|
properties["successful_requests"] = successfulRequests;
|
|
|
|
QJsonObject failedRequests;
|
|
failedRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_FAILED).toInt();
|
|
failedRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_FAILED).toInt();
|
|
failedRequests["file"] = statTracker->getStat(STAT_FILE_REQUEST_FAILED).toInt();
|
|
failedRequests["total"] = failedRequests["atp"].toInt() + failedRequests["http"].toInt()
|
|
+ failedRequests["file"].toInt();
|
|
properties["failed_requests"] = failedRequests;
|
|
|
|
QJsonObject cacheRequests;
|
|
cacheRequests["atp"] = statTracker->getStat(STAT_ATP_REQUEST_CACHE).toInt();
|
|
cacheRequests["http"] = statTracker->getStat(STAT_HTTP_REQUEST_CACHE).toInt();
|
|
cacheRequests["total"] = cacheRequests["atp"].toInt() + cacheRequests["http"].toInt();
|
|
properties["cache_requests"] = cacheRequests;
|
|
|
|
QJsonObject atpMappingRequests;
|
|
atpMappingRequests["started"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_STARTED).toInt();
|
|
atpMappingRequests["failed"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_FAILED).toInt();
|
|
atpMappingRequests["successful"] = statTracker->getStat(STAT_ATP_MAPPING_REQUEST_SUCCESS).toInt();
|
|
properties["atp_mapping_requests"] = atpMappingRequests;
|
|
|
|
properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false;
|
|
|
|
QJsonObject bytesDownloaded;
|
|
auto atpBytes = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toLongLong();
|
|
auto httpBytes = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toLongLong();
|
|
auto fileBytes = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toLongLong();
|
|
bytesDownloaded["atp"] = atpBytes;
|
|
bytesDownloaded["http"] = httpBytes;
|
|
bytesDownloaded["file"] = fileBytes;
|
|
bytesDownloaded["total"] = atpBytes + httpBytes + fileBytes;
|
|
properties["bytes_downloaded"] = bytesDownloaded;
|
|
|
|
auto myAvatar = getMyAvatar();
|
|
glm::vec3 avatarPosition = myAvatar->getWorldPosition();
|
|
properties["avatar_has_moved"] = lastAvatarPosition != avatarPosition;
|
|
lastAvatarPosition = avatarPosition;
|
|
|
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
|
auto entityActivityTracking = entityScriptingInterface->getActivityTracking();
|
|
entityScriptingInterface->resetActivityTracking();
|
|
properties["added_entity_cnt"] = entityActivityTracking.addedEntityCount;
|
|
properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount;
|
|
properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount;
|
|
|
|
NodeToOctreeSceneStats* octreeServerSceneStats = getOcteeSceneStats();
|
|
unsigned long totalServerOctreeElements = 0;
|
|
for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) {
|
|
totalServerOctreeElements += i->second.getTotalElements();
|
|
}
|
|
|
|
properties["local_octree_elements"] = (qint64) OctreeElement::getInternalNodeCount();
|
|
properties["server_octree_elements"] = (qint64) totalServerOctreeElements;
|
|
|
|
properties["active_display_plugin"] = getActiveDisplayPlugin()->getName();
|
|
properties["using_hmd"] = isHMDMode();
|
|
|
|
auto contextInfo = gl::ContextInfo::get();
|
|
properties["gl_info"] = QJsonObject{
|
|
{ "version", contextInfo.version.c_str() },
|
|
{ "sl_version", contextInfo.shadingLanguageVersion.c_str() },
|
|
{ "vendor", contextInfo.vendor.c_str() },
|
|
{ "renderer", contextInfo.renderer.c_str() },
|
|
};
|
|
properties["gpu_used_memory"] = (int)BYTES_TO_MB(gpu::Context::getUsedGPUMemSize());
|
|
properties["gpu_free_memory"] = (int)BYTES_TO_MB(gpu::Context::getFreeGPUMemSize());
|
|
properties["gpu_frame_time"] = (float)(qApp->getGPUContext()->getFrameTimerGPUAverage());
|
|
properties["batch_frame_time"] = (float)(qApp->getGPUContext()->getFrameTimerBatchAverage());
|
|
properties["ideal_thread_count"] = QThread::idealThreadCount();
|
|
|
|
auto hmdHeadPose = getHMDSensorPose();
|
|
properties["hmd_head_pose_changed"] = isHMDMode() && (hmdHeadPose != lastHMDHeadPose);
|
|
lastHMDHeadPose = hmdHeadPose;
|
|
|
|
auto leftHandPose = myAvatar->getLeftHandPose();
|
|
auto rightHandPose = myAvatar->getRightHandPose();
|
|
// controller::Pose considers two poses to be different if either are invalid. In our case, we actually
|
|
// want to consider the pose to be unchanged if it was invalid and still is invalid, so we check that first.
|
|
properties["hand_pose_changed"] =
|
|
((leftHandPose.valid || lastLeftHandPose.valid) && (leftHandPose != lastLeftHandPose))
|
|
|| ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose));
|
|
lastLeftHandPose = leftHandPose;
|
|
lastRightHandPose = rightHandPose;
|
|
|
|
UserActivityLogger::getInstance().logAction("stats", properties);
|
|
});
|
|
sendStatsTimer->start();
|
|
|
|
// Periodically check for count of nearby avatars
|
|
static int lastCountOfNearbyAvatars = -1;
|
|
QTimer* checkNearbyAvatarsTimer = new QTimer(this);
|
|
checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS); // 10 seconds, Qt::CoarseTimer ok
|
|
connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, []() {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getWorldPosition(),
|
|
NEARBY_AVATAR_RADIUS_METERS) - 1;
|
|
if (nearbyAvatars != lastCountOfNearbyAvatars) {
|
|
lastCountOfNearbyAvatars = nearbyAvatars;
|
|
UserActivityLogger::getInstance().logAction("nearby_avatars", { { "count", nearbyAvatars } });
|
|
}
|
|
});
|
|
checkNearbyAvatarsTimer->start();
|
|
|
|
// Track user activity event when we receive a mute packet
|
|
auto onMutedByMixer = []() {
|
|
UserActivityLogger::getInstance().logAction("received_mute_packet");
|
|
};
|
|
connect(DependencyManager::get<AudioClient>().data(), &AudioClient::mutedByMixer, this, onMutedByMixer);
|
|
|
|
// Track when the address bar is opened
|
|
auto onAddressBarShown = [this]() {
|
|
// Record time
|
|
UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } });
|
|
};
|
|
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarShown, this, onAddressBarShown);
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
|
OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender();
|
|
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
|
|
entityPacketSender->setMyAvatar(myAvatar.get());
|
|
|
|
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)_sessionRunTimer.elapsed() / MSECS_PER_SECOND);
|
|
|
|
updateSystemTabletMode();
|
|
|
|
// Setup the mouse ray pick
|
|
{
|
|
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::getPickEntities() | PickScriptingInterface::getPickLocalEntities()), 0.0f, 0.0f, true);
|
|
mouseRayPick->parentTransform = std::make_shared<MouseTransformNode>();
|
|
mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE);
|
|
auto mouseRayPickID = DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, mouseRayPick);
|
|
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickID(mouseRayPickID);
|
|
}
|
|
|
|
// Preload Tablet sounds
|
|
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(qApp->getEntities()->getTree());
|
|
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
|
|
DependencyManager::get<Keyboard>()->createKeyboard();
|
|
|
|
// Needs to happen later in the constructor as it depends on some other things being set up
|
|
_discordPresence = new DiscordPresence();
|
|
|
|
_pendingIdleEvent = false;
|
|
_graphicsEngine->startup();
|
|
|
|
qCDebug(interfaceapp) << "Directory Service session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
|
|
|
|
pauseUntilLoginDetermined();
|
|
}
|
|
|
|
void Application::init() {
|
|
// Make sure Login state is up to date
|
|
#if !defined(DISABLE_QML)
|
|
DependencyManager::get<DialogsManager>()->toggleLoginDialog();
|
|
#endif
|
|
DependencyManager::get<AvatarManager>()->init();
|
|
|
|
_lastTimeUpdated.start();
|
|
|
|
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
|
|
// when +connect_lobby in command line, join steam lobby
|
|
const QString STEAM_LOBBY_COMMAND_LINE_KEY = "+connect_lobby";
|
|
int lobbyIndex = arguments().indexOf(STEAM_LOBBY_COMMAND_LINE_KEY);
|
|
if (lobbyIndex != -1) {
|
|
QString lobbyId = arguments().value(lobbyIndex + 1);
|
|
steamClient->joinLobby(lobbyId);
|
|
}
|
|
}
|
|
|
|
qCDebug(interfaceapp) << "Loaded settings";
|
|
|
|
// fire off an immediate domain-server check in now that settings are loaded
|
|
QMetaObject::invokeMethod(DependencyManager::get<NodeList>().data(), "sendDomainServerCheckIn");
|
|
|
|
// This allows collision to be set up properly for shape entities supported by GeometryCache.
|
|
// This is before entity setup to ensure that it's ready for whenever instance collision is initialized.
|
|
ShapeEntityItem::setShapeInfoCalulator(ShapeEntityItem::ShapeInfoCalculator(&shapeInfoCalculator));
|
|
|
|
getEntities()->init();
|
|
|
|
ObjectMotionState::setShapeManager(&_shapeManager);
|
|
_physicsEngine->init();
|
|
|
|
EntityTreePointer tree = getEntities()->getTree();
|
|
_entitySimulation->init(tree, _physicsEngine, _entityEditSender.get());
|
|
tree->setSimulation(_entitySimulation);
|
|
|
|
_gameWorkload.startup(getEntities()->getWorkloadSpace(), _graphicsEngine->getRenderScene(), _entitySimulation);
|
|
_entitySimulation->setWorkloadSpace(getEntities()->getWorkloadSpace());
|
|
}
|
|
|
|
void Application::setupSignalsAndOperators() {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
|
auto nodeList = DependencyManager::get<NodeList>();
|
|
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
|
|
|
// General
|
|
{
|
|
connect(this, SIGNAL(aboutToQuit()), this, SLOT(onAboutToQuit()));
|
|
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
|
|
connect(_window, SIGNAL(windowMinimizedChanged(bool)), this, SLOT(windowMinimizedChanged(bool)));
|
|
|
|
auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>();
|
|
|
|
// setup a timer for domain-server check ins
|
|
QTimer* domainCheckInTimer = new QTimer(this);
|
|
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
|
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
|
connect(this, &QCoreApplication::aboutToQuit, [domainCheckInTimer] {
|
|
domainCheckInTimer->stop();
|
|
domainCheckInTimer->deleteLater();
|
|
});
|
|
|
|
connect(&domainHandler, SIGNAL(domainURLChanged(QUrl)), SLOT(domainURLChanged(QUrl)));
|
|
connect(&domainHandler, SIGNAL(redirectToErrorDomainURL(QUrl)), SLOT(goToErrorDomainURL(QUrl)));
|
|
connect(&domainHandler, &DomainHandler::domainURLChanged, [](QUrl domainURL){
|
|
auto &ch = CrashHandler::getInstance();
|
|
ch.setAnnotation("domain", domainURL.toString().toStdString());
|
|
});
|
|
connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
|
|
connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle()));
|
|
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
|
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
|
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
|
entityScriptingInterface->deleteEntity(getTabletScreenID());
|
|
entityScriptingInterface->deleteEntity(getTabletHomeButtonID());
|
|
entityScriptingInterface->deleteEntity(getTabletFrameID());
|
|
_failedToConnectToEntityServer = false;
|
|
});
|
|
|
|
connect(&domainHandler, &DomainHandler::confirmConnectWithoutAvatarEntities,
|
|
this, &Application::confirmConnectWithoutAvatarEntities);
|
|
|
|
connect(&domainHandler, &DomainHandler::connectedToDomain, this, [this]() {
|
|
if (!isServerlessMode()) {
|
|
_entityServerConnectionTimer.setInterval(ENTITY_SERVER_ADDED_TIMEOUT);
|
|
_entityServerConnectionTimer.start();
|
|
_failedToConnectToEntityServer = false;
|
|
}
|
|
});
|
|
// if we get a domain change, immediately attempt update location in directory server
|
|
connect(&domainHandler, &DomainHandler::connectedToDomain, discoverabilityManager.data(),
|
|
&DiscoverabilityManager::updateLocation);
|
|
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused);
|
|
|
|
// We could clear ATP assets only when changing domains, but it's possible that the domain you are connected
|
|
// to has gone down and switched to a new content set, so when you reconnect the cached ATP assets will no longer be valid.
|
|
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, DependencyManager::get<ScriptCache>().data(), &ScriptCache::clearATPScriptsFromCache);
|
|
|
|
// you might think we could just do this in NodeList but we only want this connection for Interface
|
|
connect(&domainHandler, &DomainHandler::limitOfSilentDomainCheckInsReached,
|
|
nodeList.data(), [nodeList]() { nodeList->reset("Domain checkin limit"); });
|
|
|
|
|
|
// update our location every 5 seconds in the directory server, assuming that we are authenticated with one
|
|
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND;
|
|
connect(&_locationUpdateTimer, &QTimer::timeout, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation);
|
|
connect(&_locationUpdateTimer, &QTimer::timeout, DependencyManager::get<AddressManager>().data(),
|
|
&AddressManager::storeCurrentAddress);
|
|
_locationUpdateTimer.start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
|
|
|
// send a location update immediately
|
|
discoverabilityManager->updateLocation();
|
|
|
|
connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded);
|
|
connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled);
|
|
connect(nodeList.data(), &NodeList::nodeActivated, this, &Application::nodeActivated);
|
|
connect(nodeList.data(), &NodeList::uuidChanged, myAvatar.get(), &MyAvatar::setSessionUUID);
|
|
connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID);
|
|
connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
|
|
|
auto accountManager = DependencyManager::get<AccountManager>();
|
|
#if defined(Q_OS_ANDROID)
|
|
connect(accountManager.data(), &AccountManager::authRequired, this, []() {
|
|
auto addressManager = DependencyManager::get<AddressManager>();
|
|
AndroidHelper::instance().showLoginDialog(addressManager->currentAddress());
|
|
});
|
|
#else
|
|
connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
|
|
#endif
|
|
connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
|
|
|
|
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
|
|
connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(),
|
|
&DialogsManager::showDomainLoginDialog);
|
|
connect(domainAccountManager.data(), &DomainAccountManager::authRequired, this, &Application::updateWindowTitle);
|
|
connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, &Application::updateWindowTitle);
|
|
// ####### TODO: Connect any other signals from domainAccountManager.
|
|
|
|
auto addressManager = DependencyManager::get<AddressManager>();
|
|
// use our MyAvatar position and quat for address manager path
|
|
addressManager->setPositionGetter([] {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
return myAvatar ? myAvatar->getWorldFeetPosition() : Vectors::ZERO;
|
|
});
|
|
addressManager->setOrientationGetter([] {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
return myAvatar ? myAvatar->getWorldOrientation() : glm::quat();
|
|
});
|
|
|
|
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
|
|
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
|
|
|
|
{
|
|
auto scriptEngines = DependencyManager::get<ScriptEngines>().data();
|
|
scriptEngines->registerScriptInitializer([this](ScriptManagerPointer manager) {
|
|
registerScriptEngineWithApplicationServices(manager);
|
|
});
|
|
|
|
connect(scriptEngines, &ScriptEngines::scriptCountChanged, this, [this] {
|
|
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
|
if (scriptEngines->getRunningScripts().isEmpty()) {
|
|
getMyAvatar()->clearScriptableSettings();
|
|
}
|
|
}, Qt::QueuedConnection);
|
|
|
|
connect(scriptEngines, &ScriptEngines::scriptsReloading, this, [this] {
|
|
getEntities()->reloadEntityScripts();
|
|
loadAvatarScripts(getMyAvatar()->getScriptUrls());
|
|
}, Qt::QueuedConnection);
|
|
|
|
connect(scriptEngines, &ScriptEngines::scriptLoadError,
|
|
this, [](const QString& filename, const QString& error) {
|
|
OffscreenUi::asyncWarning(nullptr, "Error Loading Script", filename + " failed to load.");
|
|
}, Qt::QueuedConnection);
|
|
|
|
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
|
|
connect(scriptEngines, &ScriptEngines::requestingEntityScriptServerLog,
|
|
entityScriptServerLog.data(), &EntityScriptServerLogClient::requestMessagesForScriptEngines);
|
|
}
|
|
|
|
DependencyManager::get<PickManager>()->setShouldPickHUDOperator([]() { return DependencyManager::get<HMDScriptingInterface>()->isHMDMode(); });
|
|
DependencyManager::get<PickManager>()->setCalculatePos2DFromHUDOperator([this](const glm::vec3& intersection) {
|
|
const glm::vec2 MARGIN(25.0f);
|
|
glm::vec2 maxPos = _controllerScriptingInterface->getViewportDimensions() - MARGIN;
|
|
glm::vec2 pos2D = DependencyManager::get<HMDScriptingInterface>()->overlayFromWorldPoint(intersection);
|
|
return glm::max(MARGIN, glm::min(pos2D, maxPos));
|
|
});
|
|
|
|
DependencyManager::get<UsersScriptingInterface>()->setKickConfirmationOperator([this] (const QUuid& nodeID, unsigned int banFlags) { userKickConfirmation(nodeID, banFlags); });
|
|
|
|
FileDialogHelper::setOpenDirectoryOperator([this](const QString& path) { openDirectory(path); });
|
|
QDesktopServices::setUrlHandler("file", this, "showUrlHandler");
|
|
QDesktopServices::setUrlHandler("", this, "showUrlHandler");
|
|
auto drives = QDir::drives();
|
|
for (auto drive : drives) {
|
|
QDesktopServices::setUrlHandler(QUrl(drive.absolutePath()).scheme(), this, "showUrlHandler");
|
|
}
|
|
|
|
#if defined(Q_OS_ANDROID)
|
|
connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground);
|
|
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
|
|
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
|
|
connect(&AndroidHelper::instance(), &AndroidHelper::toggleAwayMode, this, &Application::toggleAwayMode);
|
|
AndroidHelper::instance().notifyLoadComplete();
|
|
#endif
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
// Entities
|
|
{
|
|
connect(this, &Application::aboutToQuit, [this]() { setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); });
|
|
|
|
_entityServerConnectionTimer.setSingleShot(true);
|
|
connect(&_entityServerConnectionTimer, &QTimer::timeout, this, &Application::setFailedToConnectToEntityServer);
|
|
|
|
connect(_octreeProcessor.get(), &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch);
|
|
|
|
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
|
|
connect(_entitySimulation.get(), &PhysicalEntitySimulation::entityCollisionWithEntity,
|
|
getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity);
|
|
|
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
|
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
|
|
// of events related clicking, hovering over, and entering entities
|
|
getEntities()->connectSignalsToSlots(entityScriptingInterface.data());
|
|
|
|
EntityTreePointer tree = getEntities()->getTree();
|
|
// Make sure any new sounds are loaded as soon as know about them.
|
|
connect(tree.get(), &EntityTree::newCollisionSoundURL, this, [this](QUrl newURL, EntityItemID id) {
|
|
getEntities()->setCollisionSound(id, DependencyManager::get<SoundCache>()->getSound(newURL));
|
|
}, Qt::QueuedConnection);
|
|
|
|
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityItemID) {
|
|
if (entityItemID == _keyboardFocusedEntity.get()) {
|
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
|
}
|
|
}, Qt::QueuedConnection);
|
|
|
|
EntityTree::setEntityClicksCapturedOperator([this] { return _controllerScriptingInterface->areEntityClicksCaptured(); });
|
|
|
|
// If the user clicks on an object, we will check that it's a web surface, and if so, set the focus to it
|
|
auto pointerManager = DependencyManager::get<PointerManager>().data();
|
|
auto keyboardFocusOperator = [this](const QUuid& id, const PointerEvent& event) {
|
|
if (event.shouldFocus()) {
|
|
auto keyboard = DependencyManager::get<Keyboard>();
|
|
if (getEntities()->wantsKeyboardFocus(id)) {
|
|
setKeyboardFocusEntity(id);
|
|
} else if (!keyboard->containsID(id)) { // FIXME: this is a hack to make the keyboard work for now, since the keys would otherwise steal focus
|
|
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
|
|
}
|
|
}
|
|
};
|
|
connect(pointerManager, &PointerManager::triggerBeginEntity, keyboardFocusOperator);
|
|
connect(pointerManager, &PointerManager::triggerBeginOverlay, keyboardFocusOperator);
|
|
|
|
getEntities()->setEntityLoadingPriorityFunction([this](const EntityItem& item) {
|
|
if (item.getEntityHostType() == entity::HostType::AVATAR) {
|
|
return item.isMyAvatarEntity() ? Avatar::MYAVATAR_ENTITY_LOADING_PRIORITY : Avatar::OTHERAVATAR_ENTITY_LOADING_PRIORITY;
|
|
}
|
|
|
|
const float maxSize = glm::compMax(item.getScaledDimensions());
|
|
if (maxSize <= 0.0f) {
|
|
return 0.0f;
|
|
}
|
|
|
|
const glm::vec3 itemPosition = item.getWorldPosition();
|
|
const float distance = glm::distance(getMyAvatar()->getWorldPosition(), itemPosition);
|
|
float result = atan2(maxSize, distance);
|
|
bool isInView = true;
|
|
{
|
|
QMutexLocker viewLocker(&_viewMutex);
|
|
isInView = _viewFrustum.sphereIntersectsKeyhole(itemPosition, maxSize);
|
|
}
|
|
if (!isInView) {
|
|
const float OUT_OF_VIEW_PENALTY = -M_PI_2;
|
|
result += OUT_OF_VIEW_PENALTY;
|
|
}
|
|
return result;
|
|
});
|
|
|
|
EntityTreeRenderer::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
|
if (_aboutToQuit) {
|
|
return false;
|
|
}
|
|
|
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
|
if (renderable) {
|
|
renderable->addMaterial(material, parentMaterialName);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
EntityTreeRenderer::setRemoveMaterialFromEntityOperator([this](const QUuid& entityID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
|
if (_aboutToQuit) {
|
|
return false;
|
|
}
|
|
|
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
|
if (renderable) {
|
|
renderable->removeMaterial(material, parentMaterialName);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
EntityTreeRenderer::setAddMaterialToAvatarOperator([](const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto avatar = static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(avatarID));
|
|
if (avatar) {
|
|
avatar->addMaterial(material, parentMaterialName);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
EntityTreeRenderer::setRemoveMaterialFromAvatarOperator([](const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName) {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto avatar = static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(avatarID));
|
|
if (avatar) {
|
|
avatar->removeMaterial(material, parentMaterialName);
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
EntityTree::setGetUnscaledDimensionsForIDOperator([this](const QUuid& id) {
|
|
if (_aboutToQuit) {
|
|
return glm::vec3(1.0f);
|
|
}
|
|
|
|
auto entity = getEntities()->getEntity(id);
|
|
if (entity) {
|
|
return entity->getUnscaledDimensions();
|
|
}
|
|
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto avatar = static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(id));
|
|
if (avatar) {
|
|
return avatar->getSNScale();
|
|
}
|
|
return glm::vec3(1.0f);
|
|
});
|
|
|
|
ReferenceMaterial::setMaterialForUUIDOperator([this](const QUuid& entityID) -> graphics::MaterialPointer {
|
|
if (_aboutToQuit) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
|
if (renderable) {
|
|
return renderable->getTopMaterial();
|
|
}
|
|
|
|
return nullptr;
|
|
});
|
|
|
|
Procedural::opaqueStencil = [](gpu::StatePointer state) { PrepareStencil::testMaskDrawShape(*state); };
|
|
Procedural::transparentStencil = [](gpu::StatePointer state) { PrepareStencil::testMask(*state); };
|
|
|
|
EntityTree::setGetEntityObjectOperator([this](const QUuid& id) -> QObject* {
|
|
auto entities = getEntities();
|
|
if (auto entity = entities->renderableForEntityId(id)) {
|
|
return qobject_cast<QObject*>(entity.get());
|
|
}
|
|
return nullptr;
|
|
});
|
|
|
|
EntityTree::setEmitScriptEventOperator([this](const QUuid& id, const QVariant& message) {
|
|
auto entities = getEntities();
|
|
if (auto entity = entities->renderableForEntityId(id)) {
|
|
entity->emitScriptEvent(message);
|
|
}
|
|
});
|
|
|
|
EntityTree::setTextSizeOperator([this](const QUuid& id, const QString& text) {
|
|
auto entities = getEntities();
|
|
if (auto entity = entities->renderableForEntityId(id)) {
|
|
if (auto renderable = std::dynamic_pointer_cast<render::entities::TextEntityRenderer>(entity)) {
|
|
return renderable->textSize(text);
|
|
}
|
|
}
|
|
return QSizeF(0.0f, 0.0f);
|
|
});
|
|
|
|
Texture::setUnboundTextureForUUIDOperator([this](const QUuid& entityID) -> gpu::TexturePointer {
|
|
if (_aboutToQuit) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
|
if (renderable) {
|
|
return renderable->getTexture();
|
|
}
|
|
|
|
return nullptr;
|
|
});
|
|
|
|
EntityTreeRenderer::setEntitiesShouldFadeFunction([this]() {
|
|
SharedNodePointer entityServerNode = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::EntityServer);
|
|
return entityServerNode && !isPhysicsEnabled();
|
|
});
|
|
|
|
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([](unsigned int rayPickID) {
|
|
RayToEntityIntersectionResult entityResult;
|
|
entityResult.intersects = false;
|
|
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<RayPickResult>(rayPickID);
|
|
if (pickResult) {
|
|
entityResult.intersects = pickResult->type != IntersectionType::NONE;
|
|
if (entityResult.intersects) {
|
|
entityResult.intersection = pickResult->intersection;
|
|
entityResult.distance = pickResult->distance;
|
|
entityResult.surfaceNormal = pickResult->surfaceNormal;
|
|
entityResult.entityID = pickResult->objectID;
|
|
entityResult.extraInfo = pickResult->extraInfo;
|
|
}
|
|
}
|
|
return entityResult;
|
|
});
|
|
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([](unsigned int rayPickID, bool value) {
|
|
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
|
|
});
|
|
|
|
BillboardModeHelpers::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation,
|
|
BillboardMode billboardMode, const glm::vec3& frustumPos, bool rotate90x) {
|
|
const glm::quat ROTATE_90X = glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT);
|
|
if (billboardMode == BillboardMode::YAW) {
|
|
//rotate about vertical to face the camera
|
|
glm::vec3 dPosition = frustumPos - position;
|
|
// If x and z are 0, atan(x, z) is undefined, so default to 0 degrees
|
|
float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z);
|
|
glm::quat result = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)) * rotation;
|
|
if (rotate90x) {
|
|
result *= ROTATE_90X;
|
|
}
|
|
return result;
|
|
} else if (billboardMode == BillboardMode::FULL) {
|
|
// use the referencial from the avatar, y isn't always up
|
|
glm::vec3 avatarUP = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
|
|
// check to see if glm::lookAt will work / using glm::lookAt variable name
|
|
glm::highp_vec3 s(glm::cross(position - frustumPos, avatarUP));
|
|
|
|
// make sure s is not NaN for any component
|
|
if (glm::length2(s) > 0.0f) {
|
|
glm::quat result = glm::conjugate(glm::toQuat(glm::lookAt(frustumPos, position, avatarUP))) * rotation;
|
|
if (rotate90x) {
|
|
result *= ROTATE_90X;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
return rotation;
|
|
});
|
|
BillboardModeHelpers::setPrimaryViewFrustumPositionOperator([this]() {
|
|
ViewFrustum viewFrustum;
|
|
copyViewFrustum(viewFrustum);
|
|
return viewFrustum.getPosition();
|
|
});
|
|
|
|
render::entities::WebEntityRenderer::setAcquireWebSurfaceOperator([=](const QString& url, bool htmlContent, QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface) {
|
|
bool isTablet = url == TabletScriptingInterface::QML;
|
|
if (htmlContent) {
|
|
webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(render::entities::WebEntityRenderer::QML);
|
|
cachedWebSurface = true;
|
|
auto rootItemLoadedFunctor = [url, webSurface] {
|
|
webSurface->getRootItem()->setProperty(render::entities::WebEntityRenderer::URL_PROPERTY, url);
|
|
};
|
|
if (webSurface->getRootItem()) {
|
|
rootItemLoadedFunctor();
|
|
} else {
|
|
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
|
|
}
|
|
auto surfaceContext = webSurface->getSurfaceContext();
|
|
surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get<KeyboardScriptingInterface>().data());
|
|
} else {
|
|
// FIXME: the tablet should use the OffscreenQmlSurfaceCache
|
|
webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), [](OffscreenQmlSurface* webSurface) {
|
|
AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] {
|
|
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
|
|
// if the application has already stopped its event loop, delete must be explicit
|
|
delete webSurface;
|
|
});
|
|
});
|
|
auto rootItemLoadedFunctor = [webSurface, url, isTablet] {
|
|
Application::setupQmlSurface(webSurface->getSurfaceContext(), isTablet || url == LOGIN_DIALOG.toString() || url == AVATAR_INPUTS_BAR_QML.toString() ||
|
|
url == BUBBLE_ICON_QML.toString());
|
|
};
|
|
if (webSurface->getRootItem()) {
|
|
rootItemLoadedFunctor();
|
|
} else {
|
|
QObject::connect(webSurface.data(), &hifi::qml::OffscreenSurface::rootContextCreated, rootItemLoadedFunctor);
|
|
}
|
|
webSurface->load(url);
|
|
cachedWebSurface = false;
|
|
}
|
|
const uint8_t DEFAULT_MAX_FPS = 10;
|
|
const uint8_t TABLET_FPS = 90;
|
|
webSurface->setMaxFps(isTablet ? TABLET_FPS : DEFAULT_MAX_FPS);
|
|
});
|
|
render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([=](QSharedPointer<OffscreenQmlSurface>& webSurface, bool& cachedWebSurface, std::vector<QMetaObject::Connection>& connections) {
|
|
QQuickItem* rootItem = webSurface->getRootItem();
|
|
|
|
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
|
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
|
|
if (rootItem && !cachedWebSurface) {
|
|
// stop loading
|
|
QMetaObject::invokeMethod(rootItem, "stop");
|
|
}
|
|
|
|
webSurface->pause();
|
|
|
|
for (auto& connection : connections) {
|
|
QObject::disconnect(connection);
|
|
}
|
|
connections.clear();
|
|
|
|
// If the web surface was fetched out of the cache, release it back into the cache
|
|
if (cachedWebSurface) {
|
|
// If it's going back into the cache make sure to explicitly set the URL to a blank page
|
|
// in order to stop any resource consumption or audio related to the page.
|
|
if (rootItem) {
|
|
rootItem->setProperty("url", "about:blank");
|
|
}
|
|
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
|
|
if (offscreenCache) {
|
|
offscreenCache->release(render::entities::WebEntityRenderer::QML, webSurface);
|
|
}
|
|
cachedWebSurface = false;
|
|
}
|
|
webSurface.reset();
|
|
});
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
// Plugins
|
|
{
|
|
connect(PluginManager::getInstance().data(), &PluginManager::inputDeviceRunningChanged,
|
|
_controllerScriptingInterface, &controller::ScriptingInterface::updateRunningInputDevices);
|
|
|
|
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount);
|
|
if (_useSystemCursor) {
|
|
connect(this, &Application::activeDisplayPluginChanged, this, [=](){
|
|
qApp->setProperty(hifi::properties::HMD, qApp->isHMDMode());
|
|
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
|
|
|
if (displayPlugin->isHmd()) {
|
|
if (_preferredCursor.get() == Cursor::Manager::getIconName(Cursor::Icon::RETICLE)) {
|
|
setPreferredCursor(Cursor::Manager::getIconName(Cursor::Icon::RETICLE));
|
|
} else {
|
|
setPreferredCursor(Cursor::Manager::getIconName(Cursor::Icon::ARROW));
|
|
}
|
|
} else {
|
|
setPreferredCursor(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM));
|
|
}
|
|
|
|
auto &ch = CrashHandler::getInstance();
|
|
ch.setAnnotation("display_plugin", displayPlugin->getName().toStdString());
|
|
ch.setAnnotation("hmd", displayPlugin->isHmd() ? "1" : "0");
|
|
});
|
|
}
|
|
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode);
|
|
connect(this, &Application::activeDisplayPluginChanged, this, [&](){
|
|
if (getLoginDialogPoppedUp()) {
|
|
auto dialogsManager = DependencyManager::get<DialogsManager>();
|
|
auto keyboard = DependencyManager::get<Keyboard>();
|
|
if (_firstRun.get()) {
|
|
// display mode changed. Don't allow auto-switch to work after this session.
|
|
_firstRun.set(false);
|
|
}
|
|
if (isHMDMode()) {
|
|
emit loginDialogFocusDisabled();
|
|
dialogsManager->hideLoginDialog();
|
|
createLoginDialog();
|
|
} else {
|
|
DependencyManager::get<EntityScriptingInterface>()->deleteEntity(_loginDialogID);
|
|
_loginDialogID = QUuid();
|
|
_loginStateManager.tearDown();
|
|
dialogsManager->showLoginDialog();
|
|
emit loginDialogFocusEnabled();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Setup the userInputMapper with the actions
|
|
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
|
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
|
using namespace controller;
|
|
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
|
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
|
{
|
|
auto actionEnum = static_cast<Action>(action);
|
|
int key = Qt::Key_unknown;
|
|
static int lastKey = Qt::Key_unknown;
|
|
bool navAxis = false;
|
|
switch (actionEnum) {
|
|
case Action::TOGGLE_PUSHTOTALK:
|
|
if (audioScriptingInterface) {
|
|
if (state > 0.0f) {
|
|
audioScriptingInterface->setPushingToTalk(true);
|
|
} else if (state <= 0.0f) {
|
|
audioScriptingInterface->setPushingToTalk(false);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Action::UI_NAV_VERTICAL:
|
|
navAxis = true;
|
|
if (state > 0.0f) {
|
|
key = Qt::Key_Up;
|
|
} else if (state < 0.0f) {
|
|
key = Qt::Key_Down;
|
|
}
|
|
break;
|
|
|
|
case Action::UI_NAV_LATERAL:
|
|
navAxis = true;
|
|
if (state > 0.0f) {
|
|
key = Qt::Key_Right;
|
|
} else if (state < 0.0f) {
|
|
key = Qt::Key_Left;
|
|
}
|
|
break;
|
|
|
|
case Action::UI_NAV_GROUP:
|
|
navAxis = true;
|
|
if (state > 0.0f) {
|
|
key = Qt::Key_Tab;
|
|
} else if (state < 0.0f) {
|
|
key = Qt::Key_Backtab;
|
|
}
|
|
break;
|
|
|
|
case Action::UI_NAV_BACK:
|
|
key = Qt::Key_Escape;
|
|
break;
|
|
|
|
case Action::UI_NAV_SELECT:
|
|
key = Qt::Key_Return;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
auto window = tabletScriptingInterface->getTabletWindow();
|
|
if (navAxis && window) {
|
|
if (lastKey != Qt::Key_unknown) {
|
|
QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier);
|
|
sendEvent(window, &event);
|
|
lastKey = Qt::Key_unknown;
|
|
}
|
|
|
|
if (key != Qt::Key_unknown) {
|
|
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
|
|
sendEvent(window, &event);
|
|
tabletScriptingInterface->processEvent(&event);
|
|
lastKey = key;
|
|
}
|
|
} else if (key != Qt::Key_unknown && window) {
|
|
if (state) {
|
|
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
|
|
sendEvent(window, &event);
|
|
tabletScriptingInterface->processEvent(&event);
|
|
} else {
|
|
QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier);
|
|
sendEvent(window, &event);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (action == controller::toInt(controller::Action::RETICLE_CLICK)) {
|
|
auto reticlePos = getApplicationCompositor().getReticlePosition();
|
|
QPoint localPos(reticlePos.x, reticlePos.y); // both hmd and desktop already handle this in our coordinates.
|
|
if (state) {
|
|
QMouseEvent mousePress(QEvent::MouseButtonPress, localPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
|
|
sendEvent(_glWidget, &mousePress);
|
|
_reticleClickPressed = true;
|
|
} else {
|
|
QMouseEvent mouseRelease(QEvent::MouseButtonRelease, localPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
|
|
sendEvent(_glWidget, &mouseRelease);
|
|
_reticleClickPressed = false;
|
|
}
|
|
return; // nothing else to do
|
|
}
|
|
|
|
if (state) {
|
|
if (action == controller::toInt(controller::Action::TOGGLE_MUTE)) {
|
|
auto audioClient = DependencyManager::get<AudioClient>();
|
|
audioClient->setMuted(!audioClient->isMuted());
|
|
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
|
|
cycleCamera();
|
|
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU) && !isInterstitialMode()) {
|
|
toggleTabletUI();
|
|
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
|
auto oldPos = getApplicationCompositor().getReticlePosition();
|
|
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
|
} else if (action == controller::toInt(controller::Action::RETICLE_Y)) {
|
|
auto oldPos = getApplicationCompositor().getReticlePosition();
|
|
getApplicationCompositor().setReticlePosition({ oldPos.x, oldPos.y + state });
|
|
} else if (action == controller::toInt(controller::Action::TOGGLE_OVERLAY)) {
|
|
toggleOverlays();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
// UI
|
|
{
|
|
auto offscreenUi = getOffscreenUI().data();
|
|
connect(offscreenUi, &OffscreenUi::desktopReady, [this]() {
|
|
// Now that we've loaded the menu and thus switched to the previous display plugin
|
|
// we can unlock the desktop repositioning code, since all the positions will be
|
|
// relative to the desktop size for this plugin
|
|
auto offscreenUi = getOffscreenUI();
|
|
auto desktop = offscreenUi->getDesktop();
|
|
if (desktop) {
|
|
desktop->setProperty("repositionLocked", false);
|
|
}
|
|
});
|
|
|
|
#if defined(Q_OS_ANDROID) || defined(DISABLE_QML)
|
|
connect(offscreenUi, &OffscreenUi::keyboardFocusActive, [this]() {
|
|
resumeAfterLoginDialogActionTaken();
|
|
});
|
|
#else
|
|
connect(offscreenUi, &OffscreenUi::keyboardFocusActive, [this]() {
|
|
// Do not show login dialog if requested not to on the command line
|
|
if (!_noLoginSuggestion) {
|
|
showLoginScreen();
|
|
}
|
|
resumeAfterLoginDialogActionTaken();
|
|
});
|
|
#endif
|
|
|
|
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
|
|
static const int ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS = 1000;
|
|
_addAssetToWorldResizeTimer.setInterval(ADD_ASSET_TO_WORLD_TIMER_INTERVAL_MS); // 1s, Qt::CoarseTimer acceptable
|
|
connect(&_addAssetToWorldResizeTimer, &QTimer::timeout, this, &Application::addAssetToWorldCheckModelSize);
|
|
|
|
// Auto-update and close adding asset to world info message box.
|
|
static const int ADD_ASSET_TO_WORLD_INFO_TIMEOUT_MS = 5000;
|
|
_addAssetToWorldInfoTimer.setInterval(ADD_ASSET_TO_WORLD_INFO_TIMEOUT_MS); // 5s, Qt::CoarseTimer acceptable
|
|
_addAssetToWorldInfoTimer.setSingleShot(true);
|
|
connect(&_addAssetToWorldInfoTimer, &QTimer::timeout, this, &Application::addAssetToWorldInfoTimeout);
|
|
static const int ADD_ASSET_TO_WORLD_ERROR_TIMEOUT_MS = 8000;
|
|
_addAssetToWorldErrorTimer.setInterval(ADD_ASSET_TO_WORLD_ERROR_TIMEOUT_MS); // 8s, Qt::CoarseTimer acceptable
|
|
_addAssetToWorldErrorTimer.setSingleShot(true);
|
|
connect(&_addAssetToWorldErrorTimer, &QTimer::timeout, this, &Application::addAssetToWorldErrorTimeout);
|
|
|
|
connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose);
|
|
connect(&domainHandler, &DomainHandler::domainURLChanged, this, &Application::addAssetToWorldMessageClose);
|
|
connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &Application::addAssetToWorldMessageClose);
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
// Avatar
|
|
{
|
|
// Save avatar location immediately after a teleport.
|
|
connect(myAvatar.get(), &MyAvatar::positionGoneTo,
|
|
DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress);
|
|
|
|
connect(myAvatar.get(), &MyAvatar::positionGoneTo, this, [this] {
|
|
if (!_physicsEnabled) {
|
|
// when we arrive somewhere without physics enabled --> startSafeLanding
|
|
_octreeProcessor->startSafeLanding();
|
|
}
|
|
}, Qt::QueuedConnection);
|
|
|
|
connect(myAvatar.get(), &MyAvatar::skeletonModelURLChanged, [](){
|
|
QUrl avatarURL = qApp->getMyAvatar()->getSkeletonModelURL();
|
|
auto &ch = CrashHandler::getInstance();
|
|
ch.setAnnotation("avatar", avatarURL.toString().toStdString());
|
|
});
|
|
|
|
// FIXME -- I'm a little concerned about this.
|
|
connect(myAvatar->getSkeletonModel().get(), &SkeletonModel::skeletonLoaded,
|
|
this, &Application::checkSkeleton, Qt::QueuedConnection);
|
|
|
|
connect(myAvatar.get(), &MyAvatar::newCollisionSoundURL, this, [this](QUrl newURL) {
|
|
if (auto avatar = getMyAvatar()) {
|
|
auto sound = DependencyManager::get<SoundCache>()->getSound(newURL);
|
|
avatar->setCollisionSound(sound);
|
|
}
|
|
}, Qt::QueuedConnection);
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
// Camera
|
|
{
|
|
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
|
|
connect(&_myCamera, &Camera::captureMouseChanged, this, &Application::captureMouseChanged);
|
|
}
|
|
|
|
// Make sure we don't time out during slow operations at startup
|
|
updateHeartbeat();
|
|
|
|
// Audio
|
|
{
|
|
auto audioIO = DependencyManager::get<AudioClient>().data();
|
|
audioIO->setPositionGetter([] {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
|
|
return myAvatar ? myAvatar->getPositionForAudio() : Vectors::ZERO;
|
|
});
|
|
audioIO->setOrientationGetter([] {
|
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
|
|
return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
|
|
});
|
|
|
|
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [&audioIO](recording::Frame::ConstPointer frame) {
|
|
audioIO->handleRecordedAudioInput(frame->data);
|
|
});
|
|
|
|
connect(audioIO, &AudioClient::inputReceived, [](const QByteArray& audio) {
|
|
static auto recorder = DependencyManager::get<recording::Recorder>();
|
|
if (recorder->isRecording()) {
|
|
static const recording::FrameType AUDIO_FRAME_TYPE = recording::Frame::registerFrameType(AudioConstants::getAudioFrameName());
|
|
recorder->recordFrame(AUDIO_FRAME_TYPE, audio);
|
|
}
|
|
});
|
|
audioIO->startThread();
|
|
|
|
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>().data();
|
|
connect(audioIO, &AudioClient::mutedByMixer, audioScriptingInterface, &AudioScriptingInterface::mutedByMixer);
|
|
connect(audioIO, &AudioClient::receivedFirstPacket, audioScriptingInterface, &AudioScriptingInterface::receivedFirstPacket);
|
|
connect(audioIO, &AudioClient::disconnected, audioScriptingInterface, &AudioScriptingInterface::disconnected);
|
|
connect(audioIO, &AudioClient::noiseGateOpened, audioScriptingInterface, &AudioScriptingInterface::noiseGateOpened);
|
|
connect(audioIO, &AudioClient::noiseGateClosed, audioScriptingInterface, &AudioScriptingInterface::noiseGateClosed);
|
|
connect(audioIO, &AudioClient::inputReceived, audioScriptingInterface, &AudioScriptingInterface::inputReceived);
|
|
connect(audioIO, &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) {
|
|
auto audioClient = DependencyManager::get<AudioClient>();
|
|
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
|
|
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
|
|
float distance = glm::distance(myAvatarPosition, position);
|
|
|
|
if (distance < radius) {
|
|
audioClient->setMuted(true);
|
|
audioScriptingInterface->environmentMuted();
|
|
}
|
|
});
|
|
QSharedPointer<scripting::Audio> scriptingAudioSharedPointer = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
|
if (scriptingAudioSharedPointer) {
|
|
connect(this, &Application::activeDisplayPluginChanged,
|
|
scriptingAudioSharedPointer.data(), &scripting::Audio::onContextChanged);
|
|
}
|
|
}
|
|
}
|