diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 54e9fa2fba..34dc25914f 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -138,7 +138,7 @@ public: /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const override; - virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; + Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; /**jsdoc * @comment Uses the base class's JSDoc. diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 29656f4465..75399d1a9b 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -358,7 +358,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo nodeData->setNodeVersion(it->second.getNodeVersion()); nodeData->setHardwareAddress(nodeConnection.hardwareAddress); nodeData->setMachineFingerprint(nodeConnection.machineFingerprint); - + nodeData->setLastDomainCheckinTimestamp(nodeConnection.lastPingTimestamp); nodeData->setWasAssigned(true); // cleanup the PendingAssignedNodeData for this assignment now that it's connecting @@ -499,6 +499,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect // set the machine fingerprint passed in the connect request nodeData->setMachineFingerprint(nodeConnection.machineFingerprint); + // set the last ping timestamp passed in the connect request + nodeData->setLastDomainCheckinTimestamp(nodeConnection.lastPingTimestamp); + // also add an interpolation to DomainServerNodeData so that servers can get username in stats nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY, uuidStringWithoutCurlyBraces(newNode->getUUID()), username); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 051dd989f5..37643cc372 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1068,6 +1068,8 @@ void DomainServer::processListRequestPacket(QSharedPointer mess // update the connecting hostname in case it has changed nodeData->setPlaceName(nodeRequestData.placeName); + nodeData->setLastDomainCheckinTimestamp(nodeRequestData.lastPingTimestamp); + sendDomainListToNode(sendingNode, message->getSenderSockAddr()); } @@ -1174,6 +1176,10 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = static_cast(node->getLinkedData()); + domainListStream << nodeData->getLastDomainCheckinTimestamp(); + + domainListStream << usecTimestampNow(); + // store the nodeInterestSet on this DomainServerNodeData, in case it has changed auto& nodeInterestSet = nodeData->getNodeInterestSet(); diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index f465cceb96..370886cbce 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -61,6 +61,9 @@ public: void setMachineFingerprint(const QUuid& machineFingerprint) { _machineFingerprint = machineFingerprint; } const QUuid& getMachineFingerprint() { return _machineFingerprint; } + void setLastDomainCheckinTimestamp(quint64 lastDomainCheckinTimestamp) { _lastDomainCheckinTimestamp = lastDomainCheckinTimestamp; } + quint64 getLastDomainCheckinTimestamp() { return _lastDomainCheckinTimestamp; } + void addOverrideForKey(const QString& key, const QString& value, const QString& overrideValue); void removeOverrideForKey(const QString& key, const QString& value); @@ -93,7 +96,7 @@ private: QString _nodeVersion; QString _hardwareAddress; QUuid _machineFingerprint; - + quint64 _lastDomainCheckinTimestamp; QString _placeName; bool _wasAssigned { false }; diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 0a3782d79b..b3ea005bd1 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -36,6 +36,8 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c // now the machine fingerprint dataStream >> newHeader.machineFingerprint; } + + dataStream >> newHeader.lastPingTimestamp; dataStream >> newHeader.nodeType >> newHeader.publicSockAddr >> newHeader.localSockAddr diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index dd9ca6b650..779411d11e 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -22,6 +22,7 @@ public: bool isConnectRequest = true); QUuid connectUUID; + quint64 lastPingTimestamp{ 0 }; NodeType_t nodeType; HifiSockAddr publicSockAddr; HifiSockAddr localSockAddr; diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml index 57bec2250f..b426f2d986 100644 --- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml @@ -58,7 +58,8 @@ Rectangle { if (isLoggedIn) { Commerce.getWalletStatus(); } else { - // Show some error to the user + errorText.text = "There was a problem while retrieving your inventory. " + + "Please try closing and re-opening the Avatar app.\n\nLogin status result: " + isLoggedIn; } } @@ -66,11 +67,19 @@ Rectangle { if (walletStatus === 5) { getInventory(); } else { - // Show some error to the user + errorText.text = "There was a problem while retrieving your inventory. " + + "Please try closing and re-opening the Avatar app.\n\nWallet status result: " + walletStatus; } } onInventoryResult: { + if (result.status !== "success") { + errorText.text = "There was a problem while retrieving your inventory. " + + "Please try closing and re-opening the Avatar app.\n\nInventory status: " + result.status + "\nMessage: " + result.message; + } else if (result.data && result.data.assets && result.data.assets.length === 0) { + errorText.text = "You have not created any avatars yet! Create an avatar with the Avatar Creator, then close and re-open the Avatar App." + } + avatarAppInventoryModel.handlePage(result.status !== "success" && result.message, result); root.updatePreviewUrl(); } @@ -172,7 +181,7 @@ Rectangle { anchors.bottom: parent.bottom AnimatedImage { - visible: !inventoryContentsList.visible + visible: !inventoryContentsList.visible && !errorText.visible anchors.centerIn: parent width: 72 height: width @@ -181,7 +190,7 @@ Rectangle { ListView { id: inventoryContentsList - visible: avatarAppInventoryModel.count !== 0 + visible: avatarAppInventoryModel.count !== 0 && !errorText.visible interactive: contentItem.height > height clip: true model: avatarAppInventoryModel @@ -196,6 +205,18 @@ Rectangle { standaloneIncompatible: model.standalone_incompatible } } + + HifiStylesUit.GraphikRegular { + id: errorText + text: "" + visible: text !== "" + anchors.fill: parent + size: 22 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } } diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml index 840a2bb69a..9ac72fa3cd 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml @@ -19,8 +19,8 @@ Flickable { id: root contentWidth: parent.width contentHeight: audioColumnLayout.height - topMargin: 16 - bottomMargin: 16 + topMargin: 24 + bottomMargin: 24 clip: true function changePeakValuesEnabled(enabled) { @@ -73,14 +73,23 @@ Flickable { Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin height: 30 labelText: "People Volume" - from: -60 - to: 10 + from: simplifiedUI.numericConstants.mutedValue + to: 20.0 defaultValue: 0.0 - value: AudioScriptingInterface.getAvatarGain() + stepSize: 5.0 + value: AudioScriptingInterface.avatarGain live: true + function updatePeopleGain(sliderValue) { + if (AudioScriptingInterface.avatarGain !== sliderValue) { + AudioScriptingInterface.avatarGain = sliderValue; + } + } onValueChanged: { - if (AudioScriptingInterface.getAvatarGain() != peopleVolume.value) { - AudioScriptingInterface.setAvatarGain(peopleVolume.value); + updatePeopleGain(value); + } + onPressedChanged: { + if (!pressed) { + updatePeopleGain(value); } } } @@ -92,14 +101,24 @@ Flickable { Layout.topMargin: 2 height: 30 labelText: "Environment Volume" - from: -60 - to: 10 + from: simplifiedUI.numericConstants.mutedValue + to: 20.0 defaultValue: 0.0 - value: AudioScriptingInterface.getInjectorGain() + stepSize: 5.0 + value: AudioScriptingInterface.serverInjectorGain live: true + function updateEnvironmentGain(sliderValue) { + if (AudioScriptingInterface.serverInjectorGain !== sliderValue) { + AudioScriptingInterface.serverInjectorGain = sliderValue; + AudioScriptingInterface.localInjectorGain = sliderValue; + } + } onValueChanged: { - if (AudioScriptingInterface.getInjectorGain() != environmentVolume.value) { - AudioScriptingInterface.setInjectorGain(environmentVolume.value); + updateEnvironmentGain(value); + } + onPressedChanged: { + if (!pressed) { + updateEnvironmentGain(value); } } } @@ -111,14 +130,23 @@ Flickable { Layout.topMargin: 2 height: 30 labelText: "System Sound Volume" - from: -60 - to: 10 + from: simplifiedUI.numericConstants.mutedValue + to: 20.0 defaultValue: 0.0 - value: AudioScriptingInterface.getSystemInjectorGain() + stepSize: 5.0 + value: AudioScriptingInterface.systemInjectorGain live: true + function updateSystemGain(sliderValue) { + if (AudioScriptingInterface.systemInjectorGain !== sliderValue) { + AudioScriptingInterface.systemInjectorGain = sliderValue; + } + } onValueChanged: { - if (AudioScriptingInterface.getSystemInjectorGain() != systemSoundVolume.value) { - AudioScriptingInterface.setSystemInjectorGain(systemSoundVolume.value); + updateSystemGain(value); + } + onPressedChanged: { + if (!pressed) { + updateSystemGain(value); } } } @@ -189,7 +217,7 @@ Flickable { Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false height: contentItem.height - spacing: 4 + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.input delegate: Item { @@ -200,6 +228,8 @@ Flickable { id: inputDeviceCheckbox anchors.left: parent.left width: parent.width - inputLevel.width + height: paintedHeight + wrapLabel: false checked: selectedDesktop text: model.devicename ButtonGroup.group: inputDeviceButtonGroup @@ -288,7 +318,7 @@ Flickable { Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false height: contentItem.height - spacing: 4 + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.output delegate: Item { @@ -299,8 +329,10 @@ Flickable { id: outputDeviceCheckbox anchors.left: parent.left width: parent.width + height: paintedHeight checked: selectedDesktop text: model.devicename + wrapLabel: false ButtonGroup.group: outputDeviceButtonGroup onClicked: { AudioScriptingInterface.setOutputDevice(model.info, false); // `false` argument for Desktop mode setting diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml index de5e75b7e5..e32890a2dd 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml @@ -20,8 +20,8 @@ Flickable { id: root contentWidth: parent.width contentHeight: generalColumnLayout.height - topMargin: 16 - bottomMargin: 16 + topMargin: 24 + bottomMargin: 24 clip: true onAvatarNametagModeChanged: { @@ -63,6 +63,7 @@ Flickable { ColumnLayout { id: avatarNameTagsRadioButtonGroup Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons SimplifiedControls.RadioButton { id: avatarNameTagsOff @@ -110,6 +111,7 @@ Flickable { ColumnLayout { id: performanceRadioButtonGroup Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons SimplifiedControls.RadioButton { id: performanceLow @@ -157,6 +159,7 @@ Flickable { ColumnLayout { id: cameraRadioButtonGroup Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons SimplifiedControls.RadioButton { id: firstPerson diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml index a462af0722..96dbc5e180 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml @@ -19,8 +19,8 @@ Flickable { id: root contentWidth: parent.width contentHeight: vrColumnLayout.height - topMargin: 16 - bottomMargin: 16 + topMargin: 24 + bottomMargin: 24 clip: true function changePeakValuesEnabled(enabled) { @@ -70,6 +70,7 @@ Flickable { id: controlsRadioButtonGroup width: parent.width Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons ButtonGroup { id: controlsButtonGroup } @@ -202,7 +203,7 @@ Flickable { Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false height: contentItem.height - spacing: 4 + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.input delegate: Item { @@ -301,7 +302,7 @@ Flickable { Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin interactive: false height: contentItem.height - spacing: 4 + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons clip: true model: AudioScriptingInterface.devices.output delegate: Item { diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml index 5ccc1a7e5c..1f628b041d 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml @@ -147,7 +147,7 @@ QtObject { } readonly property color darkSeparator: "#595959" - readonly property color darkBackground: "#1A1A1A" + readonly property color darkBackground: "#000000" readonly property color darkBackgroundHighlight: "#575757" readonly property color highlightOnDark: Qt.rgba(1, 1, 1, 0.2) readonly property color white: "#FFFFFF" @@ -182,9 +182,10 @@ QtObject { } readonly property QtObject settings: QtObject { - property real subtitleTopMargin: 2 - property real settingsGroupTopMargin: 10 - property real spacingBetweenSettings: 48 + property int subtitleTopMargin: 2 + property int settingsGroupTopMargin: 24 + property int spacingBetweenSettings: 48 + property int spacingBetweenRadiobuttons: 14 } } @@ -220,4 +221,8 @@ QtObject { } } } + + readonly property QtObject numericConstants: QtObject { + readonly property real mutedValue: -60.0 + } } diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml index 59c4fa26e4..43d4aaee33 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/RadioButton.qml @@ -87,7 +87,6 @@ RadioButton { contentItem: Text { id: radioButtonLabel - height: root.radioButtonRadius font.pixelSize: 14 font.family: "Graphik" font.weight: Font.Normal @@ -99,5 +98,6 @@ RadioButton { enabled: root.enabled verticalAlignment: Text.AlignVCenter leftPadding: radioButtonIndicator.width + root.labelLeftMargin + topPadding: -3 // For perfect alignment when using Graphik } } diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Slider.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Slider.qml index 2b1dc68261..38b114e9d2 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Slider.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/Slider.qml @@ -30,6 +30,7 @@ Item { property alias live: sliderControl.live property alias stepSize: sliderControl.stepSize property alias snapMode: sliderControl.snapMode + property alias pressed: sliderControl.pressed property real defaultValue: 0.0 HifiStylesUit.GraphikRegular { diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index 09873396fa..27a786ece2 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -203,7 +203,10 @@ Rectangle { Image { id: outputDeviceButton - property bool outputMuted: false + property bool outputMuted: AudioScriptingInterface.avatarGain === simplifiedUI.numericConstants.mutedValue && + AudioScriptingInterface.serverInjectorGain === simplifiedUI.numericConstants.mutedValue && + AudioScriptingInterface.localInjectorGain === simplifiedUI.numericConstants.mutedValue && + AudioScriptingInterface.systemInjectorGain === simplifiedUI.numericConstants.mutedValue source: outputDeviceButton.outputMuted ? "./images/outputDeviceMuted.svg" : "./images/outputDeviceLoud.svg" anchors.centerIn: parent width: 20 @@ -228,13 +231,16 @@ Rectangle { } onClicked: { Tablet.playSound(TabletEnums.ButtonClick); - outputDeviceButton.outputMuted = !outputDeviceButton.outputMuted; + + if (!outputDeviceButton.outputMuted && !AudioScriptingInterface.muted) { + AudioScriptingInterface.muted = true; + } sendToScript({ "source": "SimplifiedTopBar.qml", "method": "setOutputMuted", "data": { - "outputMuted": outputDeviceButton.outputMuted + "outputMuted": !outputDeviceButton.outputMuted } }); } @@ -242,6 +248,69 @@ Rectangle { } + Item { + id: statusButtonContainer + anchors.verticalCenter: parent.verticalCenter + anchors.left: outputDeviceButtonContainer.right + anchors.leftMargin: 8 + width: 36 + height: width + + Rectangle { + id: statusButton + property string currentStatus + anchors.centerIn: parent + anchors.horizontalCenterOffset: 1 + anchors.verticalCenterOffset: 2 + width: 13 + height: width + radius: width/2 + visible: false + } + + ColorOverlay { + anchors.fill: statusButton + opacity: statusButton.currentStatus ? 1 : 0 + source: statusButton + color: if (statusButton.currentStatus === "busy") { + "#ff001a" + } else if (statusButton.currentStatus === "available") { + "#009036" + } else if (statusButton.currentStatus) { + "#ffed00" + } + } + + Image { + id: focusIcon + source: "./images/focus.svg" + opacity: statusButtonMouseArea.containsMouse ? 1.0 : (statusButton.currentStatus === "busy" ? 0.7 : 0.3) + anchors.centerIn: parent + width: 36 + height: 20 + fillMode: Image.PreserveAspectFit + } + + MouseArea { + id: statusButtonMouseArea + anchors.fill: parent + enabled: statusButton.currentStatus + hoverEnabled: true + onEntered: { + Tablet.playSound(TabletEnums.ButtonHover); + } + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + + sendToScript({ + "source": "SimplifiedTopBar.qml", + "method": "toggleStatus" + }); + } + } + } + + Item { id: hmdButtonContainer @@ -280,11 +349,6 @@ Rectangle { Tablet.playSound(TabletEnums.ButtonClick); var displayPluginCount = Window.getDisplayPluginCount(); if (HMD.active) { - // This next line seems backwards and shouldn't be necessary - the NOTIFY handler should - // result in `displayModeImage.source` changing automatically - but that's not working. - // This is working. So, I'm keeping it. - displayModeImage.source = "./images/vrMode.svg"; - // Switch to desktop mode - selects first VR display plugin for (var i = 0; i < displayPluginCount; i++) { if (!Window.isDisplayPluginHmd(i)) { @@ -293,11 +357,6 @@ Rectangle { } } } else { - // This next line seems backwards and shouldn't be necessary - the NOTIFY handler should - // result in `displayModeImage.source` changing automatically - but that's not working. - // This is working. So, I'm keeping it. - displayModeImage.source = "./images/desktopMode.svg"; - // Switch to VR mode - selects first HMD display plugin for (var i = 0; i < displayPluginCount; i++) { if (Window.isDisplayPluginHmd(i)) { @@ -397,8 +456,8 @@ Rectangle { } break; - case "updateOutputMuted": - outputDeviceButton.outputMuted = message.data.outputMuted; + case "updateStatusButton": + statusButton.currentStatus = message.data.currentStatus; break; default: diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/focus.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/focus.svg new file mode 100644 index 0000000000..f7950650c6 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/focus.svg @@ -0,0 +1,13 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg b/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg new file mode 100644 index 0000000000..ebd844c471 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/images/status.svg @@ -0,0 +1,3 @@ + + + diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 77d59bbd72..7606c108c6 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -49,17 +49,13 @@ #include "DeferredLightingEffect.h" #include "PickManager.h" -#include "LightingModel.h" -#include "AmbientOcclusionEffect.h" -#include "RenderShadowTask.h" -#include "AntialiasingEffect.h" - #include "scripting/SettingsScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif #include "MeshPartPayload.h" +#include "scripting/RenderScriptingInterface.h" extern bool DEV_DECIMATE_TEXTURES; @@ -369,45 +365,14 @@ Menu::Menu() { // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); - action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AntiAliasing, 0, true); - connect(action, &QAction::triggered, [action] { - auto renderConfig = qApp->getRenderEngine()->getConfiguration(); - if (renderConfig) { - auto mainViewJitterCamConfig = renderConfig->getConfig("RenderMainView.JitterCam"); - auto mainViewAntialiasingConfig = renderConfig->getConfig("RenderMainView.Antialiasing"); - if (mainViewJitterCamConfig && mainViewAntialiasingConfig) { - if (action->isChecked()) { - mainViewJitterCamConfig->play(); - mainViewAntialiasingConfig->setDebugFXAA(false); - } else { - mainViewJitterCamConfig->none(); - mainViewAntialiasingConfig->setDebugFXAA(true); - } - } - } - }); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AntiAliasing, 0, RenderScriptingInterface::getInstance()->getAntialiasingEnabled(), + RenderScriptingInterface::getInstance(), SLOT(setAntialiasingEnabled(bool))); - action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, true); - connect(action, &QAction::triggered, [action] { - auto renderConfig = qApp->getRenderEngine()->getConfiguration(); - if (renderConfig) { - auto lightingModelConfig = renderConfig->getConfig("RenderMainView.LightingModel"); - if (lightingModelConfig) { - lightingModelConfig->setShadow(action->isChecked()); - } - } - }); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, RenderScriptingInterface::getInstance()->getShadowsEnabled(), + RenderScriptingInterface::getInstance(), SLOT(setShadowsEnabled(bool))); - action = addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion, 0, false); - connect(action, &QAction::triggered, [action] { - auto renderConfig = qApp->getRenderEngine()->getConfiguration(); - if (renderConfig) { - auto lightingModelConfig = renderConfig->getConfig("RenderMainView.LightingModel"); - if (lightingModelConfig) { - lightingModelConfig->setAmbientOcclusion(action->isChecked()); - } - } - }); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion, 0, RenderScriptingInterface::getInstance()->getAmbientOcclusionEnabled(), + RenderScriptingInterface::getInstance(), SLOT(setAmbientOcclusionEnabled(bool))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DefaultSkybox, 0, true); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 39f2d9f332..7ac2103543 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2367,6 +2367,11 @@ void MyAvatar::clearJointsData() { } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setSkeletonModelURL", Q_ARG(const QUrl&, skeletonModelURL)); + return; + } + _skeletonModelChangeCount++; int skeletonModelChangeCount = _skeletonModelChangeCount; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bb5e4c17df..5b12885d1f 100755 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2409,7 +2409,7 @@ private: void updateEyeContactTarget(float deltaTime); // These are made private for MyAvatar so that you will use the "use" methods instead - virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; + Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void updatePalms() override {} void lateUpdatePalms(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 11054d25d0..9d8b733ba7 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -75,6 +75,7 @@ int main(int argc, const char* argv[]) { QCommandLineOption helpOption = parser.addHelpOption(); QCommandLineOption urlOption("url", "", "value"); + QCommandLineOption noLauncherOption("no-launcher", "Do not execute the launcher"); QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater"); QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications"); QCommandLineOption runServerOption("runServer", "Whether to run the server"); @@ -84,6 +85,7 @@ int main(int argc, const char* argv[]) { QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts ", "path"); parser.addOption(urlOption); + parser.addOption(noLauncherOption); parser.addOption(noUpdaterOption); parser.addOption(checkMinSpecOption); parser.addOption(runServerOption); @@ -106,6 +108,49 @@ int main(int argc, const char* argv[]) { Q_UNREACHABLE(); } + QString applicationPath; + { + // A temporary application instance is needed to get the location of the running executable + // Tests using high_resolution_clock show that this takes about 30-50 microseconds (on my machine, YMMV) + // If we wanted to avoid the QCoreApplication, we would need to write our own + // cross-platform implementation. + QCoreApplication tempApp(argc, const_cast(argv)); + applicationPath = QCoreApplication::applicationDirPath(); + } + + static const QString APPLICATION_CONFIG_FILENAME = "config.json"; + QDir applicationDir(applicationPath); + QFile configFile(applicationDir.filePath(APPLICATION_CONFIG_FILENAME)); + + if (configFile.exists()) { + if (!configFile.open(QIODevice::ReadOnly)) { + qWarning() << "Found application config, but could not open it"; + } else { + auto contents = configFile.readAll(); + QJsonParseError error; + + auto doc = QJsonDocument::fromJson(contents, &error); + if (error.error) { + qWarning() << "Found application config, but could not parse it: " << error.errorString(); + } else { + static const QString LAUNCHER_PATH_KEY = "launcherPath"; + QString launcherPath = doc.object()[LAUNCHER_PATH_KEY].toString(); + if (!launcherPath.isEmpty()) { + if (!parser.isSet(noLauncherOption)) { + qDebug() << "Found a launcherPath in application config. Starting launcher."; + QProcess launcher; + launcher.setProgram(launcherPath); + launcher.startDetached(); + return 0; + } else { + qDebug() << "Found a launcherPath in application config, but the launcher" + " has been suppressed. Continuing normal execution."; + } + } + } + } + } + // Early check for --traceFile argument auto tracer = DependencyManager::set(); const char * traceFile = nullptr; diff --git a/interface/src/octree/SafeLanding.cpp b/interface/src/octree/SafeLanding.cpp index 479c2a5860..2e11de508b 100644 --- a/interface/src/octree/SafeLanding.cpp +++ b/interface/src/octree/SafeLanding.cpp @@ -10,7 +10,6 @@ // #include "SafeLanding.h" - #include #include "EntityTreeRenderer.h" @@ -35,24 +34,26 @@ bool SafeLanding::SequenceLessThan::operator()(const int& a, const int& b) const } void SafeLanding::startEntitySequence(QSharedPointer entityTreeRenderer) { - auto entityTree = entityTreeRenderer->getTree(); - if (entityTree) { - Locker lock(_lock); - _entityTree = entityTree; - _trackedEntities.clear(); - _trackingEntities = true; - _maxTrackedEntityCount = 0; - connect(std::const_pointer_cast(_entityTree).get(), - &EntityTree::addingEntity, this, &SafeLanding::addTrackedEntity); - connect(std::const_pointer_cast(_entityTree).get(), - &EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity); + if (!entityTreeRenderer.isNull()) { + auto entityTree = entityTreeRenderer->getTree(); + if (entityTree) { + Locker lock(_lock); + _entityTreeRenderer = entityTreeRenderer; + _trackedEntities.clear(); + _trackingEntities = true; + _maxTrackedEntityCount = 0; + connect(std::const_pointer_cast(entityTree).get(), + &EntityTree::addingEntity, this, &SafeLanding::addTrackedEntity, Qt::DirectConnection); + connect(std::const_pointer_cast(entityTree).get(), + &EntityTree::deletingEntity, this, &SafeLanding::deleteTrackedEntity); - _sequenceNumbers.clear(); - _initialStart = INVALID_SEQUENCE; - _initialEnd = INVALID_SEQUENCE; - _startTime = usecTimestampNow(); - EntityTreeRenderer::setEntityLoadingPriorityFunction(&ElevatedPriority); + _sequenceNumbers.clear(); + _initialStart = INVALID_SEQUENCE; + _initialEnd = INVALID_SEQUENCE; + _startTime = usecTimestampNow(); + EntityTreeRenderer::setEntityLoadingPriorityFunction(&ElevatedPriority); + } } } @@ -70,7 +71,12 @@ void SafeLanding::stopEntitySequence() { void SafeLanding::addTrackedEntity(const EntityItemID& entityID) { if (_trackingEntities) { Locker lock(_lock); - EntityItemPointer entity = _entityTree->findEntityByID(entityID); + + if (_entityTreeRenderer.isNull() || _entityTreeRenderer->getTree() == nullptr) { + return; + } + + EntityItemPointer entity = _entityTreeRenderer->getTree()->findEntityByID(entityID); if (entity && !entity->isLocalEntity() && entity->getCreated() < _startTime) { @@ -111,7 +117,7 @@ bool SafeLanding::isLoadSequenceComplete() { Locker lock(_lock); _initialStart = INVALID_SEQUENCE; _initialEnd = INVALID_SEQUENCE; - _entityTree = nullptr; + _entityTreeRenderer.clear(); _trackingEntities = false; // Don't track anything else that comes in. EntityTreeRenderer::setEntityLoadingPriorityFunction(StandardPriority); } @@ -158,7 +164,7 @@ bool SafeLanding::isSequenceNumbersComplete() { return false; } -bool isEntityPhysicsReady(const EntityItemPointer& entity) { +bool SafeLanding::isEntityPhysicsReady(const EntityItemPointer& entity) { if (entity && !entity->getCollisionless()) { const auto& entityType = entity->getType(); if (entityType == EntityTypes::Model) { @@ -168,7 +174,10 @@ bool isEntityPhysicsReady(const EntityItemPointer& entity) { bool hasAABox; entity->getAABox(hasAABox); if (hasAABox && downloadedCollisionTypes.count(modelEntity->getShapeType()) != 0) { - return (!entity->shouldBePhysical() || entity->isInPhysicsSimulation() || modelEntity->computeShapeFailedToLoad()); + auto space = _entityTreeRenderer->getWorkloadSpace(); + uint8_t region = space ? space->getRegion(entity->getSpaceIndex()) : (uint8_t)workload::Region::INVALID; + bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); + return (!shouldBePhysical || entity->isInPhysicsSimulation() || modelEntity->computeShapeFailedToLoad()); } } } diff --git a/interface/src/octree/SafeLanding.h b/interface/src/octree/SafeLanding.h index 51357b60ff..428ca15bdc 100644 --- a/interface/src/octree/SafeLanding.h +++ b/interface/src/octree/SafeLanding.h @@ -38,13 +38,14 @@ private slots: private: bool isSequenceNumbersComplete(); + bool isEntityPhysicsReady(const EntityItemPointer& entity); void debugDumpSequenceIDs() const; bool isEntityLoadingComplete(); std::mutex _lock; using Locker = std::lock_guard; bool _trackingEntities { false }; - EntityTreePointer _entityTree; + QSharedPointer _entityTreeRenderer; using EntityMap = std::map; EntityMap _trackedEntities; diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index cb8b211352..6df4729ee0 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -400,10 +400,19 @@ void Audio::setReverbOptions(const AudioEffectOptions* options) { } void Audio::setAvatarGain(float gain) { + bool changed = false; + if (getAvatarGain() != gain) { + changed = true; + } + withWriteLock([&] { // ask the NodeList to set the master avatar gain DependencyManager::get()->setAvatarGain(QUuid(), gain); }); + + if (changed) { + emit avatarGainChanged(gain); + } } float Audio::getAvatarGain() { @@ -413,10 +422,19 @@ float Audio::getAvatarGain() { } void Audio::setInjectorGain(float gain) { + bool changed = false; + if (getInjectorGain() != gain) { + changed = true; + } + withWriteLock([&] { // ask the NodeList to set the audio injector gain DependencyManager::get()->setInjectorGain(gain); }); + + if (changed) { + emit serverInjectorGainChanged(gain); + } } float Audio::getInjectorGain() { @@ -426,6 +444,11 @@ float Audio::getInjectorGain() { } void Audio::setLocalInjectorGain(float gain) { + bool changed = false; + if (getLocalInjectorGain() != gain) { + changed = true; + } + withWriteLock([&] { if (_localInjectorGain != gain) { _localInjectorGain = gain; @@ -436,6 +459,11 @@ void Audio::setLocalInjectorGain(float gain) { DependencyManager::get()->setLocalInjectorGain(gain); } }); + + + if (changed) { + emit localInjectorGainChanged(gain); + } } float Audio::getLocalInjectorGain() { @@ -445,6 +473,11 @@ float Audio::getLocalInjectorGain() { } void Audio::setSystemInjectorGain(float gain) { + bool changed = false; + if (getSystemInjectorGain() != gain) { + changed = true; + } + withWriteLock([&] { if (_systemInjectorGain != gain) { _systemInjectorGain = gain; @@ -455,6 +488,10 @@ void Audio::setSystemInjectorGain(float gain) { DependencyManager::get()->setSystemInjectorGain(gain); } }); + + if (changed) { + emit systemInjectorGainChanged(gain); + } } float Audio::getSystemInjectorGain() { diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index ed54dca5c6..c7ac98402c 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -66,6 +66,10 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @property {boolean} pushToTalkHMD - true if HMD push-to-talk is enabled, otherwise false. * @property {boolean} pushingToTalk - true if the user is currently pushing-to-talk, otherwise * false. + * @property {float} avatarGain - The gain (relative volume) that avatars' voices are played at. This gain is used at the server. + * @property {float} localInjectorGain - The gain (relative volume) that local injectors (local environment sounds) are played at. + * @property {float} serverInjectorGain - The gain (relative volume) that server injectors (server environment sounds) are played at. This gain is used at the server. + * @property {float} systemInjectorGain - The gain (relative volume) that system sounds are played at. * * @comment The following properties are from AudioScriptingInterface.h. * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise @@ -90,6 +94,10 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_PROPERTY(bool pushToTalkDesktop READ getPTTDesktop WRITE setPTTDesktop NOTIFY pushToTalkDesktopChanged) Q_PROPERTY(bool pushToTalkHMD READ getPTTHMD WRITE setPTTHMD NOTIFY pushToTalkHMDChanged) Q_PROPERTY(bool pushingToTalk READ getPushingToTalk WRITE setPushingToTalk NOTIFY pushingToTalkChanged) + Q_PROPERTY(float avatarGain READ getAvatarGain WRITE setAvatarGain NOTIFY avatarGainChanged) + Q_PROPERTY(float localInjectorGain READ getLocalInjectorGain WRITE setLocalInjectorGain NOTIFY localInjectorGainChanged) + Q_PROPERTY(float serverInjectorGain READ getInjectorGain WRITE setInjectorGain NOTIFY serverInjectorGainChanged) + Q_PROPERTY(float systemInjectorGain READ getSystemInjectorGain WRITE setSystemInjectorGain NOTIFY systemInjectorGainChanged) public: static QString AUDIO; @@ -412,6 +420,38 @@ signals: */ void pushingToTalkChanged(bool talking); + /**jsdoc + * Triggered when the avatar gain changes. + * @function Audio.avatarGainChanged + * @param {float} gain - The new avatar gain value. + * @returns {Signal} + */ + void avatarGainChanged(float gain); + + /**jsdoc + * Triggered when the local injector gain changes. + * @function Audio.localInjectorGainChanged + * @param {float} gain - The new local injector gain value. + * @returns {Signal} + */ + void localInjectorGainChanged(float gain); + + /**jsdoc + * Triggered when the server injector gain changes. + * @function Audio.serverInjectorGainChanged + * @param {float} gain - The new server injector gain value. + * @returns {Signal} + */ + void serverInjectorGainChanged(float gain); + + /**jsdoc + * Triggered when the system injector gain changes. + * @function Audio.systemInjectorGainChanged + * @param {float} gain - The new system injector gain value. + * @returns {Signal} + */ + void systemInjectorGainChanged(float gain); + public slots: /**jsdoc diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index 8581c7527d..360a75b557 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -7,6 +7,9 @@ // #include "RenderScriptingInterface.h" +#include "LightingModel.h" +#include "AntialiasingEffect.h" + const QString DEFERRED = "deferred"; const QString FORWARD = "forward"; @@ -17,6 +20,9 @@ RenderScriptingInterface* RenderScriptingInterface::getInstance() { RenderScriptingInterface::RenderScriptingInterface() { setRenderMethod((render::Args::RenderMethod)_renderMethodSetting.get() == render::Args::RenderMethod::DEFERRED ? DEFERRED : FORWARD); + setShadowsEnabled(_shadowsEnabledSetting.get()); + setAmbientOcclusionEnabled(_ambientOcclusionEnabledSetting.get()); + setAntialiasingEnabled(_antialiasingEnabledSetting.get()); } QString RenderScriptingInterface::getRenderMethod() { @@ -24,6 +30,11 @@ QString RenderScriptingInterface::getRenderMethod() { } void RenderScriptingInterface::setRenderMethod(const QString& renderMethod) { + render::Args::RenderMethod newMethod = renderMethod == FORWARD ? render::Args::RenderMethod::FORWARD : render::Args::RenderMethod::DEFERRED; + if (_renderMethodSetting.get() == newMethod) { + return; + } + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setRenderMethod", Q_ARG(const QString&, renderMethod)); return; @@ -31,14 +42,81 @@ void RenderScriptingInterface::setRenderMethod(const QString& renderMethod) { auto config = dynamic_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.DeferredForwardSwitch")); if (config) { - if (renderMethod == DEFERRED) { - _renderMethodSetting.set(render::Args::RenderMethod::DEFERRED); - config->setBranch(render::Args::RenderMethod::DEFERRED); - emit config->dirtyEnabled(); - } else if (renderMethod == FORWARD) { - _renderMethodSetting.set(render::Args::RenderMethod::FORWARD); - config->setBranch(render::Args::RenderMethod::FORWARD); - emit config->dirtyEnabled(); + _renderMethodSetting.set(newMethod); + config->setBranch(newMethod); + emit config->dirtyEnabled(); + } +} + +bool RenderScriptingInterface::getShadowsEnabled() { + return _shadowsEnabledSetting.get(); +} + +void RenderScriptingInterface::setShadowsEnabled(bool enabled) { + if (_shadowsEnabledSetting.get() == enabled) { + return; + } + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setShadowsEnabled", Q_ARG(bool, enabled)); + return; + } + + auto lightingModelConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.LightingModel"); + if (lightingModelConfig) { + Menu::getInstance()->setIsOptionChecked(MenuOption::Shadows, enabled); + _shadowsEnabledSetting.set(enabled); + lightingModelConfig->setShadow(enabled); + } +} + +bool RenderScriptingInterface::getAmbientOcclusionEnabled() { + return _ambientOcclusionEnabledSetting.get(); +} + +void RenderScriptingInterface::setAmbientOcclusionEnabled(bool enabled) { + if (_ambientOcclusionEnabledSetting.get() == enabled) { + return; + } + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAmbientOcclusionEnabled", Q_ARG(bool, enabled)); + return; + } + + auto lightingModelConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.LightingModel"); + if (lightingModelConfig) { + Menu::getInstance()->setIsOptionChecked(MenuOption::AmbientOcclusion, enabled); + _ambientOcclusionEnabledSetting.set(enabled); + lightingModelConfig->setAmbientOcclusion(enabled); + } +} + +bool RenderScriptingInterface::getAntialiasingEnabled() { + return _antialiasingEnabledSetting.get(); +} + +void RenderScriptingInterface::setAntialiasingEnabled(bool enabled) { + if (_antialiasingEnabledSetting.get() == enabled) { + return; + } + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setAntialiasingEnabled", Q_ARG(bool, enabled)); + return; + } + + auto mainViewJitterCamConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.JitterCam"); + auto mainViewAntialiasingConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.Antialiasing"); + if (mainViewJitterCamConfig && mainViewAntialiasingConfig) { + Menu::getInstance()->setIsOptionChecked(MenuOption::AntiAliasing, enabled); + _antialiasingEnabledSetting.set(enabled); + if (enabled) { + mainViewJitterCamConfig->play(); + mainViewAntialiasingConfig->setDebugFXAA(false); + } else { + mainViewJitterCamConfig->none(); + mainViewAntialiasingConfig->setDebugFXAA(true); } } } \ No newline at end of file diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index 2aff3a08b6..329433fb60 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -26,6 +26,9 @@ class RenderScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(QString renderMethod READ getRenderMethod WRITE setRenderMethod) + Q_PROPERTY(bool shadowsEnabled READ getShadowsEnabled WRITE setShadowsEnabled) + Q_PROPERTY(bool ambientOcclusionEnabled READ getAmbientOcclusionEnabled WRITE setAmbientOcclusionEnabled) + Q_PROPERTY(bool antialiasingEnabled READ getAntialiasingEnabled WRITE setAntialiasingEnabled) public: RenderScriptingInterface(); @@ -37,8 +40,8 @@ public slots: * Get a config for a job by name * @function Render.getConfig * @param {string} name - Can be: - * - . Search for the first job named job_name traversing the the sub graph of task and jobs (from this task as root) - * - .[.]. Allows you to first look for the parent_name job (from this task as root) and then search from there for the + * - : Search for the first job named job_name traversing the the sub graph of task and jobs (from this task as root) + * - .[.]: Allows you to first look for the parent_name job (from this task as root) and then search from there for the * optional sub_parent_names and finally from there looking for the job_name (assuming every job in the path is found) * @returns {object} The sub job config. */ @@ -58,8 +61,53 @@ public slots: */ void setRenderMethod(const QString& renderMethod); + /**jsdoc + * Whether or not shadows are enabled + * @function Render.getShadowsEnabled + * @returns {bool} true if shadows are enabled, otherwise false + */ + bool getShadowsEnabled(); + + /**jsdoc + * Enables or disables shadows + * @function Render.setShadowsEnabled + * @param {bool} enabled - true to enable shadows, false to disable them + */ + void setShadowsEnabled(bool enabled); + + /**jsdoc + * Whether or not ambient occlusion is enabled + * @function Render.getAmbientOcclusionEnabled + * @returns {bool} true if ambient occlusion is enabled, otherwise false + */ + bool getAmbientOcclusionEnabled(); + + /**jsdoc + * Enables or disables ambient occlusion + * @function Render.setAmbientOcclusionEnabled + * @param {bool} enabled - true to enable ambient occlusion, false to disable it + */ + void setAmbientOcclusionEnabled(bool enabled); + + /**jsdoc + * Whether or not anti-aliasing is enabled + * @function Render.getAntialiasingEnabled + * @returns {bool} true if anti-aliasing is enabled, otherwise false + */ + bool getAntialiasingEnabled(); + + /**jsdoc + * Enables or disables anti-aliasing + * @function Render.setAntialiasingEnabled + * @param {bool} enabled - true to enable anti-aliasing, false to disable it + */ + void setAntialiasingEnabled(bool enabled); + private: Setting::Handle _renderMethodSetting { "renderMethod", RENDER_FORWARD ? render::Args::RenderMethod::FORWARD : render::Args::RenderMethod::DEFERRED }; + Setting::Handle _shadowsEnabledSetting { "shadowsEnabled", true }; + Setting::Handle _ambientOcclusionEnabledSetting { "ambientOcclusionEnabled", false }; + Setting::Handle _antialiasingEnabledSetting { "antialiasingEnabled", true }; }; #endif // hifi_RenderScriptingInterface_h diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index c15b5cde11..5856188868 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -44,13 +44,14 @@ SelectionScriptingInterface::SelectionScriptingInterface() { } /**jsdoc + * The type of a specific item in a selection list. * * * * * - * - * + * + * * *
ValueDescription
"avatar"
"entity"
"avatar"The item is an avatar.
"entity"The item is an entity.
* @typedef {string} Selection.ItemType @@ -245,9 +246,10 @@ void SelectionScriptingInterface::printList(const QString& listName) { } /**jsdoc + * A selection list. * @typedef {object} Selection.SelectedItemsList - * @property {Uuid[]} avatars - The IDs of the avatars in the selection. - * @property {Uuid[]} entities - The IDs of the entities in the selection. + * @property {Uuid[]} avatars - The IDs of the avatars in the selection list. + * @property {Uuid[]} entities - The IDs of the entities in the selection list. */ QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& listName) const { QReadLocker lock(&_selectionListsLock); @@ -438,18 +440,19 @@ bool SelectionHighlightStyle::fromVariantMap(const QVariantMap& properties) { } /**jsdoc + * The highlighting style of a selection list. * @typedef {object} Selection.HighlightStyle - * @property {Color} outlineUnoccludedColor - Color of the specified highlight region. - * @property {Color} outlineOccludedColor - "" - * @property {Color} fillUnoccludedColor- "" - * @property {Color} fillOccludedColor- "" - * @property {number} outlineUnoccludedAlpha - Alpha value ranging from 0.0 (not visible) to 1.0 - * (fully opaque) for the specified highlight region. - * @property {number} outlineOccludedAlpha - "" - * @property {number} fillUnoccludedAlpha - "" - * @property {number} fillOccludedAlpha - "" - * @property {number} outlineWidth - Width of the outline, in pixels. - * @property {boolean} isOutlineSmooth - true to enable outline smooth fall-off. + * @property {Color} outlineUnoccludedColor=255,178,51 - Unoccluded outline color. + * @property {Color} outlineOccludedColor=255,178,51 - Occluded outline color. + * @property {Color} fillUnoccludedColor=51,178,255 - Unoccluded fill color. + * @property {Color} fillOccludedColor=51,178,255 - Occluded fill color. + * @property {number} outlineUnoccludedAlpha=0.9 - Unoccluded outline alpha, range 0.01.0. + * @property {number} outlineOccludedAlpha=0.9 - Occluded outline alpha, range 0.01.0. + * @property {number} fillUnoccludedAlpha=0.0 - Unoccluded fill alpha, range 0.01.0. + * @property {number} fillOccludedAlpha=0.0 - Occluded fill alpha, range 0.01.0. + * @property {number} outlineWidth=2 - Width of the outline, in pixels. + * @property {boolean} isOutlineSmooth=false - true to fade the outside edge of the outline, false + * to have a sharp edge. */ QVariantMap SelectionHighlightStyle::toVariantMap() const { QVariantMap properties; diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index fcb4090184..4386ee5ee6 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -77,47 +77,45 @@ protected: }; /**jsdoc - * The Selection API provides a means of grouping together avatars and entities in named lists. + * The Selection API provides a means of grouping together and highlighting avatars and entities in named lists. + * * @namespace Selection * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @example Outline an entity when it is grabbed by a controller. - * // Create a box and copy the following text into the entity's "Script URL" field. + * @example Outline an entity when it is grabbed by the mouse or a controller. + * // Create an entity and copy the following script into the entity's "Script URL" field. + * // Move the entity behind another entity to see the occluded outline. * (function () { - * print("Starting highlight script..............."); - * var _this = this; - * var prevID = 0; - * var listName = "contextOverlayHighlightList"; - * var listType = "entity"; - * - * _this.startNearGrab = function(entityID){ - * if (prevID !== entityID) { - * Selection.addToSelectedItemsList(listName, listType, entityID); - * prevID = entityID; - * } + * var LIST_NAME = "SelectionExample", + * ITEM_TYPE = "entity", + * HIGHLIGHT_STYLE = { + * outlineUnoccludedColor: { red: 0, green: 180, blue: 239 }, + * outlineUnoccludedAlpha: 0.5, + * outlineOccludedColor: { red: 239, green: 180, blue: 0 }, + * outlineOccludedAlpha: 0.5, + * outlineWidth: 4 + * }; + * + * Selection.enableListHighlight(LIST_NAME, HIGHLIGHT_STYLE); + * + * this.startNearGrab = function (entityID) { + * Selection.addToSelectedItemsList(LIST_NAME, ITEM_TYPE, entityID); * }; - * - * _this.releaseGrab = function(entityID){ - * if (prevID !== 0) { - * Selection.removeFromSelectedItemsList("contextOverlayHighlightList", listType, prevID); - * prevID = 0; - * } + * + * this.startDistanceGrab = function (entityID) { + * Selection.addToSelectedItemsList(LIST_NAME, ITEM_TYPE, entityID); * }; - * - * var cleanup = function(){ - * Entities.findEntities(MyAvatar.position, 1000).forEach(function(entity) { - * try { - * Selection.removeListFromMap(listName); - * } catch (e) { - * print("Error cleaning up."); - * } - * }); + * + * this.releaseGrab = function (entityID) { + * Selection.removeFromSelectedItemsList(LIST_NAME, ITEM_TYPE, entityID); * }; - * - * Script.scriptEnding.connect(cleanup); + * + * Script.scriptEnding.connect(function () { + * Selection.removeListFromMap(LIST_NAME); + * }); * }); */ class SelectionScriptingInterface : public QObject, public Dependency { @@ -127,121 +125,119 @@ public: SelectionScriptingInterface(); /**jsdoc - * Get the names of all the selection lists. - * @function Selection.getListNames - * @returns {list[]} An array of names of all the selection lists. - */ + * Gets the names of all current selection lists. + * @function Selection.getListNames + * @returns {string[]} The names of all current selection lists. + * @example List all the current selection lists. + * print("Selection lists: " + Selection.getListNames()); + */ Q_INVOKABLE QStringList getListNames() const; /**jsdoc - * Delete a named selection list. - * @function Selection.removeListFromMap - * @param {string} listName - The name of the selection list. - * @returns {boolean} true if the selection existed and was successfully removed, otherwise false. - */ + * Deletes a selection list. + * @function Selection.removeListFromMap + * @param {string} listName - The name of the selection list to delete. + * @returns {boolean} true if the selection existed and was successfully removed, otherwise false. + */ Q_INVOKABLE bool removeListFromMap(const QString& listName); /**jsdoc - * Add an item to a selection list. - * @function Selection.addToSelectedItemsList - * @param {string} listName - The name of the selection list to add the item to. - * @param {Selection.ItemType} itemType - The type of the item being added. - * @param {Uuid} id - The ID of the item to add to the selection. - * @returns {boolean} true if the item was successfully added, otherwise false. - */ + * Adds an item to a selection list. The list is created if it doesn't exist. + * @function Selection.addToSelectedItemsList + * @param {string} listName - The name of the selection list to add the item to. + * @param {Selection.ItemType} itemType - The type of item being added. + * @param {Uuid} itemID - The ID of the item to add. + * @returns {boolean} true if the item was successfully added or already existed in the list, otherwise + * false. + */ Q_INVOKABLE bool addToSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); + /**jsdoc - * Remove an item from a selection list. - * @function Selection.removeFromSelectedItemsList - * @param {string} listName - The name of the selection list to remove the item from. - * @param {Selection.ItemType} itemType - The type of the item being removed. - * @param {Uuid} id - The ID of the item to remove. - * @returns {boolean} true if the item was successfully removed, otherwise false. - * is returned if the list doesn't contain any data. - */ + * Removes an item from a selection list. + * @function Selection.removeFromSelectedItemsList + * @param {string} listName - The name of the selection list to remove the item from. + * @param {Selection.ItemType} itemType - The type of item being removed. + * @param {Uuid} itemID - The ID of the item to remove. + * @returns {boolean} true if the item was successfully removed or was not in the list, otherwise + * false. + */ Q_INVOKABLE bool removeFromSelectedItemsList(const QString& listName, const QString& itemType, const QUuid& id); + /**jsdoc - * Remove all items from a selection. - * @function Selection.clearSelectedItemsList - * @param {string} listName - The name of the selection list. - * @returns {boolean} true if the item was successfully cleared, otherwise false. - */ + * Removes all items from a selection list. + * @function Selection.clearSelectedItemsList + * @param {string} listName - The name of the selection list. + * @returns {boolean} true always. + */ Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); /**jsdoc - * Print out the list of avatars and entities in a selection to the debug log (not the script log). - * @function Selection.printList - * @param {string} listName - The name of the selection list. - */ + * Prints the list of avatars and entities in a selection to the program log (but not the Script Log window). + * @function Selection.printList + * @param {string} listName - The name of the selection list. + */ Q_INVOKABLE void printList(const QString& listName); /**jsdoc - * Get the list of avatars and entities stored in a selection list. - * @function Selection.getSelectedItemsList - * @param {string} listName - The name of the selection list. - * @returns {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function - * returns an empty object with no properties. - */ + * Gets the list of avatars and entities in a selection list. + * @function Selection.getSelectedItemsList + * @param {string} listName - The name of the selection list. + * @returns {Selection.SelectedItemsList} The content of the selection list if the list exists, otherwise an empty object. + */ Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const; /**jsdoc - * Get the names of the highlighted selection lists. - * @function Selection.getHighlightedListNames - * @returns {string[]} An array of names of the selection list currently highlight enabled. - */ + * Gets the names of all current selection lists that have highlighting enabled. + * @function Selection.getHighlightedListNames + * @returns {string[]} The names of the selection lists that currently have highlighting enabled. + */ Q_INVOKABLE QStringList getHighlightedListNames() const; /**jsdoc - * Enable highlighting for a selection list. - * If the selection list doesn't exist, it will be created. - * All objects in the list will be displayed with the highlight effect specified. - * The function can be called several times with different values in the style to modify it.
- * Note: This function implicitly calls {@link Selection.enableListToScene}. - * @function Selection.enableListHighlight - * @param {string} listName - The name of the selection list. - * @param {Selection.HighlightStyle} highlightStyle - The highlight style. - * @returns {boolean} true if the selection was successfully enabled for highlight. - */ + * Enables highlighting for a selection list. All items in or subsequently added to the list are displayed with the + * highlight effect specified. The method can be called multiple times with different values in the style to modify the + * highlighting. + *

Note: This function implicitly calls {@link Selection.enableListToScene|enableListToScene}.

+ * @function Selection.enableListHighlight + * @param {string} listName - The name of the selection list. + * @param {Selection.HighlightStyle} highlightStyle - The highlight style. + * @returns {boolean} true always. + */ Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); /**jsdoc - * Disable highlighting for the selection list. - * If the selection list doesn't exist or wasn't enabled for highlighting then nothing happens and false is - * returned.
- * Note: This function implicitly calls {@link Selection.disableListToScene}. - * @function Selection.disableListHighlight - * @param {string} listName - The name of the selection list. - * @returns {boolean} true if the selection was successfully disabled for highlight, otherwise - * false. - */ + * Disables highlighting for a selection list. + *

