_enableProcessOctreeThread(true), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), _aboutToQuit(false), _notifiedPacketVersionMismatchThisDomain(false), _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0){ PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); // FIXME this may be excessively conservative. On the other hand // maybe I'm used to having an 8-core machine // Perhaps find the ideal thread count and subtract 2 or 3 // (main thread, present thread, random OS load) // More threads == faster concurrent loads, but also more concurrent // load on the GPU until we can serialize GPU transfers (off the main thread) QThreadPool::globalInstance()->setMaxThreadCount(2); 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 _logger = new FileLogger(this); // After setting organization name in order to get correct directory qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us auto nodeList = DependencyManager::get<NodeList>(); // Set up a watchdog thread to intentionally crash the application on deadlocks _deadlockWatchdogThread = new DeadlockWatchdogThread(); _deadlockWatchdogThread->start(); qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); _bookmarks = new Bookmarks(); // Before setting up the menu // start the nodeThread so its event loop is running QThread* nodeThread = new QThread(this); nodeThread->setObjectName("NodeList Thread"); nodeThread->start(); // make sure the node thread is given highest priority nodeThread->setPriority(QThread::TimeCriticalPriority); // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(nodeList.data()); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); // put the NodeList and datagram processing on the node thread nodeList->moveToThread(nodeThread); // put the audio processing on a separate thread QThread* audioThread = new QThread(); audioThread->setObjectName("Audio Thread"); auto audioIO = DependencyManager::get<AudioClient>(); 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; }); audioIO->moveToThread(audioThread); recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { audioIO->handleRecordedAudioInput(frame->data); }); connect(audioIO.data(), &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); } }); auto& audioScriptingInterface = AudioScriptingInterface::getInstance(); connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start); connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit); connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled); connect(audioIO.data(), &AudioClient::mutedByMixer, &audioScriptingInterface, &AudioScriptingInterface::mutedByMixer); connect(audioIO.data(), &AudioClient::receivedFirstPacket, &audioScriptingInterface, &AudioScriptingInterface::receivedFirstPacket); connect(audioIO.data(), &AudioClient::disconnected, &audioScriptingInterface, &AudioScriptingInterface::disconnected); connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { auto audioClient = DependencyManager::get<AudioClient>(); auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(); float distance = glm::distance(myAvatarPosition, position); bool shouldMute = !audioClient->isMuted() && (distance < radius); if (shouldMute) { audioClient->toggleMute(); AudioScriptingInterface::getInstance().environmentMuted(); } }); audioThread->start(); ResourceManager::init(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); // Setup MessagesClient auto messagesClient = DependencyManager::get<MessagesClient>(); QThread* messagesThread = new QThread; messagesThread->setObjectName("Messages Client Thread"); messagesClient->moveToThread(messagesThread); connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); messagesThread->start(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND; auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>(); 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); // if we get a domain change, immediately attempt update location in metaverse server connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); // 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, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // you might think we could just do this in NodeList but we only want this connection for Interface connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); // connect to appropriate slots on AccountManager auto accountManager = DependencyManager::get<AccountManager>(); auto dialogsManager = DependencyManager::get<DialogsManager>(); connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // 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(NetworkingConstants::METAVERSE_SERVER_URL); auto addressManager = DependencyManager::get<AddressManager>(); // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter([this]{ return getMyAvatar()->getPosition(); }); addressManager->setOrientationGetter([this]{ return getMyAvatar()->getOrientation(); }); connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); // Save avatar location immediately after a teleport. connect(getMyAvatar(), &MyAvatar::positionGoneTo, DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress); auto scriptEngines = DependencyManager::get<ScriptEngines>().data(); scriptEngines->registerScriptInitializer([this](ScriptEngine* engine){ registerScriptEngineWithApplicationServices(engine); }); connect(scriptEngines, &ScriptEngines::scriptCountChanged, scriptEngines, [this] { auto scriptEngines = DependencyManager::get<ScriptEngines>(); if (scriptEngines->getRunningScripts().isEmpty()) { getMyAvatar()->clearScriptableSettings(); } }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { getEntities()->reloadEntityScripts(); }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptLoadError, scriptEngines, [](const QString& filename, const QString& error){ OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load."); }, Qt::QueuedConnection); #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); // connect to the packet sent signal of the _entityEditSender connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); // send the identity packet for our avatar each second to our avatar mixer connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket); identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); ResourceCache::setRequestLimit(3); _glWidget = new GLCanvas(); getApplicationCompositor().setRenderingWidget(_glWidget); _window->setCentralWidget(_glWidget); _window->restoreGeometry(); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); #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(Qt::BlankCursor); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); _glWidget->makeCurrent(); _glWidget->initializeGL(); _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->create(_glWidget->context()->contextHandle()); _chromiumShareContext->makeCurrent(); qt_gl_set_global_share_context(_chromiumShareContext->getContext()); _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->create(_glWidget->context()->contextHandle()); _offscreenContext->makeCurrent(); initializeGL(); _offscreenContext->makeCurrent(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); // 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. auto gpuIdent = GPUIdent::getInstance(); auto glContextData = getGLContextData(); QJsonObject properties = { { "version", applicationVersion() }, { "previousSessionCrashed", _previousSessionCrashed }, { "previousSessionRuntime", sessionRunTime.get() }, { "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.value("version").toString()) }, { "gl_version", glContextData["version"] }, { "gl_vender", glContextData["vendor"] }, { "gl_sl_version", glContextData["slVersion"] }, { "gl_renderer", glContextData["renderer"] } }; 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(); } UserActivityLogger::getInstance().logAction("launch", properties); // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); _entityEditSender.setMyAvatar(getMyAvatar()); // 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!! _overlays.init(); // do this before scripts load // Make sure we don't time out during slow operations at startup updateHeartbeat(); connect(this, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); // hook up bandwidth estimator QSharedPointer<BandwidthRecorder> bandwidthRecorder = DependencyManager::get<BandwidthRecorder>(); connect(nodeList.data(), &LimitedNodeList::dataSent, bandwidthRecorder.data(), &BandwidthRecorder::updateOutboundData); connect(&nodeList->getPacketReceiver(), &PacketReceiver::dataReceived, bandwidthRecorder.data(), &BandwidthRecorder::updateInboundData); // FIXME -- I'm a little concerned about this. connect(getMyAvatar()->getSkeletonModel().get(), &SkeletonModel::skeletonLoaded, this, &Application::checkSkeleton, Qt::QueuedConnection); // 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 offscreenUi = DependencyManager::get<OffscreenUi>(); if (offscreenUi->navigationFocused()) { auto actionEnum = static_cast<Action>(action); int key = Qt::Key_unknown; static int lastKey = Qt::Key_unknown; bool navAxis = false; switch (actionEnum) { 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; } if (navAxis) { if (lastKey != Qt::Key_unknown) { QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); lastKey = Qt::Key_unknown; } if (key != Qt::Key_unknown) { QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); lastKey = key; } } else if (key != Qt::Key_unknown) { if (state) { QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); } else { QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &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)) { DependencyManager::get<AudioClient>()->toggleMute(); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { if (!offscreenUi->navigationFocused()) { toggleMenuUnderReticle(); } } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { toggleMenuUnderReticle(); } 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(); } } }); _applicationStateDevice = userInputMapper->getStateDevice(); _applicationStateDevice->setInputVariant(STATE_IN_HMD, []() -> float { return qApp->isHMDMode() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float { return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float { return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float { return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0; }); // Setup the keyboardMouseDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); // force the model the look at the correct directory (weird order of operations issue) scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation()); // do this as late as possible so that all required subsystems are initialized scriptEngines->loadScripts(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); loadSettings(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now connect(&_settingsTimer, &QTimer::timeout, this, &Application::saveSettings); connect(&_settingsThread, SIGNAL(started()), &_settingsTimer, SLOT(start())); connect(&_settingsThread, SIGNAL(finished()), &_settingsTimer, SLOT(stop())); _settingsTimer.moveToThread(&_settingsThread); _settingsTimer.setSingleShot(false); _settingsTimer.setInterval(SAVE_SETTINGS_INTERVAL); _settingsThread.start(); if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); // So that camera doesn't auto-switch to third person. } else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) { Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true); cameraMenuChanged(); } else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) { Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true); cameraMenuChanged(); } // set the local loopback interface for local sounds from audio scripts AudioScriptingInterface::getInstance().setLocalAudioInterface(audioIO.data()); this->installEventFilter(this); // initialize our face trackers after loading the menu settings auto faceshiftTracker = DependencyManager::get<Faceshift>(); faceshiftTracker->init(); connect(faceshiftTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled);#ifdef HAVE_DDE auto ddeTracker = DependencyManager::get<DdeFaceTracker>(); ddeTracker->init();Lines 501–1000 of 5476. Previous lines Load more
_enableProcessOctreeThread(true), _lastNackTime(usecTimestampNow()), _lastSendDownstreamAudioStats(usecTimestampNow()), _aboutToQuit(false), _notifiedPacketVersionMismatchThisDomain(false), _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0) { PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); // FIXME this may be excessively conservative. On the other hand // maybe I'm used to having an 8-core machine // Perhaps find the ideal thread count and subtract 2 or 3 // (main thread, present thread, random OS load) // More threads == faster concurrent loads, but also more concurrent // load on the GPU until we can serialize GPU transfers (off the main thread) QThreadPool::globalInstance()->setMaxThreadCount(2); 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 _logger = new FileLogger(this); // After setting organization name in order to get correct directory qInstallMessageHandler(messageHandler); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us auto nodeList = DependencyManager::get<NodeList>(); // Set up a watchdog thread to intentionally crash the application on deadlocks _deadlockWatchdogThread = new DeadlockWatchdogThread(); _deadlockWatchdogThread->start(); qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion()); _bookmarks = new Bookmarks(); // Before setting up the menu // start the nodeThread so its event loop is running QThread* nodeThread = new QThread(this); nodeThread->setObjectName("NodeList Thread"); nodeThread->start(); // make sure the node thread is given highest priority nodeThread->setPriority(QThread::TimeCriticalPriority); // setup a timer for domain-server check ins QTimer* domainCheckInTimer = new QTimer(nodeList.data()); connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); // put the NodeList and datagram processing on the node thread nodeList->moveToThread(nodeThread); // put the audio processing on a separate thread QThread* audioThread = new QThread(); audioThread->setObjectName("Audio Thread"); auto audioIO = DependencyManager::get<AudioClient>(); 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; }); audioIO->moveToThread(audioThread); recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) { audioIO->handleRecordedAudioInput(frame->data); }); connect(audioIO.data(), &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); } }); auto& audioScriptingInterface = AudioScriptingInterface::getInstance(); connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start); connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit); connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled); connect(audioIO.data(), &AudioClient::mutedByMixer, &audioScriptingInterface, &AudioScriptingInterface::mutedByMixer); connect(audioIO.data(), &AudioClient::receivedFirstPacket, &audioScriptingInterface, &AudioScriptingInterface::receivedFirstPacket); connect(audioIO.data(), &AudioClient::disconnected, &audioScriptingInterface, &AudioScriptingInterface::disconnected); connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { auto audioClient = DependencyManager::get<AudioClient>(); auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(); float distance = glm::distance(myAvatarPosition, position); bool shouldMute = !audioClient->isMuted() && (distance < radius); if (shouldMute) { audioClient->toggleMute(); AudioScriptingInterface::getInstance().environmentMuted(); } }); audioThread->start(); ResourceManager::init(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); // Setup MessagesClient auto messagesClient = DependencyManager::get<MessagesClient>(); QThread* messagesThread = new QThread; messagesThread->setObjectName("Messages Client Thread"); messagesClient->moveToThread(messagesThread); connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init); messagesThread->start(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &Application::domainConnectionRefused); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * MSECS_PER_SECOND; auto discoverabilityManager = DependencyManager::get<DiscoverabilityManager>(); 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); // if we get a domain change, immediately attempt update location in metaverse server connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); // 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, getMyAvatar(), &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // you might think we could just do this in NodeList but we only want this connection for Interface connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); // connect to appropriate slots on AccountManager auto accountManager = DependencyManager::get<AccountManager>(); auto dialogsManager = DependencyManager::get<DialogsManager>(); connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); // 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(NetworkingConstants::METAVERSE_SERVER_URL); auto addressManager = DependencyManager::get<AddressManager>(); // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter([this]{ return getMyAvatar()->getPosition(); }); addressManager->setOrientationGetter([this]{ return getMyAvatar()->getOrientation(); }); connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); // Save avatar location immediately after a teleport. connect(getMyAvatar(), &MyAvatar::positionGoneTo, DependencyManager::get<AddressManager>().data(), &AddressManager::storeCurrentAddress); auto scriptEngines = DependencyManager::get<ScriptEngines>().data(); scriptEngines->registerScriptInitializer([this](ScriptEngine* engine){ registerScriptEngineWithApplicationServices(engine); }); connect(scriptEngines, &ScriptEngines::scriptCountChanged, scriptEngines, [this] { auto scriptEngines = DependencyManager::get<ScriptEngines>(); if (scriptEngines->getRunningScripts().isEmpty()) { getMyAvatar()->clearScriptableSettings(); } }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptsReloading, scriptEngines, [this] { getEntities()->reloadEntityScripts(); }, Qt::QueuedConnection); connect(scriptEngines, &ScriptEngines::scriptLoadError, scriptEngines, [](const QString& filename, const QString& error){ OffscreenUi::warning(nullptr, "Error Loading Script", filename + " failed to load."); }, Qt::QueuedConnection); #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); // connect to the packet sent signal of the _entityEditSender connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); // send the identity packet for our avatar each second to our avatar mixer connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket); identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); ResourceCache::setRequestLimit(3); _glWidget = new GLCanvas(); getApplicationCompositor().setRenderingWidget(_glWidget); _window->setCentralWidget(_glWidget); _window->restoreGeometry(); _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); #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(Qt::BlankCursor); // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); _glWidget->makeCurrent(); _glWidget->initializeGL(); _chromiumShareContext = new OffscreenGLCanvas(); _chromiumShareContext->create(_glWidget->context()->contextHandle()); _chromiumShareContext->makeCurrent(); qt_gl_set_global_share_context(_chromiumShareContext->getContext()); _offscreenContext = new OffscreenGLCanvas(); _offscreenContext->create(_glWidget->context()->contextHandle()); _offscreenContext->makeCurrent(); initializeGL(); _offscreenContext->makeCurrent(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); // 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. auto gpuIdent = GPUIdent::getInstance(); auto glContextData = getGLContextData(); QJsonObject properties = { { "version", applicationVersion() }, { "previousSessionCrashed", _previousSessionCrashed }, { "previousSessionRuntime", sessionRunTime.get() }, { "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.value("version").toString()) }, { "gl_version", glContextData["version"] }, { "gl_vender", glContextData["vendor"] }, { "gl_sl_version", glContextData["slVersion"] }, { "gl_renderer", glContextData["renderer"] } }; 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(); } UserActivityLogger::getInstance().logAction("launch", properties); // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); _entityEditSender.setMyAvatar(getMyAvatar()); // 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!! _overlays.init(); // do this before scripts load // Make sure we don't time out during slow operations at startup updateHeartbeat(); connect(this, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); // hook up bandwidth estimator QSharedPointer<BandwidthRecorder> bandwidthRecorder = DependencyManager::get<BandwidthRecorder>(); connect(nodeList.data(), &LimitedNodeList::dataSent, bandwidthRecorder.data(), &BandwidthRecorder::updateOutboundData); connect(&nodeList->getPacketReceiver(), &PacketReceiver::dataReceived, bandwidthRecorder.data(), &BandwidthRecorder::updateInboundData); // FIXME -- I'm a little concerned about this. connect(getMyAvatar()->getSkeletonModel().get(), &SkeletonModel::skeletonLoaded, this, &Application::checkSkeleton, Qt::QueuedConnection); // 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 offscreenUi = DependencyManager::get<OffscreenUi>(); if (offscreenUi->navigationFocused()) { auto actionEnum = static_cast<Action>(action); int key = Qt::Key_unknown; static int lastKey = Qt::Key_unknown; bool navAxis = false; switch (actionEnum) { 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; } if (navAxis) { if (lastKey != Qt::Key_unknown) { QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); lastKey = Qt::Key_unknown; } if (key != Qt::Key_unknown) { QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); lastKey = key; } } else if (key != Qt::Key_unknown) { if (state) { QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &event); } else { QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); sendEvent(offscreenUi->getWindow(), &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)) { DependencyManager::get<AudioClient>()->toggleMute(); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); } else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) { if (!offscreenUi->navigationFocused()) { toggleMenuUnderReticle(); } } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { toggleMenuUnderReticle(); } 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(); } } }); _applicationStateDevice = userInputMapper->getStateDevice(); _applicationStateDevice->setInputVariant(STATE_IN_HMD, []() -> float { return qApp->isHMDMode() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float { return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float { return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_NAV_FOCUSED, []() -> float { return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0; }); // Setup the keyboardMouseDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); // force the model the look at the correct directory (weird order of operations issue) scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation()); // do this as late as possible so that all required subsystems are initialized scriptEngines->loadScripts(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); loadSettings(); // Make sure we don't time out during slow operations at startup updateHeartbeat(); int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now connect(&_settingsTimer, &QTimer::timeout, this, &Application::saveSettings); connect(&_settingsThread, SIGNAL(started()), &_settingsTimer, SLOT(start())); connect(&_settingsThread, SIGNAL(finished()), &_settingsTimer, SLOT(stop())); _settingsTimer.moveToThread(&_settingsThread); _settingsTimer.setSingleShot(false); _settingsTimer.setInterval(SAVE_SETTINGS_INTERVAL); _settingsThread.start(); if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN); // So that camera doesn't auto-switch to third person. } else if (Menu::getInstance()->isOptionChecked(MenuOption::IndependentMode)) { Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true); cameraMenuChanged(); } else if (Menu::getInstance()->isOptionChecked(MenuOption::CameraEntityMode)) { Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true); cameraMenuChanged(); } // set the local loopback interface for local sounds from audio scripts AudioScriptingInterface::getInstance().setLocalAudioInterface(audioIO.data()); this->installEventFilter(this); // initialize our face trackers after loading the menu settings auto faceshiftTracker = DependencyManager::get<Faceshift>(); faceshiftTracker->init(); connect(faceshiftTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #ifdef HAVE_DDE auto ddeTracker = DependencyManager::get<DdeFaceTracker>(); ddeTracker->init();