connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled);#endif #ifdef HAVE_IVIEWHMD auto eyeTracker = DependencyManager::get<EyeTracker>(); eyeTracker->init(); setActiveEyeTracker();#endif auto applicationUpdater = DependencyManager::get<AutoUpdater>(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); // Now that menu is initialized we can sync myAvatar with it's state. getMyAvatar()->updateMotionBehaviorFromMenu(); // FIXME spacemouse code still needs cleanup#if 0 // the 3Dconnexion device wants to be initialized after a window is displayed. SpacemouseManager::getInstance().init();#endif // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, [this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) { if (_keyboardFocusedItem != entityItemID) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; auto properties = entityScriptingInterface->getEntityProperties(entityItemID); if (EntityTypes::Web == properties.getType() && !properties.getLocked() && properties.getVisible()) { auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(entityItemID); RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get()); if (webEntity) { webEntity->setProxyWindow(_window->windowHandle()); if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _keyboardFocusedItem = entityItemID; _lastAcceptedKeyPress = usecTimestampNow(); if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) { _keyboardFocusHighlight = new Cube3DOverlay(); _keyboardFocusHighlight->setAlpha(1.0f); _keyboardFocusHighlight->setBorderSize(1.0f); _keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 }); _keyboardFocusHighlight->setIsSolid(false); _keyboardFocusHighlight->setPulseMin(0.5); _keyboardFocusHighlight->setPulseMax(1.0); _keyboardFocusHighlight->setColorPulse(1.0); _keyboardFocusHighlight->setIgnoreRayIntersection(true); _keyboardFocusHighlight->setDrawInFront(true); } _keyboardFocusHighlight->setRotation(webEntity->getRotation()); _keyboardFocusHighlight->setPosition(webEntity->getPosition()); _keyboardFocusHighlight->setDimensions(webEntity->getDimensions() * 1.05f); _keyboardFocusHighlight->setVisible(true); _keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight); } } if (_keyboardFocusedItem == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } } }); connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) { if (entityItemID == _keyboardFocusedItem) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } } }); // If the user clicks somewhere where there is NO entity at all, we will release focus connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, [=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } }); connect(this, &Application::aboutToQuit, [=]() { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } }); // Add periodic checks to send user activity data static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; static int SEND_STATS_INTERVAL_MS = 10000; static int NEARBY_AVATAR_RADIUS_METERS = 10; // Periodically send fps as a user activity event QTimer* sendStatsTimer = new QTimer(this); sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); 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); } auto displayPlugin = qApp->getActiveDisplayPlugin(); properties["fps"] = _frameCounter.rate(); properties["present_rate"] = displayPlugin->presentRate(); properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); properties["sim_rate"] = getAverageSimsPerSecond(); properties["avatar_sim_rate"] = getAvatarSimrate(); auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>(); properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(); properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(); properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); auto nodeList = DependencyManager::get<NodeList>(); 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; auto loadingRequests = ResourceCache::getLoadingRequests(); properties["active_downloads"] = loadingRequests.size(); properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; 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); connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() { auto avatarManager = DependencyManager::get<AvatarManager>(); int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(), 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 onAddressBarToggled = [this]() { // Record time UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } }); }; connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled); // Make sure we don't time out during slow operations at startup updateHeartbeat(); OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender(); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); entityPacketSender->setMyAvatar(getMyAvatar()); connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); // After all of the constructor is completed, then set firstRun to false. Setting::Handle<bool> firstRun{ Settings::firstRun, true }; firstRun.set(false);} void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) { case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: case DomainHandler::ConnectionRefusedReason::TooManyUsers: case DomainHandler::ConnectionRefusedReason::Unknown: { QString message = "Unable to connect to the location you are visiting.\n"; message += reasonMessage; OffscreenUi::warning("", message); break; } default: // nothing to do. break; }} QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; QMetaObject::invokeMethod(this, "getUserAgent", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userAgent)); return userAgent; } QString userAgent = "Mozilla/5.0 (HighFidelityInterface/" + BuildInfo::VERSION + "; " + QSysInfo::productType() + " " + QSysInfo::productVersion() + ")"; auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); }; // For each plugin, add to userAgent auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); for (auto& dp : displayPlugins) { if (dp->isActive() && dp->isHmd()) { userAgent += " " + formatPluginName(dp->getName()); } } auto inputPlugins= PluginManager::getInstance()->getInputPlugins(); for (auto& ip : inputPlugins) { if (ip->isActive()) { userAgent += " " + formatPluginName(ip->getName()); } } return userAgent;} void Application::toggleMenuUnderReticle() const { // In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly // different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both // on the menu and off. // Even in 2D, it is arguable whether the user would want the menu to be to the side. const float X_LEFT_SHIFT = 50.0; auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto reticlePosition = getApplicationCompositor().getReticlePosition(); offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y));} void Application::checkChangeCursor() { QMutexLocker locker(&_changeCursorLock); if (_cursorNeedsChanging) {#ifdef Q_OS_MAC auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget#else // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the // window menu, which is a pain, so only hide it for the GL surface auto cursorTarget = _glWidget;#endif cursorTarget->setCursor(_desiredCursor); _cursorNeedsChanging = false; }} void Application::showCursor(const QCursor& cursor) { QMutexLocker locker(&_changeCursorLock); _desiredCursor = cursor; _cursorNeedsChanging = true;} void Application::updateHeartbeat() const { static_cast<DeadlockWatchdogThread*>(_deadlockWatchdogThread)->updateHeartbeat();} void Application::aboutToQuit() { emit beforeAboutToQuit(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->isActive()) { inputPlugin->deactivate(); } } getActiveDisplayPlugin()->deactivate(); // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get<OffscreenUi>()->hide("RunningScripts"); _aboutToQuit = true; cleanupBeforeQuit();} void Application::cleanupBeforeQuit() { // add a logline indicating if QTWEBENGINE_REMOTE_DEBUGGING is set or not QString webengineRemoteDebugging = QProcessEnvironment::systemEnvironment().value("QTWEBENGINE_REMOTE_DEBUGGING", "false"); qCDebug(interfaceapp) << "QTWEBENGINE_REMOTE_DEBUGGING =" << webengineRemoteDebugging; // Stop third party processes so that they're not left running in the event of a subsequent shutdown crash.#ifdef HAVE_DDE DependencyManager::get<DdeFaceTracker>()->setEnabled(false);#endif#ifdef HAVE_IVIEWHMD DependencyManager::get<EyeTracker>()->setEnabled(false, true);#endif AnimDebugDraw::getInstance().shutdown(); // FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete: _applicationStateDevice.reset(); if (_keyboardFocusHighlightID > 0) { getOverlays().deleteOverlay(_keyboardFocusHighlightID); _keyboardFocusHighlightID = -1; } _keyboardFocusHighlight = nullptr; auto nodeList = DependencyManager::get<NodeList>(); // send the domain a disconnect packet, force stoppage of domain-server check-ins nodeList->getDomainHandler().disconnect(); nodeList->setIsShuttingDown(true); // tell the packet receiver we're shutting down, so it can drop packets nodeList->getPacketReceiver().setShouldDropPackets(true); getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); DependencyManager::get<ScriptEngines>()->saveScripts(); DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts DependencyManager::destroy<ScriptEngines>(); // Cleanup all overlays after the scripts, as scripts might add more _overlays.cleanupAllOverlays(); // first stop all timers directly or by invokeMethod // depending on what thread they run in locationUpdateTimer.stop(); identityPacketTimer.stop(); pingTimer.stop(); QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::BlockingQueuedConnection); // save state _settingsThread.quit(); saveSettings(); _window->saveGeometry(); // stop the AudioClient QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "stop", Qt::BlockingQueuedConnection); // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy<AudioClient>(); // destroy the AudioInjectorManager so it and its thread have a chance to go down safely // this will also stop any ongoing network injectors DependencyManager::destroy<AudioInjectorManager>(); // Destroy third party processes after scripts have finished using them.#ifdef HAVE_DDE DependencyManager::destroy<DdeFaceTracker>();#endif#ifdef HAVE_IVIEWHMD DependencyManager::destroy<EyeTracker>();#endif DependencyManager::destroy<OffscreenUi>();} Application::~Application() { _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(nullptr); _octreeProcessor.terminate(); _entityEditSender.terminate(); _physicsEngine->setCharacterController(nullptr); // remove avatars from physics engine DependencyManager::get<AvatarManager>()->clearAllAvatars(); VectorOfMotionStates motionStates; DependencyManager::get<AvatarManager>()->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); DependencyManager::destroy<OffscreenUi>(); DependencyManager::destroy<AvatarManager>(); DependencyManager::destroy<AnimationCache>(); DependencyManager::destroy<FramebufferCache>(); DependencyManager::destroy<TextureCache>(); DependencyManager::destroy<ModelCache>(); DependencyManager::destroy<GeometryCache>(); DependencyManager::destroy<ScriptCache>(); DependencyManager::destroy<SoundCache>(); ResourceManager::cleanup(); QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); // remove the NodeList from the DependencyManager DependencyManager::destroy<NodeList>(); // ask the node thread to quit and wait until it is done nodeThread->quit(); nodeThread->wait(); Leapmotion::destroy(); #if 0 ConnexionClient::getInstance().destroy();#endif // The window takes ownership of the menu, so this has the side effect of destroying it. _window->setMenuBar(nullptr); _window->deleteLater(); qInstallMessageHandler(nullptr); // NOTE: Do this as late as possible so we continue to get our log messages} void Application::initializeGL() { qCDebug(interfaceapp) << "Created Display Window."; // initialize glut for shape drawing; Qt apparently initializes it on OS X if (_isGLInitialized) { return; } else { _isGLInitialized = true; } gpu::Context::init<gpu::gl::GLBackend>(); _gpuContext = std::make_shared<gpu::Context>(); // The gpu context can make child contexts for transfers, so // we need to restore primary rendering context _offscreenContext->makeCurrent(); initDisplay(); qCDebug(interfaceapp, "Initialized Display."); // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; _renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor); _renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", cullFunctor); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); // TODO: Load a cached config file // The UI can't be created until the primary OpenGL // context is created, because it needs to share // texture resources // Needs to happen AFTER the render engine initialization to access its configuration initializeUi(); qCDebug(interfaceapp, "Initialized Offscreen UI."); _offscreenContext->makeCurrent(); // call Menu getInstance static method to set up the menu // Needs to happen AFTER the QML UI initialization _window->setMenuBar(Menu::getInstance()); init(); qCDebug(interfaceapp, "init() complete."); // create thread for parsing of octree data independent of the main network and rendering threads _octreeProcessor.initialize(_enableProcessOctreeThread); connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); _entityEditSender.initialize(_enableProcessOctreeThread); _idleLoopStdev.reset(); // update before the first render update(0);} FrameTimingsScriptingInterface _frameTimingsScriptingInterface; extern void setupPreferences(); void Application::initializeUi() { AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); auto offscreenUi = DependencyManager::get<OffscreenUi>(); offscreenUi->create(_offscreenContext->getContext()); auto rootContext = offscreenUi->getRootContext(); offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // support the window management and scripting proxies for VR use offscreenUi->createDesktop(QString("hifi/Desktop.qml")); // FIXME either expose so that dialogs can set this themselves orLines 1001–1500 of 5476. Previous lines Load more
connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #endif #ifdef HAVE_IVIEWHMD auto eyeTracker = DependencyManager::get<EyeTracker>(); eyeTracker->init(); setActiveEyeTracker(); #endif auto applicationUpdater = DependencyManager::get<AutoUpdater>(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); // Now that menu is initialized we can sync myAvatar with it's state. getMyAvatar()->updateMotionBehaviorFromMenu(); // FIXME spacemouse code still needs cleanup #if 0 // the 3Dconnexion device wants to be initialized after a window is displayed. SpacemouseManager::getInstance().init(); #endif // If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>(); connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity, [this, entityScriptingInterface](const EntityItemID& entityItemID, const MouseEvent& event) { if (_keyboardFocusedItem != entityItemID) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; auto properties = entityScriptingInterface->getEntityProperties(entityItemID); if (EntityTypes::Web == properties.getType() && !properties.getLocked() && properties.getVisible()) { auto entity = entityScriptingInterface->getEntityTree()->findEntityByID(entityItemID); RenderableWebEntityItem* webEntity = dynamic_cast<RenderableWebEntityItem*>(entity.get()); if (webEntity) { webEntity->setProxyWindow(_window->windowHandle()); if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _keyboardFocusedItem = entityItemID; _lastAcceptedKeyPress = usecTimestampNow(); if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) { _keyboardFocusHighlight = new Cube3DOverlay(); _keyboardFocusHighlight->setAlpha(1.0f); _keyboardFocusHighlight->setBorderSize(1.0f); _keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 }); _keyboardFocusHighlight->setIsSolid(false); _keyboardFocusHighlight->setPulseMin(0.5); _keyboardFocusHighlight->setPulseMax(1.0); _keyboardFocusHighlight->setColorPulse(1.0); _keyboardFocusHighlight->setIgnoreRayIntersection(true); _keyboardFocusHighlight->setDrawInFront(true); } _keyboardFocusHighlight->setRotation(webEntity->getRotation()); _keyboardFocusHighlight->setPosition(webEntity->getPosition()); _keyboardFocusHighlight->setDimensions(webEntity->getDimensions() * 1.05f); _keyboardFocusHighlight->setVisible(true); _keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight); } } if (_keyboardFocusedItem == UNKNOWN_ENTITY_ID && _keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } } }); connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, [=](const EntityItemID& entityItemID) { if (entityItemID == _keyboardFocusedItem) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } } }); // If the user clicks somewhere where there is NO entity at all, we will release focus connect(getEntities(), &EntityTreeRenderer::mousePressOffEntity, [=](const RayToEntityIntersectionResult& entityItemID, const QMouseEvent* event) { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } }); connect(this, &Application::aboutToQuit, [=]() { _keyboardFocusedItem = UNKNOWN_ENTITY_ID; if (_keyboardFocusHighlight) { _keyboardFocusHighlight->setVisible(false); } }); // Add periodic checks to send user activity data static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; static int SEND_STATS_INTERVAL_MS = 10000; static int NEARBY_AVATAR_RADIUS_METERS = 10; // Periodically send fps as a user activity event QTimer* sendStatsTimer = new QTimer(this); sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS); 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); } auto displayPlugin = qApp->getActiveDisplayPlugin(); properties["fps"] = _frameCounter.rate(); properties["present_rate"] = displayPlugin->presentRate(); properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate(); properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate(); properties["sim_rate"] = getAverageSimsPerSecond(); properties["avatar_sim_rate"] = getAvatarSimrate(); auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>(); properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond(); properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond(); properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond(); properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond(); auto nodeList = DependencyManager::get<NodeList>(); 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; auto loadingRequests = ResourceCache::getLoadingRequests(); properties["active_downloads"] = loadingRequests.size(); properties["pending_downloads"] = ResourceCache::getPendingRequestCount(); properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; 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); connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() { auto avatarManager = DependencyManager::get<AvatarManager>(); int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(), 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 onAddressBarToggled = [this]() { // Record time UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } }); }; connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled); // Make sure we don't time out during slow operations at startup updateHeartbeat(); OctreeEditPacketSender* packetSender = entityScriptingInterface->getPacketSender(); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); entityPacketSender->setMyAvatar(getMyAvatar()); connect(this, &Application::applicationStateChanged, this, &Application::activeChanged); qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0); // After all of the constructor is completed, then set firstRun to false. Setting::Handle<bool> firstRun{ Settings::firstRun, true }; firstRun.set(false); } void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCode) { switch (static_cast<DomainHandler::ConnectionRefusedReason>(reasonCode)) { case DomainHandler::ConnectionRefusedReason::ProtocolMismatch: case DomainHandler::ConnectionRefusedReason::TooManyUsers: case DomainHandler::ConnectionRefusedReason::Unknown: { QString message = "Unable to connect to the location you are visiting.\n"; message += reasonMessage; OffscreenUi::warning("", message); break; } default: // nothing to do. break; } } QString Application::getUserAgent() { if (QThread::currentThread() != thread()) { QString userAgent; QMetaObject::invokeMethod(this, "getUserAgent", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QString, userAgent)); return userAgent; } QString userAgent = "Mozilla/5.0 (HighFidelityInterface/" + BuildInfo::VERSION + "; " + QSysInfo::productType() + " " + QSysInfo::productVersion() + ")"; auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); }; // For each plugin, add to userAgent auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); for (auto& dp : displayPlugins) { if (dp->isActive() && dp->isHmd()) { userAgent += " " + formatPluginName(dp->getName()); } } auto inputPlugins= PluginManager::getInstance()->getInputPlugins(); for (auto& ip : inputPlugins) { if (ip->isActive()) { userAgent += " " + formatPluginName(ip->getName()); } } return userAgent; } void Application::toggleMenuUnderReticle() const { // In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly // different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both // on the menu and off. // Even in 2D, it is arguable whether the user would want the menu to be to the side. const float X_LEFT_SHIFT = 50.0; auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto reticlePosition = getApplicationCompositor().getReticlePosition(); offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y)); } void Application::checkChangeCursor() { QMutexLocker locker(&_changeCursorLock); if (_cursorNeedsChanging) { #ifdef Q_OS_MAC auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget #else // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the // window menu, which is a pain, so only hide it for the GL surface auto cursorTarget = _glWidget; #endif cursorTarget->setCursor(_desiredCursor); _cursorNeedsChanging = false; } } void Application::showCursor(const QCursor& cursor) { QMutexLocker locker(&_changeCursorLock); _desiredCursor = cursor; _cursorNeedsChanging = true; } void Application::updateHeartbeat() const { static_cast<DeadlockWatchdogThread*>(_deadlockWatchdogThread)->updateHeartbeat(); } void Application::aboutToQuit() { emit beforeAboutToQuit(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->isActive()) { inputPlugin->deactivate(); } } getActiveDisplayPlugin()->deactivate(); // Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown. DependencyManager::get<OffscreenUi>()->hide("RunningScripts"); _aboutToQuit = true; cleanupBeforeQuit(); } void Application::cleanupBeforeQuit() { // add a logline indicating if QTWEBENGINE_REMOTE_DEBUGGING is set or not QString webengineRemoteDebugging = QProcessEnvironment::systemEnvironment().value("QTWEBENGINE_REMOTE_DEBUGGING", "false"); qCDebug(interfaceapp) << "QTWEBENGINE_REMOTE_DEBUGGING =" << webengineRemoteDebugging; // Stop third party processes so that they're not left running in the event of a subsequent shutdown crash. #ifdef HAVE_DDE DependencyManager::get<DdeFaceTracker>()->setEnabled(false); #endif #ifdef HAVE_IVIEWHMD DependencyManager::get<EyeTracker>()->setEnabled(false, true); #endif AnimDebugDraw::getInstance().shutdown(); // FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete: _applicationStateDevice.reset(); if (_keyboardFocusHighlightID > 0) { getOverlays().deleteOverlay(_keyboardFocusHighlightID); _keyboardFocusHighlightID = -1; } _keyboardFocusHighlight = nullptr; auto nodeList = DependencyManager::get<NodeList>(); // send the domain a disconnect packet, force stoppage of domain-server check-ins nodeList->getDomainHandler().disconnect(); nodeList->setIsShuttingDown(true); // tell the packet receiver we're shutting down, so it can drop packets nodeList->getPacketReceiver().setShouldDropPackets(true); getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); DependencyManager::get<ScriptEngines>()->saveScripts(); DependencyManager::get<ScriptEngines>()->shutdownScripting(); // stop all currently running global scripts DependencyManager::destroy<ScriptEngines>(); // Cleanup all overlays after the scripts, as scripts might add more _overlays.cleanupAllOverlays(); // first stop all timers directly or by invokeMethod // depending on what thread they run in locationUpdateTimer.stop(); identityPacketTimer.stop(); pingTimer.stop(); QMetaObject::invokeMethod(&_settingsTimer, "stop", Qt::BlockingQueuedConnection); // save state _settingsThread.quit(); saveSettings(); _window->saveGeometry(); // stop the AudioClient QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "stop", Qt::BlockingQueuedConnection); // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy<AudioClient>(); // destroy the AudioInjectorManager so it and its thread have a chance to go down safely // this will also stop any ongoing network injectors DependencyManager::destroy<AudioInjectorManager>(); // Destroy third party processes after scripts have finished using them. #ifdef HAVE_DDE DependencyManager::destroy<DdeFaceTracker>(); #endif #ifdef HAVE_IVIEWHMD DependencyManager::destroy<EyeTracker>(); #endif DependencyManager::destroy<OffscreenUi>(); } Application::~Application() { _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); EntityTreePointer tree = getEntities()->getTree(); tree->setSimulation(nullptr); _octreeProcessor.terminate(); _entityEditSender.terminate(); _physicsEngine->setCharacterController(nullptr); // remove avatars from physics engine DependencyManager::get<AvatarManager>()->clearAllAvatars(); VectorOfMotionStates motionStates; DependencyManager::get<AvatarManager>()->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); DependencyManager::destroy<OffscreenUi>(); DependencyManager::destroy<AvatarManager>(); DependencyManager::destroy<AnimationCache>(); DependencyManager::destroy<FramebufferCache>(); DependencyManager::destroy<TextureCache>(); DependencyManager::destroy<ModelCache>(); DependencyManager::destroy<GeometryCache>(); DependencyManager::destroy<ScriptCache>(); DependencyManager::destroy<SoundCache>(); ResourceManager::cleanup(); QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); // remove the NodeList from the DependencyManager DependencyManager::destroy<NodeList>(); // ask the node thread to quit and wait until it is done nodeThread->quit(); nodeThread->wait(); Leapmotion::destroy(); #if 0 ConnexionClient::getInstance().destroy(); #endif // The window takes ownership of the menu, so this has the side effect of destroying it. _window->setMenuBar(nullptr); _window->deleteLater(); qInstallMessageHandler(nullptr); // NOTE: Do this as late as possible so we continue to get our log messages } void Application::initializeGL() { qCDebug(interfaceapp) << "Created Display Window."; // initialize glut for shape drawing; Qt apparently initializes it on OS X if (_isGLInitialized) { return; } else { _isGLInitialized = true; } gpu::Context::init<gpu::gl::GLBackend>(); _gpuContext = std::make_shared<gpu::Context>(); // The gpu context can make child contexts for transfers, so // we need to restore primary rendering context _offscreenContext->makeCurrent(); initDisplay(); qCDebug(interfaceapp, "Initialized Display."); // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; _renderEngine->addJob<RenderShadowTask>("RenderShadowTask", cullFunctor); _renderEngine->addJob<RenderDeferredTask>("RenderDeferredTask", cullFunctor); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); // TODO: Load a cached config file // The UI can't be created until the primary OpenGL // context is created, because it needs to share // texture resources // Needs to happen AFTER the render engine initialization to access its configuration initializeUi(); qCDebug(interfaceapp, "Initialized Offscreen UI."); _offscreenContext->makeCurrent(); // call Menu getInstance static method to set up the menu // Needs to happen AFTER the QML UI initialization _window->setMenuBar(Menu::getInstance()); init(); qCDebug(interfaceapp, "init() complete."); // create thread for parsing of octree data independent of the main network and rendering threads _octreeProcessor.initialize(_enableProcessOctreeThread); connect(&_octreeProcessor, &OctreePacketProcessor::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); _entityEditSender.initialize(_enableProcessOctreeThread); _idleLoopStdev.reset(); // update before the first render update(0); } FrameTimingsScriptingInterface _frameTimingsScriptingInterface; extern void setupPreferences(); void Application::initializeUi() { AddressBarDialog::registerType(); ErrorDialog::registerType(); LoginDialog::registerType(); Tooltip::registerType(); UpdateDialog::registerType(); qmlRegisterType<Preference>("Hifi", 1, 0, "Preference"); auto offscreenUi = DependencyManager::get<OffscreenUi>(); offscreenUi->create(_offscreenContext->getContext()); auto rootContext = offscreenUi->getRootContext(); offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); // OffscreenUi is a subclass of OffscreenQmlSurface specifically designed to // support the window management and scripting proxies for VR use offscreenUi->createDesktop(QString("hifi/Desktop.qml")); // FIXME either expose so that dialogs can set this themselves or