Note: This function implicitly calls {@link Selection.disableListToScene|disableListToScene}.

+ * @function Selection.disableListHighlight + * @param {string} listName - The name of the selection list. + * @returns {boolean} true always. + */ Q_INVOKABLE bool disableListHighlight(const QString& listName); + /**jsdoc - * Enable scene selection for the selection list. - * If the Selection doesn't exist, it will be created. - * All objects in the list will be sent to a scene selection. - * @function Selection.enableListToScene - * @param {string} listName - The name of the selection list. - * @returns {boolean} true if the selection was successfully enabled on the scene, otherwise false. - */ + * Enables scene selection for a selection list. All items in or subsequently added to the list are sent to a scene + * selection in the rendering engine for debugging purposes. + * @function Selection.enableListToScene + * @param {string} listName - The name of the selection list. + * @returns {boolean} true always. + */ Q_INVOKABLE bool enableListToScene(const QString& listName); /**jsdoc - * Disable scene selection for the named selection. - * If the selection list doesn't exist or wasn't enabled on the scene then nothing happens and false is - * returned. - * @function Selection.disableListToScene - * @param {string} listName - The name of the selection list. - * @returns {boolean} true if the selection was successfully disabled on the scene, false otherwise. - */ + * Disables scene selection for a selection list. + * @function Selection.disableListToScene + * @param {string} listName - The name of the selection list. + * @returns {boolean} true always. + */ Q_INVOKABLE bool disableListToScene(const QString& listName); /**jsdoc - * Get the highlight style values for the a selection list. - * If the selection doesn't exist or hasn't been highlight enabled yet, an empty object is returned. - * @function Selection.getListHighlightStyle - * @param {string} listName - The name of the selection list. - * @returns {Selection.HighlightStyle} highlight style - */ + * Gets the current highlighting style for a selection list. + * @function Selection.getListHighlightStyle + * @param {string} listName - The name of the selection list. + * @returns {Selection.HighlightStyle} The highlight style of the selection list if the list exists and highlighting is + * enabled, otherwise an empty object. + */ Q_INVOKABLE QVariantMap getListHighlightStyle(const QString& listName) const; @@ -253,7 +249,7 @@ public: signals: /**jsdoc - * Triggered when a list's content changes. + * Triggered when a selection list's content changes or the list is deleted. * @function Selection.selectedItemsListChanged * @param {string} listName - The name of the selection list that changed. * @returns {Signal} @@ -276,7 +272,6 @@ private: void setupHandler(const QString& selectionName); void removeHandler(const QString& selectionName); - }; #endif // hifi_SelectionScriptingInterface_h diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index e907e550f3..25a8b627cb 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -16,7 +16,8 @@ #include /**jsdoc - * The Settings API provides a facility to store and retrieve values that persist between Interface runs. + * The Settings API provides a facility to store and retrieve values that persist between Interface runs. + * * @namespace Settings * * @hifi-interface @@ -33,7 +34,7 @@ public: public slots: /**jsdoc - * Retrieve the value from a named setting. + * Retrieves the value from a named setting. * @function Settings.getValue * @param {string} key - The name of the setting. * @param {string|number|boolean|object} [defaultValue=""] - The value to return if the setting doesn't exist. @@ -50,8 +51,8 @@ public slots: QVariant getValue(const QString& setting, const QVariant& defaultValue); /**jsdoc - * Store a value in a named setting. If the setting already exists its value is overwritten, otherwise a new setting is - * created. If the value is set to null or undefined, the setting is deleted. + * Stores a value in a named setting. If the setting already exists, its value is overwritten. If the value is + * null or undefined, the setting is deleted. * @function Settings.setValue * @param {string} key - The name of the setting. Be sure to use a unique name if creating a new setting. * @param {string|number|boolean|object|undefined} value - The value to store in the setting. If null or diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index de2441ae62..0c2b494b0b 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -313,8 +313,9 @@ public slots: * Takes a snapshot of the current Interface view from the primary camera. When a still image only is captured, * {@link Window.stillSnapshotTaken|stillSnapshotTaken} is emitted; when a still image plus moving images are captured, * {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted} - * are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings > - * General > Snapshots. + * are emitted. + *

Snapshots are saved to the path specified in Settings > General > Snapshots, which can be accessed via the + * {@link Snapshot} API.

* * @function Window.takeSnapshot * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} @@ -351,13 +352,15 @@ public slots: * var notify = true; * var animated = true; * var aspect = 1920 / 1080; - * var filename = ""; + * var filename = "example-snapshot"; * Window.takeSnapshot(notify, animated, aspect, filename); */ void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f, const QString& filename = QString()); /**jsdoc * Takes a still snapshot of the current view from the secondary camera that can be set up through the {@link Render} API. + *

Snapshots are saved to the path specified in Settings > General > Snapshots, which can be accessed via the + * {@link Snapshot} API.

* @function Window.takeSecondaryCameraSnapshot * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} * signal. @@ -372,6 +375,8 @@ public slots: /**jsdoc * Takes a 360° snapshot at a given position for the secondary camera. The secondary camera does not need to have been * set up. + *

Snapshots are saved to the path specified in Settings > General > Snapshots, which can be accessed via the + * {@link Snapshot} API.

* @function Window.takeSecondaryCamera360Snapshot * @param {Vec3} cameraPosition - The position of the camera for the snapshot. * @param {boolean} [cubemapOutputFormat=false] - If true then the snapshot is saved as a cube map image, diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index f13f4cd587..73c5a8e3c5 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -38,6 +38,10 @@ private: /**jsdoc + * The Snapshot API provides access to the path that snapshots are saved to. This path is that provided in + * Settings > General > Snapshots. Snapshots may be taken using Window API functions such as + * {@link Window.takeSnapshot}. + * * @namespace Snapshot * * @hifi-interface @@ -64,23 +68,31 @@ public: signals: /**jsdoc + * Triggered when the path that snapshots are saved to is changed. * @function Snapshot.snapshotLocationSet - * @param {string} location + * @param {string} location - The new snapshots location. * @returns {Signal} + * @example Report when the snapshots location is changed. + * // Run this script then change the snapshots location in Settings > General > Snapshots. + * Snapshot.snapshotLocationSet.connect(function (path) { + * print("New snapshot location: " + path); + * }); */ void snapshotLocationSet(const QString& value); public slots: /**jsdoc + * Gets the path that snapshots are saved to. * @function Snapshot.getSnapshotsLocation - * @returns {string} + * @returns {string} The path to save snapshots to. */ Q_INVOKABLE QString getSnapshotsLocation(); /**jsdoc + * Sets the path that snapshots are saved to. * @function Snapshot.setSnapshotsLocation - * @param {String} location + * @param {String} location - The path to save snapshots to. */ Q_INVOKABLE void setSnapshotsLocation(const QString& location); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 1939e7fa0e..cb13945320 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -314,10 +314,11 @@ class Stats : public QQuickItem { STATS_PROPERTY(QVector3D, parabolaPicksUpdated, QVector3D(0, 0, 0)) STATS_PROPERTY(QVector3D, collisionPicksUpdated, QVector3D(0, 0, 0)) -#ifdef DEBUG_EVENT_QUEUE - STATS_PROPERTY(bool, eventQueueDebuggingOn, true) STATS_PROPERTY(int, mainThreadQueueDepth, -1); STATS_PROPERTY(int, nodeListThreadQueueDepth, -1); + +#ifdef DEBUG_EVENT_QUEUE + STATS_PROPERTY(bool, eventQueueDebuggingOn, true) #else STATS_PROPERTY(bool, eventQueueDebuggingOn, false) #endif // DEBUG_EVENT_QUEUE diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index a4767e1f34..c321065be2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -339,7 +339,7 @@ public: */ Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; - virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; + Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setAttachmentData(const QVector& attachmentData) override; void updateDisplayNameAlpha(bool showDisplayName); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index d29e05c7bc..8acaebf882 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1209,7 +1209,7 @@ public: const QString& getDisplayName() const { return _displayName; } const QString& getSessionDisplayName() const { return _sessionDisplayName; } bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; } - virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); virtual void setSessionDisplayName(const QString& sessionDisplayName) { diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index c1253f825f..de6141c9d8 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -15,7 +15,7 @@ // These properties have JSDoc documentation in HMDScriptingInterface.h. class AbstractHMDScriptingInterface : public QObject { Q_OBJECT - Q_PROPERTY(bool active READ isHMDMode NOTIFY mountedChanged) + Q_PROPERTY(bool active READ isHMDMode NOTIFY displayModeChanged) Q_PROPERTY(float ipd READ getIPD) Q_PROPERTY(float eyeHeight READ getEyeHeight) Q_PROPERTY(float playerHeight READ getPlayerHeight) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 13c93fb2fa..8bda55f2a2 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -363,7 +363,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { ShapeType type = getShapeType(); auto model = getModel(); - if (!model) { + if (!model || !model->isLoaded()) { type = SHAPE_TYPE_NONE; } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index c072dedaf9..39700bfc31 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1770,6 +1770,7 @@ signals: /**jsdoc * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered * for a collision with an avatar. + *

See also, {@link Script.addEventHandler}.

* @function Entities.collisionWithEntity * @param {Uuid} idA - The ID of one entity in the collision. For an entity script, this is the ID of the entity containing * the script. @@ -1882,6 +1883,7 @@ signals: /**jsdoc * Triggered when a mouse button is clicked while the mouse cursor is on an entity, or a controller trigger is fully * pressed while its laser is on an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.mousePressOnEntity * @param {Uuid} entityID - The ID of the entity that was pressed. * @param {PointerEvent} event - Details of the event. @@ -1906,6 +1908,7 @@ signals: /**jsdoc * Repeatedly triggered while the mouse cursor or controller laser moves on an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.mouseMoveOnEntity * @param {Uuid} entityID - The ID of the entity that was moved on. * @param {PointerEvent} event - Details of the event. @@ -1916,6 +1919,7 @@ signals: /**jsdoc * Triggered when a mouse button is released after clicking on an entity or the controller trigger is partly or fully * released after pressing on an entity, even if the mouse pointer or controller laser has moved off the entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.mouseReleaseOnEntity * @param {Uuid} entityID - The ID of the entity that was originally pressed. * @param {PointerEvent} event - Details of the event. @@ -1942,6 +1946,7 @@ signals: /**jsdoc * Triggered when a mouse button is clicked while the mouse cursor is on an entity. Note: Not triggered by controller. + *

See also, {@link Script.addEventHandler}.

* @function Entities.clickDownOnEntity * @param {Uuid} entityID - The ID of the entity that was clicked. * @param {PointerEvent} event - Details of the event. @@ -1952,6 +1957,7 @@ signals: /**jsdoc * Repeatedly triggered while a mouse button continues to be held after clicking an entity, even if the mouse cursor has * moved off the entity. Note: Not triggered by controller. + *

See also, {@link Script.addEventHandler}.

* @function Entities.holdingClickOnEntity * @param {Uuid} entityID - The ID of the entity that was originally clicked. * @param {PointerEvent} event - Details of the event. @@ -1962,6 +1968,7 @@ signals: /**jsdoc * Triggered when a mouse button is released after clicking on an entity, even if the mouse cursor has moved off the * entity. Note: Not triggered by controller. + *

See also, {@link Script.addEventHandler}.

* @function Entities.clickReleaseOnEntity * @param {Uuid} entityID - The ID of the entity that was originally clicked. * @param {PointerEvent} event - Details of the event. @@ -1971,6 +1978,7 @@ signals: /**jsdoc * Triggered when the mouse cursor or controller laser starts hovering on an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.hoverEnterEntity * @param {Uuid} entityID - The ID of the entity that is being hovered. * @param {PointerEvent} event - Details of the event. @@ -1980,6 +1988,7 @@ signals: /**jsdoc * Repeatedly triggered when the mouse cursor or controller laser moves while hovering over an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.hoverOverEntity * @param {Uuid} entityID - The ID of the entity that is being hovered. * @param {PointerEvent} event - Details of the event. @@ -1989,6 +1998,7 @@ signals: /**jsdoc * Triggered when the mouse cursor or controller laser stops hovering over an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.hoverLeaveEntity * @param {Uuid} entityID - The ID of the entity that was being hovered. * @param {PointerEvent} event - Details of the event. @@ -1999,6 +2009,7 @@ signals: /**jsdoc * Triggered when an avatar enters an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.enterEntity * @param {Uuid} entityID - The ID of the entity that the avatar entered. * @returns {Signal} @@ -2032,6 +2043,7 @@ signals: /**jsdoc * Triggered when an avatar leaves an entity. + *

See also, {@link Script.addEventHandler}.

* @function Entities.leaveEntity * @param {Uuid} entityID - The ID of the entity that the avatar left. * @returns {Signal} diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 70269c6401..c896613df5 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -27,7 +27,7 @@ namespace baker { class GetModelPartsTask { public: using Input = hfm::Model::Pointer; - using Output = VaryingSet6, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, QHash, std::vector>; + using Output = VaryingSet5, hifi::URL, baker::MeshIndicesToModelNames, baker::BlendshapesPerMesh, std::vector>; using JobModel = Job::ModelIO; void run(const BakeContextPointer& context, const Input& input, Output& output) { @@ -40,8 +40,7 @@ namespace baker { for (int i = 0; i < hfmModelIn->meshes.size(); i++) { blendshapesPerMesh.push_back(hfmModelIn->meshes[i].blendshapes.toStdVector()); } - output.edit4() = hfmModelIn->materials; - output.edit5() = hfmModelIn->joints.toStdVector(); + output.edit4() = hfmModelIn->joints.toStdVector(); } }; @@ -134,17 +133,16 @@ namespace baker { const auto url = modelPartsIn.getN(1); const auto meshIndicesToModelNames = modelPartsIn.getN(2); const auto blendshapesPerMeshIn = modelPartsIn.getN(3); - const auto materials = modelPartsIn.getN(4); - const auto jointsIn = modelPartsIn.getN(5); + const auto jointsIn = modelPartsIn.getN(4); // Calculate normals and tangents for meshes and blendshapes if they do not exist // Note: Normals are never calculated here for OBJ models. OBJ files optionally define normals on a per-face basis, so for consistency normals are calculated beforehand in OBJSerializer. const auto normalsPerMesh = model.addJob("CalculateMeshNormals", meshesIn); - const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn, materials).asVarying(); + const auto calculateMeshTangentsInputs = CalculateMeshTangentsTask::Input(normalsPerMesh, meshesIn).asVarying(); const auto tangentsPerMesh = model.addJob("CalculateMeshTangents", calculateMeshTangentsInputs); const auto calculateBlendshapeNormalsInputs = CalculateBlendshapeNormalsTask::Input(blendshapesPerMeshIn, meshesIn).asVarying(); const auto normalsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeNormals", calculateBlendshapeNormalsInputs); - const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn, materials).asVarying(); + const auto calculateBlendshapeTangentsInputs = CalculateBlendshapeTangentsTask::Input(normalsPerBlendshapePerMesh, blendshapesPerMeshIn, meshesIn).asVarying(); const auto tangentsPerBlendshapePerMesh = model.addJob("CalculateBlendshapeTangents", calculateBlendshapeTangentsInputs); // Build the graphics::MeshPointer for each hfm::Mesh diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp index ba8fd94f09..905de9b5a7 100644 --- a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.cpp @@ -19,7 +19,6 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte const auto& normalsPerBlendshapePerMesh = input.get0(); const auto& blendshapesPerMesh = input.get1(); const auto& meshes = input.get2(); - const auto& materials = input.get3(); auto& tangentsPerBlendshapePerMeshOut = output; tangentsPerBlendshapePerMeshOut.reserve(normalsPerBlendshapePerMesh.size()); @@ -30,16 +29,6 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte tangentsPerBlendshapePerMeshOut.emplace_back(); auto& tangentsPerBlendshapeOut = tangentsPerBlendshapePerMeshOut[tangentsPerBlendshapePerMeshOut.size()-1]; - // Check if we actually need to calculate the tangents, or just append empty arrays - bool needTangents = false; - for (const auto& meshPart : mesh.parts) { - auto materialIt = materials.find(meshPart.materialID); - if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { - needTangents = true; - break; - } - } - for (size_t j = 0; j < blendshapes.size(); j++) { const auto& blendshape = blendshapes[j]; const auto& tangentsIn = blendshape.tangents; @@ -53,8 +42,8 @@ void CalculateBlendshapeTangentsTask::run(const baker::BakeContextPointer& conte continue; } - // Check if we can and should calculate tangents (we need normals to calculate the tangents) - if (normals.empty() || !needTangents) { + // Check if we can calculate tangents (we need normals and texcoords to calculate the tangents) + if (normals.empty() || normals.size() != (size_t)mesh.texCoords.size()) { continue; } tangentsOut.resize(normals.size()); diff --git a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h index c24b41d2d9..4ad8fee036 100644 --- a/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h +++ b/libraries/model-baker/src/model-baker/CalculateBlendshapeTangentsTask.h @@ -18,7 +18,7 @@ // Calculate blendshape tangents if not already present in the blendshape class CalculateBlendshapeTangentsTask { public: - using Input = baker::VaryingSet4, baker::BlendshapesPerMesh, std::vector, QHash>; + using Input = baker::VaryingSet3, baker::BlendshapesPerMesh, std::vector>; using Output = std::vector; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp index d2144a0e30..297d8cbde7 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.cpp @@ -13,21 +13,9 @@ #include "ModelMath.h" -bool needTangents(const hfm::Mesh& mesh, const QHash& materials) { - // Check if we actually need to calculate the tangents - for (const auto& meshPart : mesh.parts) { - auto materialIt = materials.find(meshPart.materialID); - if (materialIt != materials.end() && (*materialIt).needTangentSpace()) { - return true; - } - } - return false; -} - void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { const auto& normalsPerMesh = input.get0(); const std::vector& meshes = input.get1(); - const auto& materials = input.get2(); auto& tangentsPerMeshOut = output; tangentsPerMeshOut.reserve(meshes.size()); @@ -39,10 +27,10 @@ void CalculateMeshTangentsTask::run(const baker::BakeContextPointer& context, co auto& tangentsOut = tangentsPerMeshOut[tangentsPerMeshOut.size()-1]; // Check if we already have tangents and therefore do not need to do any calculation - // Otherwise confirm if we have the normals needed, and need to calculate the tangents + // Otherwise confirm if we have the normals and texcoords needed if (!tangentsIn.empty()) { tangentsOut = tangentsIn.toStdVector(); - } else if (!normals.empty() && needTangents(mesh, materials)) { + } else if (!normals.empty() && mesh.vertices.size() == mesh.texCoords.size()) { tangentsOut.resize(normals.size()); baker::calculateTangents(mesh, [&mesh, &normals, &tangentsOut](int firstIndex, int secondIndex, glm::vec3* outVertices, glm::vec2* outTexCoords, glm::vec3& outNormal) { diff --git a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h index b8fdb7d5f4..2ad5759476 100644 --- a/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h +++ b/libraries/model-baker/src/model-baker/CalculateMeshTangentsTask.h @@ -22,7 +22,7 @@ class CalculateMeshTangentsTask { public: using NormalsPerMesh = std::vector>; - using Input = baker::VaryingSet3, QHash>; + using Input = baker::VaryingSet2>; using Output = baker::TangentsPerMesh; using JobModel = baker::Job::ModelIO; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b3862e4252..31576ff8d9 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -91,10 +91,10 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) connect(accountManager.data(), &AccountManager::newKeypair, this, &NodeList::sendDomainServerCheckIn); // clear out NodeList when login is finished and we know our new username - connect(accountManager.data(), SIGNAL(usernameChanged(QString)) , this, SLOT(reset())); + connect(accountManager.data(), &AccountManager::usernameChanged , this, [this]{ reset("Username changed"); }); // clear our NodeList when logout is requested - connect(accountManager.data(), SIGNAL(logoutComplete()) , this, SLOT(reset())); + connect(accountManager.data(), &AccountManager::logoutComplete , this, [this]{ reset("Logged out"); }); // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); @@ -412,6 +412,8 @@ void NodeList::sendDomainServerCheckIn() { packetStream << FingerprintUtils::getMachineFingerprint(); } + packetStream << usecTimestampNow(); + // pack our data to send to the domain-server including // the hostname information (so the domain-server can see which place name we came in on) packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); @@ -618,32 +620,14 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { - if (_domainHandler.getSockAddr().isNull()) { - qWarning() << "IGNORING DomainList packet while not connected to a Domain Server"; - // refuse to process this packet if we aren't currently connected to the DS - return; - } - - // this is a packet from the domain server, reset the count of un-replied check-ins - _domainHandler.clearPendingCheckins(); - - // emit our signal so listeners know we just heard from the DS - emit receivedDomainServerList(); - - DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); + // parse header information QDataStream packetStream(message->getMessage()); // grab the domain's ID from the beginning of the packet QUuid domainUUID; packetStream >> domainUUID; - if (_domainHandler.isConnected() && _domainHandler.getUUID() != domainUUID) { - // Recieved packet from different domain. - qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" << _domainHandler.getUUID(); - return; - } - Node::LocalID domainLocalID; packetStream >> domainLocalID; @@ -654,6 +638,57 @@ void NodeList::processDomainServerList(QSharedPointer message) packetStream >> newUUID; packetStream >> newLocalID; + // pull the permissions/right/privileges for this node out of the stream + NodePermissions newPermissions; + packetStream >> newPermissions; + setPermissions(newPermissions); + // Is packet authentication enabled? + bool isAuthenticated; + packetStream >> isAuthenticated; + setAuthenticatePackets(isAuthenticated); + + quint64 connectRequestTimestamp; + quint64 now = usecTimestampNow(); + packetStream >> connectRequestTimestamp; + quint64 pingLagTime = (now - connectRequestTimestamp) / USECS_PER_MSEC; + quint64 domainServerPingReceiveTime; + + packetStream >> domainServerPingReceiveTime; + quint64 domainServerRequestLag = (domainServerPingReceiveTime - connectRequestTimestamp) / USECS_PER_MSEC; + quint64 domainServerResponseLag = (now - domainServerPingReceiveTime) / USECS_PER_MSEC; + + if (_domainHandler.getSockAddr().isNull()) { + qWarning(networking) << "IGNORING DomainList packet while not connected to a Domain Server: sent " << pingLagTime << " msec ago."; + qWarning(networking) << "DomainList request lag (with skew): " << domainServerRequestLag << "msec"; + qWarning(networking) << "DomainList response lag (with skew): " << domainServerResponseLag << "msec"; + // refuse to process this packet if we aren't currently connected to the DS + return; + } + + // warn if ping lag is getting long + if (pingLagTime > MSECS_PER_SECOND) { + qCDebug(networking) << "DomainList ping is lagging: " << pingLagTime << "msec"; + qCDebug(networking) << "DomainList request lag (with skew): " << domainServerRequestLag << "msec"; + qCDebug(networking) << "DomainList response lag (with skew): " << domainServerResponseLag << "msec"; + } + + // this is a packet from the domain server, reset the count of un-replied check-ins + _domainHandler.clearPendingCheckins(); + + // emit our signal so listeners know we just heard from the DS + emit receivedDomainServerList(); + + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); + + if (_domainHandler.isConnected() && _domainHandler.getUUID() != domainUUID) { + // Recieved packet from different domain. + qWarning() << "IGNORING DomainList packet from" << domainUUID << "while connected to" + << _domainHandler.getUUID() << ": sent " << pingLagTime << " msec ago."; + qWarning(networking) << "DomainList request lag (with skew): " << domainServerRequestLag << "msec"; + qWarning(networking) << "DomainList response lag (with skew): " << domainServerResponseLag << "msec"; + return; + } + // when connected, if the session ID or local ID were not null and changed, we should reset auto currentLocalID = getSessionLocalID(); auto currentSessionID = getSessionUUID(); @@ -684,15 +719,6 @@ void NodeList::processDomainServerList(QSharedPointer message) DependencyManager::get()->lookupShareableNameForDomainID(domainUUID); } - // pull the permissions/right/privileges for this node out of the stream - NodePermissions newPermissions; - packetStream >> newPermissions; - setPermissions(newPermissions); - // Is packet authentication enabled? - bool isAuthenticated; - packetStream >> isAuthenticated; - setAuthenticatePackets(isAuthenticated); - // pull each node in the packet while (packetStream.device()->pos() < message->getSize()) { parseNodeFromPacketStream(packetStream); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index f8574b3b94..566e1e4946 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -27,7 +27,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::StunResponse: return 17; case PacketType::DomainList: - return static_cast(DomainListVersion::AuthenticationOptional); + return static_cast(DomainListVersion::HasTimestamp); case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: @@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: - return static_cast(DomainConnectRequestVersion::AlwaysHasMachineFingerprint); + return static_cast(DomainConnectRequestVersion::HasTimestamp); case PacketType::DomainServerAddedNode: return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5deadd8c43..903c1f4c93 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -344,7 +344,8 @@ enum class DomainConnectRequestVersion : PacketVersion { HasProtocolVersions, HasMACAddress, HasMachineFingerprint, - AlwaysHasMachineFingerprint + AlwaysHasMachineFingerprint, + HasTimestamp }; enum class DomainConnectionDeniedVersion : PacketVersion { @@ -363,7 +364,8 @@ enum class DomainListVersion : PacketVersion { PermissionsGrid, GetUsernameFromUUIDSupport, GetMachineFingerprintFromUUIDSupport, - AuthenticationOptional + AuthenticationOptional, + HasTimestamp }; enum class AudioVersion : PacketVersion { diff --git a/libraries/platform/src/LinuxPlatform.cpp b/libraries/platform/src/LinuxPlatform.cpp index aa63eb1e08..e376a45165 100644 --- a/libraries/platform/src/LinuxPlatform.cpp +++ b/libraries/platform/src/LinuxPlatform.cpp @@ -9,15 +9,51 @@ #include "LinuxPlatform.h" #include "platformJsonKeys.h" #include - +#include using namespace platform; + +static void getLCpuId( uint32_t* p, uint32_t ax ) +{ + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) + __asm __volatile + ( "movl %%ebx, %%esi\n\t" + "cpuid\n\t" + "xchgl %%ebx, %%esi" + : "=a" (p[0]), "=S" (p[1]), + "=c" (p[2]), "=d" (p[3]) + : "0" (ax) + ); +#endif +} + void LinuxInstance::enumerateCpu() { json cpu = {}; + + uint32_t cpuInfo[4]={0,0,0,0}; + char CPUBrandString[16]; + char CPUModelString[16]; + char CPUClockString[16]; + uint32_t nExIds; + getLCpuId(cpuInfo, 0x80000000); + nExIds = cpuInfo[0]; + + for (uint32_t i = 0x80000000; i <= nExIds; ++i) { + getLCpuId(cpuInfo, i); + // Interpret CPU brand string + if (i == 0x80000002) { + memcpy(CPUBrandString, cpuInfo, sizeof(cpuInfo)); + } else if (i == 0x80000003) { + memcpy(CPUModelString, cpuInfo, sizeof(cpuInfo)); + } else if (i == 0x80000004) { + memcpy(CPUClockString, cpuInfo, sizeof(cpuInfo)); + } + } - cpu["cpuBrand"] = ""; - cpu["cpuModel"] = ""; - cpu["cpuClockSpeed"] = ""; - cpu["cpuNumCores"] = ""; + cpu["cpuBrand"] = CPUBrandString; + cpu["cpuModel"] = CPUModelString; + cpu["cpuClockSpeed"] = CPUClockString; + cpu["cpuNumCores"] = std::thread::hardware_concurrency(); _cpu.push_back(cpu); } diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index a445ea2343..180e914d60 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -378,6 +378,8 @@ void JitterSample::configure(const Config& config) { } } else if (config.stop) { _sampleSequence.currentIndex = -1; + } else { + _sampleSequence.currentIndex = config.getIndex(); } _scale = config.scale; } @@ -392,10 +394,10 @@ void JitterSample::run(const render::RenderContextPointer& renderContext, Output } } - jitter.x = 0.0f; - jitter.y = 0.0f; if (current >= 0) { jitter = _sampleSequence.offsets[current]; + } else { + jitter = glm::vec2(0.0f); } } diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index 936ade043d..7d8bbb44d9 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -109,7 +109,6 @@ public: void setDebugFXAA(bool debug) { debugFXAAX = (debug ? 0.0f : 1.0f); emit dirty();} bool debugFXAA() const { return (debugFXAAX == 0.0f ? true : false); } - float blend{ 0.25f }; float sharpen{ 0.05f }; diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index d808823d0c..624869bbf5 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -248,7 +248,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Debugging task is happening in the "over" layer after tone mapping and just before HUD { // Debug the bounds of the rendered items, still look at the zbuffer - const auto extraDebugBuffers = RenderDeferredTaskDebug::ExtraBuffers(linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, ambientOcclusionFramebuffer, scatteringResource, velocityBuffer); + const auto extraDebugBuffers = RenderDeferredTaskDebug::ExtraBuffers(linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, ambientOcclusionUniforms, scatteringResource, velocityBuffer); const auto debugInputs = RenderDeferredTaskDebug::Input(fetchedItems, shadowTaskOutputs, lightingStageInputs, lightClusters, prepareDeferredOutputs, extraDebugBuffers, deferredFrameTransform, jitter, lightingModel).asVarying(); task.addJob("DebugRenderDeferredTask", debugInputs); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7abb63ca1c..309161206c 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -987,6 +987,31 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& }; }; + /**jsdoc + * The name of an entity event. When the entity event occurs, any function that has been registered for that event via + * {@link Script.addEventHandler} is called with parameters per the entity event. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Event NameEntity Event
"enterEntity"{@link Entities.enterEntity}
"leaveEntity"{@link Entities.leaveEntity}
"mousePressOnEntity"{@link Entities.mousePressOnEntity}
"mouseMoveOnEntity"{@link Entities.mouseMoveOnEntity}
"mouseReleaseOnEntity"{@link Entities.mouseReleaseOnEntity}
"clickDownOnEntity"{@link Entities.clickDownOnEntity}
"holdingClickOnEntity"{@link Entities.holdingClickOnEntity}
"clickReleaseOnEntity"{@link Entities.clickReleaseOnEntity}
"hoverEnterEntity"{@link Entities.hoverEnterEntity}
"hoverOverEntity"{@link Entities.hoverOverEntity}
"hoverLeaveEntity"{@link Entities.hoverLeaveEntity}
"collisionWithEntity"{@link Entities.collisionWithEntity}
+ * + * @typedef {string} Script.EntityEvent + */ connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity")); connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity")); @@ -2147,7 +2172,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& } /**jsdoc - * Triggered when the script starts for a user. + * Triggered when the script starts for a user. See also, {@link Script.entityScriptPreloadFinished}. *

Note: Can only be connected to via this.preload = function (...) { ... } in the entity script.

*
Available in:Client Entity ScriptsServer Entity Scripts
* @function Entities.preload @@ -2161,7 +2186,7 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& * this.entityID = entityID; * print("Entity ID: " + this.entityID); * }; - * ); + * }); * * var entityID = Entities.addEntity({ * type: "Box", diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 3549578ed5..e58609f01d 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -100,6 +100,8 @@ public: }; /**jsdoc + * The Script API provides facilities for working with scripts. + * * @namespace Script * * @hifi-interface @@ -108,7 +110,14 @@ public: * @hifi-server-entity * @hifi-assignment-client * - * @property {string} context + * @property {string} context - The context that the script is running in: + *
    + *
  • "client": An Interface or avatar script.
  • + *
  • "entity_client": A client entity script.
  • + *
  • "entity_server": A server entity script.
  • + *
  • "agent": An assignment client script.
  • + *
+ * Read-only. */ class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider { Q_OBJECT @@ -150,9 +159,19 @@ public: QList getListOfEntityScriptIDs(); /**jsdoc - * Stop the current script. + * Stops and unloads the current script. + *

Warning: If an assignment client script, the script gets restarted after stopping.

* @function Script.stop - * @param {boolean} [marshal=false] + * @param {boolean} [marshal=false] - Marshal. + *

Deprecated: This parameter is deprecated and will be removed.

+ * @example Stop a script after 5s. + * Script.setInterval(function () { + * print("Hello"); + * }, 1000); + * + * Script.setTimeout(function () { + * Script.stop(true); + * }, 5000); */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // NOTE - this is intended to be a public interface for Agent scripts, and local scripts, but not for EntityScripts @@ -167,18 +186,20 @@ public: /**jsdoc * @function Script.registerGlobalObject - * @param {string} name - * @param {object} object + * @param {string} name - Name. + * @param {object} object - Object. + * @deprecated This function is deprecated and will be removed. */ /// registers a global object by name Q_INVOKABLE void registerGlobalObject(const QString& name, QObject* object); /**jsdoc * @function Script.registerGetterSetter - * @param {string} name - * @param {object} getter - * @param {object} setter - * @param {string} [parent=""] + * @param {string} name - Name. + * @param {function} getter - Getter. + * @param {function} setter - Setter. + * @param {string} [parent=""] - Parent. + * @deprecated This function is deprecated and will be removed. */ /// registers a global getter/setter Q_INVOKABLE void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, @@ -186,19 +207,21 @@ public: /**jsdoc * @function Script.registerFunction - * @param {string} name - * @param {object} function - * @param {number} [numArguments=-1] + * @param {string} name - Name. + * @param {function} function - Function. + * @param {number} [numArguments=-1] - Number of arguments. + * @deprecated This function is deprecated and will be removed. */ /// register a global function Q_INVOKABLE void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); /**jsdoc * @function Script.registerFunction - * @param {string} parent - * @param {string} name - * @param {object} function - * @param {number} [numArguments=-1] + * @param {string} parent - Parent. + * @param {string} name - Name. + * @param {function} function - Function. + * @param {number} [numArguments=-1] - Number of arguments. + * @deprecated This function is deprecated and will be removed. */ /// register a function as a method on a previously registered global object Q_INVOKABLE void registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature fun, @@ -206,27 +229,30 @@ public: /**jsdoc * @function Script.registerValue - * @param {string} name - * @param {object} value + * @param {string} name - Name. + * @param {object} value - Value. + * @deprecated This function is deprecated and will be removed. */ /// registers a global object by name Q_INVOKABLE void registerValue(const QString& valueName, QScriptValue value); /**jsdoc * @function Script.evaluate - * @param {string} program - * @param {string} filename - * @param {number} [lineNumber=-1] - * @returns {object} + * @param {string} program - Program. + * @param {string} filename - File name. + * @param {number} [lineNumber=-1] - Line number. + * @returns {object} Object. + * @deprecated This function is deprecated and will be removed. */ /// evaluate some code in the context of the ScriptEngine and return the result Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget /**jsdoc * @function Script.evaluateInClosure - * @param {object} locals - * @param {object} program - * @returns {object} + * @param {object} locals - Locals. + * @param {object} program - Program. + * @returns {object} Object. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program); @@ -237,38 +263,53 @@ public: bool hasValidScriptSuffix(const QString& scriptFileName); /**jsdoc + * Gets the context that the script is running in: Interface/avatar, client entity, server entity, or assignment client. * @function Script.getContext - * @returns {string} + * @returns {string} The context that the script is running in: + *
    + *
  • "client": An Interface or avatar script.
  • + *
  • "entity_client": A client entity script.
  • + *
  • "entity_server": A server entity script.
  • + *
  • "agent": An assignment client script.
  • + *
*/ Q_INVOKABLE QString getContext() const; /**jsdoc + * Checks whether the script is running as an Interface or avatar script. * @function Script.isClientScript - * @returns {boolean} + * @returns {boolean} true if the script is running as an Interface or avatar script, false if it + * isn't. */ Q_INVOKABLE bool isClientScript() const { return _context == CLIENT_SCRIPT; } /**jsdoc + * Checks whether the application was compiled as a debug build. * @function Script.isDebugMode - * @returns {boolean} + * @returns {boolean} true if the application was compiled as a debug build, false if it was + * compiled as a release build. */ Q_INVOKABLE bool isDebugMode() const; /**jsdoc + * Checks whether the script is running as a client entity script. * @function Script.isEntityClientScript - * @returns {boolean} + * @returns {boolean} true if the script is running as a client entity script, false if it isn't. */ Q_INVOKABLE bool isEntityClientScript() const { return _context == ENTITY_CLIENT_SCRIPT; } /**jsdoc + * Checks whether the script is running as a server entity script. * @function Script.isEntityServerScript - * @returns {boolean} + * @returns {boolean} true if the script is running as a server entity script, false if it isn't. */ Q_INVOKABLE bool isEntityServerScript() const { return _context == ENTITY_SERVER_SCRIPT; } /**jsdoc + * Checks whether the script is running as an assignment client script. * @function Script.isAgentScript - * @returns {boolean} + * @returns {boolean} true if the script is running as an assignment client script, false if it + * isn't. */ Q_INVOKABLE bool isAgentScript() const { return _context == AGENT_SCRIPT; } @@ -276,25 +317,42 @@ public: // NOTE - these are intended to be public interfaces available to scripts /**jsdoc + * Adds a function to the list of functions called when an entity event occurs on a particular entity. * @function Script.addEventHandler - * @param {Uuid} entityID - * @param {string} eventName - * @param {function} handler + * @param {Uuid} entityID - The ID of the entity. + * @param {Script.EntityEvent} eventName - The name of the entity event. + * @param {function} handler - The function to call when the entity event occurs on the entity. It can be either the name + * of a function or an in-line definition. + * @example Report when a mouse press occurs on a particular entity. + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * lifetime: 300 // Delete after 5 minutes. + * }); + * + * function reportMousePress(entityID, event) { + * print("Mouse pressed on entity: " + JSON.stringify(event)); + * } + * + * Script.addEventHandler(entityID, "mousePressOnEntity", reportMousePress); */ Q_INVOKABLE void addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); /**jsdoc + * Removes a function from the list of functions called when an entity event occurs on a particular entity. * @function Script.removeEventHandler - * @param {Uuid} entityID - * @param {string} eventName - * @param {function} handler + * @param {Uuid} entityID - The ID of the entity. + * @param {Script.EntityEvent} eventName - The name of the entity event. + * @param {function} handler - The name of the function to no longer call when the entity event occurs on the entity. */ Q_INVOKABLE void removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler); /**jsdoc - * Start a new Interface or entity script. + * Starts running another script in Interface. + *
Available in:Interface ScriptsAvatar Scripts
* @function Script.load - * @param {string} filename - The URL of the script to load. Can be relative to the current script. + * @param {string} filename - The URL of the script to load. This can be relative to the current script's URL. * @example Load a script from another script. * // First file: scriptA.js * print("This is script A"); @@ -303,7 +361,7 @@ public: * print("This is script B"); * Script.load("scriptA.js"); * - * // If you run scriptB.js you should see both scripts in the running scripts list. + * // If you run scriptB.js you should see both scripts in the Running Scripts dialog. * // And you should see the following output: * // This is script B * // This is script A @@ -311,22 +369,24 @@ public: Q_INVOKABLE void load(const QString& loadfile); /**jsdoc - * Include JavaScript from other files in the current script. If a callback is specified the files are loaded and included - * asynchronously, otherwise they are included synchronously (i.e., script execution blocks while the files are included). + * Includes JavaScript from other files in the current script. If a callback is specified, the files are loaded and + * included asynchronously, otherwise they are included synchronously (i.e., script execution blocks while the files are + * included). * @function Script.include + * @variation 0 * @param {string[]} filenames - The URLs of the scripts to include. Each can be relative to the current script. - * @param {function} [callback=null] - The function to call back when the scripts have been included. Can be an in-line - * function or the name of a function. + * @param {function} [callback=null] - The function to call back when the scripts have been included. It can be either the + * name of a function or an in-line definition. */ Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue()); /**jsdoc - * Include JavaScript from another file in the current script. If a callback is specified the file is loaded and included + * Includes JavaScript from another file in the current script. If a callback is specified, the file is loaded and included * asynchronously, otherwise it is included synchronously (i.e., script execution blocks while the file is included). * @function Script.include - * @param {string} filename - The URL of the script to include. Can be relative to the current script. - * @param {function} [callback=null] - The function to call back when the script has been included. Can be an in-line - * function or the name of a function. + * @param {string} filename - The URL of the script to include. It can be relative to the current script. + * @param {function} [callback=null] - The function to call back when the script has been included. It can be either the + * name of a function or an in-line definition. * @example Include a script file asynchronously. * // First file: scriptA.js * print("This is script A"); @@ -349,16 +409,21 @@ public: // MODULE related methods /**jsdoc + * Provides access to methods or objects provided in an external JavaScript or JSON file. + * See {@link https://docs.highfidelity.com/script/js-tips.html} for further details. * @function Script.require - * @param {string} module + * @param {string} module - The module to use. May be a JavaScript file or the name of a system module such as + * "sppUi". */ Q_INVOKABLE QScriptValue require(const QString& moduleId); /**jsdoc * @function Script.resetModuleCache - * @param {boolean} [deleteScriptCache=false] + * @param {boolean} [deleteScriptCache=false] - Delete script cache. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false); + QScriptValue currentModule(); bool registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent); QScriptValue newModule(const QString& modulePath, const QScriptValue& parent = QScriptValue()); @@ -366,53 +431,53 @@ public: QScriptValue instantiateModule(const QScriptValue& module, const QString& sourceCode); /**jsdoc - * Call a function at a set interval. + * Calls a function repeatedly, at a set interval. * @function Script.setInterval - * @param {function} function - The function to call. Can be an in-line function or the name of a function. + * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. * @param {number} interval - The interval at which to call the function, in ms. - * @returns {object} A handle to the interval timer. Can be used by {@link Script.clearInterval}. + * @returns {object} A handle to the interval timer. This can be used in {@link Script.clearInterval}. * @example Print a message every second. * Script.setInterval(function () { - * print("Timer fired"); + * print("Interval timer fired"); * }, 1000); */ Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS); /**jsdoc - * Call a function after a delay. + * Calls a function once, after a delay. * @function Script.setTimeout - * @param {function} function - The function to call. Can be an in-line function or the name of a function. + * @param {function} function - The function to call. This can be either the name of a function or an in-line definition. * @param {number} timeout - The delay after which to call the function, in ms. - * @returns {object} A handle to the timeout timer. Can be used by {@link Script.clearTimeout}. - * @example Print a message after a second. + * @returns {object} A handle to the timeout timer. This can be used in {@link Script.clearTimeout}. + * @example Print a message once, after a second. * Script.setTimeout(function () { - * print("Timer fired"); + * print("Timeout timer fired"); * }, 1000); */ Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS); /**jsdoc - * Stop an interval timer set by {@link Script.setInterval|setInterval}. + * Stops an interval timer set by {@link Script.setInterval|setInterval}. * @function Script.clearInterval - * @param {object} timer - The interval timer to clear. + * @param {object} timer - The interval timer to stop. * @example Stop an interval timer. * // Print a message every second. * var timer = Script.setInterval(function () { - * print("Timer fired"); + * print("Interval timer fired"); * }, 1000); * * // Stop the timer after 10 seconds. * Script.setTimeout(function () { - * print("Stop timer"); + * print("Stop interval timer"); * Script.clearInterval(timer); * }, 10000); */ Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } /**jsdoc - * Clear a timeout timer set by {@link Script.setTimeout|setTimeout}. + * Stops a timeout timer set by {@link Script.setTimeout|setTimeout}. * @function Script.clearTimeout - * @param {object} timer - The timeout timer to clear. + * @param {object} timer - The timeout timer to stop. * @example Stop a timeout timer. * // Print a message after two seconds. * var timer = Script.setTimeout(function () { @@ -425,34 +490,53 @@ public: Q_INVOKABLE void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } /**jsdoc + * Prints a message to the program log. + *

Alternatively, you can use {@link print}, {@link console.log}, or one of the other {@link console} methods.

* @function Script.print - * @param {string} message + * @param {string} message - The message to print. + */ + /**jsdoc + * Prints a message to the program log. + *

This is an alias of {@link Script.print}.

+ * @function print + * @param {string} message - The message to print. */ Q_INVOKABLE void print(const QString& message); /**jsdoc - * Resolve a relative path to an absolute path. + * Resolves a relative path to an absolute path. The relative path is relative to the script's location. * @function Script.resolvePath * @param {string} path - The relative path to resolve. * @returns {string} The absolute path. + * @example Report the directory and filename of the running script. + * print(Script.resolvePath("")); + * @example Report the directory of the running script. + * print(Script.resolvePath(".")); + * @example Report the path to a file located relative to the running script. + * print(Script.resolvePath("../assets/sounds/hello.wav")); */ Q_INVOKABLE QUrl resolvePath(const QString& path) const; /**jsdoc + * Gets the path to the resources directory for QML files. * @function Script.resourcesPath - * @returns {string} + * @returns {string} The path to the resources directory for QML files. */ Q_INVOKABLE QUrl resourcesPath() const; /**jsdoc + * Starts timing a section of code in order to send usage data about it to High Fidelity. Shouldn't be used outside of the + * standard scripts. * @function Script.beginProfileRange - * @param {string} label + * @param {string} label - A name that identifies the section of code. */ Q_INVOKABLE void beginProfileRange(const QString& label) const; /**jsdoc + * Finishes timing a section of code in order to send usage data about it to High Fidelity. Shouldn't be used outside of + * the standard scripts. * @function Script.endProfileRange - * @param {string} label + * @param {string} label - A name that identifies the section of code. */ Q_INVOKABLE void endProfileRange(const QString& label) const; @@ -460,9 +544,10 @@ public: // Entity Script Related methods /**jsdoc + * Checks whether an entity has an entity script running. * @function Script.isEntityScriptRunning - * @param {Uuid} entityID - * @returns {boolean} + * @param {Uuid} entityID - The ID of the entity. + * @returns {boolean} true if the entity has an entity script running, false if it doesn't. */ Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) { QReadLocker locker { &_entityScriptsLock }; @@ -474,60 +559,71 @@ public: /**jsdoc * @function Script.loadEntityScript - * @param {Uuid} entityID - * @param {string} script - * @param {boolean} forceRedownload + * @param {Uuid} entityID - Entity ID. + * @param {string} script - Script. + * @param {boolean} forceRedownload - Force re-download. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload); /**jsdoc * @function Script.unloadEntityScript - * @param {Uuid} entityID - * @param {boolean} [shouldRemoveFromMap=false] + * @param {Uuid} entityID - Entity ID. + * @param {boolean} [shouldRemoveFromMap=false] - Should remove from map. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method /**jsdoc * @function Script.unloadAllEntityScripts + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void unloadAllEntityScripts(); /**jsdoc + * Calls a method in an entity script. * @function Script.callEntityScriptMethod - * @param {Uuid} entityID - * @param {string} methodName - * @param {string[]} parameters - * @param {Uuid} [remoteCallerID=Uuid.NULL] + * @param {Uuid} entityID - The ID of the entity running the entity script. + * @param {string} methodName - The name of the method to call. + * @param {string[]} [parameters=[]] - The parameters to call the specified method with. + * @param {Uuid} [remoteCallerID=Uuid.NULL] - An ID that identifies the caller. */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList(), const QUuid& remoteCallerID = QUuid()) override; /**jsdoc + * Calls a method in an entity script. * @function Script.callEntityScriptMethod - * @param {Uuid} entityID - * @param {string} methodName - * @param {PointerEvent} event + * @param {Uuid} entityID - Entity ID. + * @param {string} methodName - Method name. + * @param {PointerEvent} event - Pointer event. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event); /**jsdoc + * Calls a method in an entity script. * @function Script.callEntityScriptMethod - * @param {Uuid} entityID - * @param {string} methodName - * @param {Uuid} otherID - * @param {Collision} collision + * @param {Uuid} entityID - Entity ID. + * @param {string} methodName - Method name. + * @param {Uuid} otherID - Other entity ID. + * @param {Collision} collision - Collision. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision); /**jsdoc + * Manually runs the JavaScript garbage collector which reclaims memory by disposing of objects that are no longer + * reachable. * @function Script.requestGarbageCollection */ Q_INVOKABLE void requestGarbageCollection() { collectGarbage(); } /**jsdoc * @function Script.generateUUID - * @returns {Uuid} + * @returns {Uuid} A new UUID. + * @deprecated This function is deprecated and will be removed. Use {@link Uuid.generate} instead. */ Q_INVOKABLE QUuid generateUUID() { return QUuid::createUuid(); } @@ -573,7 +669,7 @@ public slots: /**jsdoc * @function Script.callAnimationStateHandler - * @param {function} callback - Callback. + * @param {function} callback - Callback function. * @param {object} parameters - Parameters. * @param {string[]} names - Names. * @param {boolean} useNames - Use names. @@ -584,7 +680,8 @@ public slots: /**jsdoc * @function Script.updateMemoryCost - * @param {number} deltaSize + * @param {number} deltaSize - Delta size. + * @deprecated This function is deprecated and will be removed. */ void updateMemoryCost(const qint64&); @@ -592,31 +689,37 @@ signals: /**jsdoc * @function Script.scriptLoaded - * @param {string} filename + * @param {string} filename - File name. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void scriptLoaded(const QString& scriptFilename); /**jsdoc * @function Script.errorLoadingScript - * @param {string} filename + * @param {string} filename - File name. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void errorLoadingScript(const QString& scriptFilename); /**jsdoc - * Triggered regularly at a system-determined frequency. + * Triggered frequently at a system-determined interval. * @function Script.update * @param {number} deltaTime - The time since the last update, in s. * @returns {Signal} + * @example Report script update intervals. + * Script.update.connect(function (deltaTime) { + * print("Update: " + deltaTime); + * }); */ void update(float deltaTime); /**jsdoc - * Triggered when the script is ending. + * Triggered when the script is stopping. * @function Script.scriptEnding * @returns {Signal} - * @example Connect to the scriptEnding signal. + * @example Report when a script is stopping. * print("Script started"); * * Script.scriptEnding.connect(function () { @@ -632,52 +735,60 @@ signals: /**jsdoc * @function Script.finished - * @param {string} filename - * @param {object} engine + * @param {string} filename - File name. + * @param {object} engine - Engine. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void finished(const QString& fileNameString, ScriptEnginePointer); /**jsdoc * @function Script.cleanupMenuItem - * @param {string} menuItem + * @param {string} menuItem - Menu item. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void cleanupMenuItem(const QString& menuItemString); /**jsdoc + * Triggered when a script prints a message to the program log via {@link Script.print}, {@link print}, + * {@link console.log}, {@link console.info}, {@link console.warn}, {@link console.error}, or {@link console.debug}. * @function Script.printedMessage - * @param {string} message - * @param {string} scriptName + * @param {string} message - The message. + * @param {string} scriptName - The name of the script that generated the message. * @returns {Signal} */ void printedMessage(const QString& message, const QString& scriptName); /**jsdoc + * Triggered when a script generates an error or {@link console.error} is called. * @function Script.errorMessage - * @param {string} message - * @param {string} scriptName + * @param {string} message - The error message. + * @param {string} scriptName - The name of the script that generated the error message. * @returns {Signal} */ void errorMessage(const QString& message, const QString& scriptName); /**jsdoc + * Triggered when a script generates a warning or {@link console.warn} is called. * @function Script.warningMessage - * @param {string} message - * @param {string} scriptName + * @param {string} message - The warning message. + * @param {string} scriptName - The name of the script that generated the warning message. * @returns {Signal} */ void warningMessage(const QString& message, const QString& scriptName); /**jsdoc + * Triggered when a script generates an information message or {@link console.info} is called. * @function Script.infoMessage - * @param {string} message - * @param {string} scriptName + * @param {string} message - The information message. + * @param {string} scriptName - The name of the script that generated the information message. * @returns {Signal} */ void infoMessage(const QString& message, const QString& scriptName); /**jsdoc + * Triggered when the running state of the script changes, e.g., from running to stopping. * @function Script.runningStateChanged * @returns {Signal} */ @@ -686,26 +797,30 @@ signals: /**jsdoc * @function Script.clearDebugWindow * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void clearDebugWindow(); /**jsdoc * @function Script.loadScript - * @param {string} scriptName - * @param {boolean} isUserLoaded + * @param {string} scriptName - Script name. + * @param {boolean} isUserLoaded - Is user loaded. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void loadScript(const QString& scriptName, bool isUserLoaded); /**jsdoc * @function Script.reloadScript - * @param {string} scriptName - * @param {boolean} isUserLoaded + * @param {string} scriptName - Script name. + * @param {boolean} isUserLoaded - Is user loaded. * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void reloadScript(const QString& scriptName, bool isUserLoaded); /**jsdoc + * Triggered when the script has stopped. * @function Script.doneRunning * @returns {Signal} */ @@ -714,14 +829,35 @@ signals: /**jsdoc * @function Script.entityScriptDetailsUpdated * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ // Emitted when an entity script is added or removed, or when the status of an entity // script is updated (goes from RUNNING to ERROR_RUNNING_SCRIPT, for example) void entityScriptDetailsUpdated(); /**jsdoc + * Triggered when the script starts for the user. See also, {@link Entities.preload}. + *
Available in:Client Entity ScriptsServer Entity Scripts
* @function Script.entityScriptPreloadFinished + * @param {Uuid} entityID - The ID of the entity that the script is running in. * @returns {Signal} + * @example Get the ID of the entity that a client entity script is running in. + * var entityScript = (function () { + * this.entityID = Uuid.NULL; + * + * Script.entityScriptPreloadFinished.connect(function (entityID) { + * this.entityID = entityID; + * print("Entity ID: " + this.entityID); + * }); + * + * var entityID = Entities.addEntity({ + * type: "Box", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.5, y: 0.5, z: 0.5 }, + * color: { red: 255, green: 0, blue: 0 }, + * script: "(" + entityScript + ")", // Could host the script on a Web server instead. + * lifetime: 300 // Delete after 5 minutes. + * }); */ // Emitted when an entity script has finished running preload void entityScriptPreloadFinished(const EntityItemID& entityID); @@ -731,16 +867,18 @@ protected: /**jsdoc * @function Script.executeOnScriptThread - * @param {object} function - * @param {ConnectionType} [type=2] + * @param {function} function - Function. + * @param {ConnectionType} [type=2] - Connection type. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void executeOnScriptThread(std::function function, const Qt::ConnectionType& type = Qt::QueuedConnection ); /**jsdoc * @function Script._requireResolve - * @param {string} module - * @param {string} [relativeTo=""] - * @returns {string} + * @param {string} module - Module. + * @param {string} [relativeTo=""] - Relative to. + * @returns {string} Result. + * @deprecated This function is deprecated and will be removed. */ // note: this is not meant to be called directly, but just to have QMetaObject take care of wiring it up in general; // then inside of init() we just have to do "Script.require.resolve = Script._requireResolve;" @@ -763,12 +901,13 @@ protected: /**jsdoc * @function Script.entityScriptContentAvailable - * @param {Uuid} entityID - * @param {string} scriptOrURL - * @param {string} contents - * @param {boolean} isURL - * @param {boolean} success - * @param {string} status + * @param {Uuid} entityID - Entity ID. + * @param {string} scriptOrURL - Path. + * @param {string} contents - Contents. + * @param {boolean} isURL - Is a URL. + * @param {boolean} success - Success. + * @param {string} status - Status. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success, const QString& status); diff --git a/libraries/shared/src/BaseScriptEngine.h b/libraries/shared/src/BaseScriptEngine.h index 8820a386bf..443c7b0500 100644 --- a/libraries/shared/src/BaseScriptEngine.h +++ b/libraries/shared/src/BaseScriptEngine.h @@ -31,8 +31,32 @@ public: BaseScriptEngine() {} + /**jsdoc + * @function Script.lintScript + * @param {string} sourceCode - Source code. + * @param {string} fileName - File name. + * @param {number} [lineNumber=1] - Line number. + * @returns {object} Object. + * @deprecated This function is deprecated and will be removed. + */ Q_INVOKABLE QScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1); + + /**jsdoc + * @function Script.makeError + * @param {object} [other] - Other. + * @param {string} [type="Error"] - Error. + * @returns {object} Object. + * @deprecated This function is deprecated and will be removed. + */ Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error"); + + /**jsdoc + * @function Script.formatExecption + * @param {object} exception - Exception. + * @param {boolean} inludeExtendeDetails - Include extended details. + * @returns {string} String. + * @deprecated This function is deprecated and will be removed. + */ Q_INVOKABLE QString formatException(const QScriptValue& exception, bool includeExtendedDetails); QScriptValue cloneUncaughtException(const QString& detail = QString()); @@ -48,6 +72,25 @@ public: // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways static bool IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method); signals: + /**jsdoc + * @function Script.signalHandlerException + * @param {object} exception - Exception. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + // Script.signalHandlerException is exposed by QScriptEngine. + + /**jsdoc + * Triggered when a script generates an unhandled exception. + * @function Script.unhandledException + * @param {object} exception - The details of the exception. + * @returns {Signal} + * @example Report the details of an unhandled exception. + * Script.unhandledException.connect(function (exception) { + * print("Unhandled exception: " + JSON.stringify(exception)); + * }); + * var properties = JSON.parse("{ x: 1"); // Invalid JSON string. + */ void unhandledException(const QScriptValue& exception); protected: diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index 773e40aaee..d5c2f3ec6c 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -28,8 +28,8 @@ #endif -#include +#include #include "SharedLogging.h" GPUIdent GPUIdent::_instance {}; diff --git a/scripts/system/simplifiedUI/simplifiedStatusIndicator/simplifiedStatusIndicator.js b/scripts/system/simplifiedUI/simplifiedStatusIndicator/simplifiedStatusIndicator.js new file mode 100644 index 0000000000..9968260034 --- /dev/null +++ b/scripts/system/simplifiedUI/simplifiedStatusIndicator/simplifiedStatusIndicator.js @@ -0,0 +1,243 @@ +// +// simplifiedStatusIndicator.js +// +// Created by Robin Wilson on 2019-05-17 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +function simplifiedStatusIndicator(properties) { + var that = this; + var DEBUG = false; + + // #region HEARTBEAT + + // Send heartbeat with updates to database + // When this stops, the database will set status to offline + var HEARTBEAT_TIMEOUT_MS = 5000, + heartbeat; + function startHeartbeatTimer() { + if (heartbeat) { + Script.clearTimeout(heartbeat); + heartbeat = false; + } + + heartbeat = Script.setTimeout(function() { + heartbeat = false; + getStatus(setStatus); + }, HEARTBEAT_TIMEOUT_MS); + } + + // #endregion HEARTBEAT + + + // #region SEND/GET STATUS REQUEST + + function setStatusExternally(newStatus) { + if (!newStatus) { + return; + } + + setStatus(newStatus); + } + + + var request = Script.require('request').request, + REQUEST_URL = "https://highfidelity.co/api/statusIndicator/"; + function setStatus(newStatus) { + if (heartbeat) { + Script.clearTimeout(heartbeat); + heartbeat = false; + } + + if (newStatus && currentStatus !== newStatus) { + currentStatus = newStatus; + that.statusChanged(); + } + + var queryParamString = "type=heartbeat"; + queryParamString += "&username=" + AccountServices.username; + + var displayNameToSend = MyAvatar.sessionDisplayName; + if (displayNameToSend === "") { + displayNameToSend = MyAvatar.displayName; + } + queryParamString += "&displayName=" + displayNameToSend; + queryParamString += "&status=" + currentStatus; + queryParamString += "&organization=" + location.hostname; + + var uri = REQUEST_URL + "?" + queryParamString; + + if (DEBUG) { + console.log("simplifiedStatusIndicator: setStatus: " + uri); + } + + request({ + uri: uri + }, function (error, response) { + startHeartbeatTimer(); + + if (error || !response || response.status !== "success") { + console.error("Error with setStatus: " + JSON.stringify(response)); + return; + } + }); + } + + // Get status from database + function getStatus(callback) { + var queryParamString = "type=getStatus"; + queryParamString += "&username=" + AccountServices.username; + + var uri = REQUEST_URL + "?" + queryParamString; + + if (DEBUG) { + console.log("simplifiedStatusIndicator: getStatus: " + uri); + } + + request({ + uri: uri + }, function (error, response) { + if (error || !response || response.status !== "success") { + console.error("Error with getStatus: " + JSON.stringify(response)); + } else if (response.data.userStatus.toLowerCase() !== "offline") { + if (response.data.userStatus !== currentStatus) { + currentStatus = response.data.userStatus; + that.statusChanged(); + } + } + + if (callback) { + callback(); + } + }); + } + + + function getLocalStatus() { + return currentStatus; + } + + // #endregion SEND/GET STATUS REQUEST + + + // #region SIGNALS + + var currentStatus = "available"; // Default is available + function toggleStatus() { + if (currentStatus === "busy") { + currentStatus = "available"; + // Else if current status is "available" OR anything else (custom) + } else { + currentStatus = "busy"; + } + + that.statusChanged(); + + setStatus(); + } + + + // When avatar becomes active from being away + // Set status back to previousStatus + function onWentActive() { + if (currentStatus !== previousStatus) { + currentStatus = previousStatus; + that.statusChanged(); + } + setStatus(); + } + + + // When avatar goes away, set status to busy + var previousStatus; + function onWentAway() { + previousStatus = currentStatus; + if (currentStatus !== "busy") { + currentStatus = "busy"; + that.statusChanged(); + } + setStatus(); + } + + + // Domain changed update avatar location + function onDomainChanged() { + var queryParamString = "type=updateEmployee"; + queryParamString += "&username=" + AccountServices.username; + queryParamString += "&location=unknown"; + + var uri = REQUEST_URL + "?" + queryParamString; + + if (DEBUG) { + console.log("simplifiedStatusIndicator: onDomainChanged: " + uri); + } + + request({ + uri: uri + }, function (error, response) { + if (error || !response || response.status !== "success") { + console.error("Error with onDomainChanged: " + JSON.stringify(response)); + } else { + // successfully sent updateLocation + if (DEBUG) { + console.log("simplifiedStatusIndicator: Successfully updated location after domain change."); + } + } + }); + } + + + function statusChanged() { + + } + + // #endregion SIGNALS + + + // #region APP LIFETIME + + // Creates the app button and sets up signals and hearbeat + function startup() { + MyAvatar.wentAway.connect(onWentAway); + MyAvatar.wentActive.connect(onWentActive); + MyAvatar.displayNameChanged.connect(setStatus); + Window.domainChanged.connect(onDomainChanged); + + getStatus(setStatus); + } + + + // Cleans up timeouts, signals, and overlays + function unload() { + MyAvatar.wentAway.disconnect(onWentAway); + MyAvatar.wentActive.disconnect(onWentActive); + MyAvatar.displayNameChanged.disconnect(setStatus); + Window.domainChanged.disconnect(onDomainChanged); + if (heartbeat) { + Script.clearTimeout(heartbeat); + heartbeat = false; + } + } + + // #endregion APP LIFETIME + + that.startup = startup; + that.unload = unload; + that.toggleStatus = toggleStatus; + that.setStatus = setStatus; + that.getLocalStatus = getLocalStatus; + that.statusChanged = statusChanged; + + // Overwrite with the given properties + var overwriteableKeys = ["statusChanged"]; + Object.keys(properties).forEach(function (key) { + if (overwriteableKeys.indexOf(key) > -1) { + that[key] = properties[key]; + } + }); +} + +module.exports = simplifiedStatusIndicator; \ No newline at end of file diff --git a/scripts/system/simplifiedUI/simplifiedUI.js b/scripts/system/simplifiedUI/simplifiedUI.js index c3556a1fbf..351372613b 100644 --- a/scripts/system/simplifiedUI/simplifiedUI.js +++ b/scripts/system/simplifiedUI/simplifiedUI.js @@ -238,40 +238,57 @@ function updateOutputDeviceMutedOverlay(isMuted) { } -var savedAvatarGain = Audio.getAvatarGain(); -var savedInjectorGain = Audio.getInjectorGain(); -var savedLocalInjectorGain = Audio.getLocalInjectorGain(); -var savedSystemInjectorGain = Audio.getSystemInjectorGain(); +var savedAvatarGain = Audio.avatarGain; +var savedServerInjectorGain = Audio.serverInjectorGain; +var savedLocalInjectorGain = Audio.localInjectorGain; +var savedSystemInjectorGain = Audio.systemInjectorGain; +var MUTED_VALUE_DB = -60; // This should always match `SimplifiedConstants.qml` -> numericConstants -> mutedValue! function setOutputMuted(outputMuted) { - updateOutputDeviceMutedOverlay(outputMuted); - if (outputMuted) { - savedAvatarGain = Audio.getAvatarGain(); - savedInjectorGain = Audio.getInjectorGain(); - savedLocalInjectorGain = Audio.getLocalInjectorGain(); - savedSystemInjectorGain = Audio.getSystemInjectorGain(); + savedAvatarGain = Audio.avatarGain; + savedServerInjectorGain = Audio.serverInjectorGain; + savedLocalInjectorGain = Audio.localInjectorGain; + savedSystemInjectorGain = Audio.systemInjectorGain; - Audio.setAvatarGain(-60); - Audio.setInjectorGain(-60); - Audio.setLocalInjectorGain(-60); - Audio.setSystemInjectorGain(-60); + Audio.avatarGain = MUTED_VALUE_DB; + Audio.serverInjectorGain = MUTED_VALUE_DB; + Audio.localInjectorGain = MUTED_VALUE_DB; + Audio.systemInjectorGain = MUTED_VALUE_DB; } else { - if (savedAvatarGain === -60) { + if (savedAvatarGain === MUTED_VALUE_DB) { savedAvatarGain = 0; } - Audio.setAvatarGain(savedAvatarGain); - if (savedInjectorGain === -60) { - savedInjectorGain = 0; + Audio.avatarGain = savedAvatarGain; + if (savedServerInjectorGain === MUTED_VALUE_DB) { + savedServerInjectorGain = 0; } - Audio.setInjectorGain(savedInjectorGain); - if (savedLocalInjectorGain === -60) { + Audio.serverInjectorGain = savedServerInjectorGain; + if (savedLocalInjectorGain === MUTED_VALUE_DB) { savedLocalInjectorGain = 0; } - Audio.setLocalInjectorGain(savedLocalInjectorGain); - if (savedSystemInjectorGain === -60) { + Audio.localInjectorGain = savedLocalInjectorGain; + if (savedSystemInjectorGain === MUTED_VALUE_DB) { savedSystemInjectorGain = 0; } - Audio.setSystemInjectorGain(savedSystemInjectorGain); + Audio.systemInjectorGain = savedSystemInjectorGain; + } +} + + +var WAIT_FOR_TOP_BAR_MS = 1000; +function sendLocalStatusToQml() { + var currentStatus = si.getLocalStatus(); + + if (topBarWindow && currentStatus) { + topBarWindow.sendToQml({ + "source": "simplifiedUI.js", + "method": "updateStatusButton", + "data": { + "currentStatus": currentStatus + } + }); + } else { + Script.setTimeout(sendLocalStatusToQml, WAIT_FOR_TOP_BAR_MS); } } @@ -295,6 +312,10 @@ function onMessageFromTopBar(message) { setOutputMuted(message.data.outputMuted); break; + case "toggleStatus": + si.toggleStatus(); + break; + default: console.log("Unrecognized message from " + TOP_BAR_MESSAGE_SOURCE + ": " + JSON.stringify(message)); break; @@ -312,7 +333,10 @@ function onTopBarClosed() { function isOutputMuted() { - return Audio.getAvatarGain() === -60 && Audio.getInjectorGain() === -60 && Audio.getLocalInjectorGain() === -60 && Audio.getSystemInjectorGain() === -60; + return Audio.avatarGain === MUTED_VALUE_DB && + Audio.serverInjectorGain === MUTED_VALUE_DB && + Audio.localInjectorGain === MUTED_VALUE_DB && + Audio.systemInjectorGain === MUTED_VALUE_DB; } @@ -346,13 +370,11 @@ function loadSimplifiedTopBar() { topBarWindow.fromQml.connect(onMessageFromTopBar); topBarWindow.closed.connect(onTopBarClosed); - topBarWindow.sendToQml({ - "source": "simplifiedUI.js", - "method": "updateOutputMuted", - "data": { - "outputMuted": isOutputMuted() - } - }) + // The eventbridge takes a nonzero time to initialize, so we have to wait a bit + // for the QML to load and for that to happen before updating the UI. + Script.setTimeout(function() { + sendLocalStatusToQml(); + }, WAIT_FOR_TOP_BAR_MS); } @@ -435,7 +457,20 @@ function ensureFirstPersonCameraInHMD(isHMDMode) { } } -var simplifiedNametag = Script.require("./simplifiedNametag/simplifiedNametag.js"); + +function onStatusChanged() { + sendLocalStatusToQml(); +} + + +function maybeUpdateOutputDeviceMutedOverlay() { + updateOutputDeviceMutedOverlay(isOutputMuted()); +} + + +var simplifiedNametag = Script.require("./simplifiedNametag/simplifiedNametag.js?" + Date.now()); +var SimplifiedStatusIndicator = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js?" + Date.now()); +var si; var oldShowAudioTools; var oldShowBubbleTools; var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false); @@ -456,11 +491,19 @@ function startup() { loadSimplifiedTopBar(); simplifiedNametag.create(); + si = new SimplifiedStatusIndicator({ + statusChanged: onStatusChanged + }); + si.startup(); updateInputDeviceMutedOverlay(Audio.muted); updateOutputDeviceMutedOverlay(isOutputMuted()); Audio.mutedDesktopChanged.connect(onDesktopInputDeviceMutedChanged); Window.geometryChanged.connect(onGeometryChanged); HMD.displayModeChanged.connect(ensureFirstPersonCameraInHMD); + Audio.avatarGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); + Audio.localInjectorGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); + Audio.serverInjectorGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); + Audio.systemInjectorGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); oldShowAudioTools = AvatarInputs.showAudioTools; AvatarInputs.showAudioTools = false; @@ -506,10 +549,15 @@ function shutdown() { maybeDeleteOutputDeviceMutedOverlay(); simplifiedNametag.destroy(); + si.unload(); Audio.mutedDesktopChanged.disconnect(onDesktopInputDeviceMutedChanged); Window.geometryChanged.disconnect(onGeometryChanged); HMD.displayModeChanged.disconnect(ensureFirstPersonCameraInHMD); + Audio.avatarGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); + Audio.localInjectorGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); + Audio.serverInjectorGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); + Audio.systemInjectorGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); AvatarInputs.showAudioTools = oldShowAudioTools; AvatarInputs.showBubbleTools = oldShowBubbleTools;