diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c10a616818..d2bfdde7ea 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -37,7 +37,6 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; // FIXME - what we'd actually like to do is send to users at ~50% of their present rate down to 30hz. Assume 90 for now. const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; -const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000; AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message) diff --git a/interface/resources/icons/tablet-icons/scope-auto.svg b/interface/resources/icons/tablet-icons/scope-auto.svg new file mode 100644 index 0000000000..85ef3f0e38 --- /dev/null +++ b/interface/resources/icons/tablet-icons/scope-auto.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/scope-pause.svg b/interface/resources/icons/tablet-icons/scope-pause.svg new file mode 100644 index 0000000000..3fe74fcc9f --- /dev/null +++ b/interface/resources/icons/tablet-icons/scope-pause.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/tablet-icons/scope-play.svg b/interface/resources/icons/tablet-icons/scope-play.svg new file mode 100644 index 0000000000..56d90ef38a --- /dev/null +++ b/interface/resources/icons/tablet-icons/scope-play.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 58d589b667..564c74b526 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -181,6 +181,31 @@ Item { root.avatarMixerOutPps + "pps, " + root.myAvatarSendRate.toFixed(2) + "hz"; } + StatText { + visible: root.expanded; + text: "Audio Mixer In: " + root.audioMixerInKbps + " kbps, " + + root.audioMixerInPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " + + "Silent: " + root.audioSilentInboundPPS + " pps"; + } + StatText { + visible: root.expanded; + text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " + + root.audioMixerOutPps + "pps"; + } + StatText { + visible: root.expanded; + text: "Audio Out Mic: " + root.audioMicOutboundPPS + " pps, " + + "Silent: " + root.audioSilentOutboundPPS + " pps"; + } + StatText { + visible: root.expanded; + text: "Audio Codec: " + root.audioCodec + " Noise Gate: " + + root.audioNoiseGate; + } StatText { visible: root.expanded; text: "Downloads: " + root.downloads + "/" + root.downloadLimit + diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml new file mode 100644 index 0000000000..35ee58be0c --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -0,0 +1,244 @@ +// +// TabletEntityStatistics.qml +// +// Created by Vlad Stelmahovsky on 3/11/17 +// Copyright 2017 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../styles-uit" +import "../../controls-uit" as HifiControls +import "../../windows" + +Rectangle { + id: root + objectName: "EntityStatistics" + + property var eventBridge; + signal sendToScript(var message); + property bool isHMD: false + + color: hifi.colors.baseGray + + property int colorScheme: hifi.colorSchemes.dark + + HifiConstants { id: hifi } + + Component.onCompleted: { + OctreeStats.startUpdates() + } + + Component.onDestruction: { + OctreeStats.stopUpdates() + } + + Flickable { + id: scrollView + width: parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.bottomMargin: hifi.dimensions.tabletMenuHeader + contentWidth: column.implicitWidth + contentHeight: column.implicitHeight + boundsBehavior: Flickable.StopAtBounds + + Column { + id: column + anchors.margins: 10 + anchors.left: parent.left + anchors.right: parent.right + y: hifi.dimensions.tabletMenuHeader //-bgNavBar + spacing: 20 + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Elements on Servers:") + text: OctreeStats.serverElements + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Local Elements:") + text: OctreeStats.localElements + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Elements Memory:") + text: OctreeStats.localElementsMemory + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Sending Mode:") + text: OctreeStats.sendingMode + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Incoming Entity Packets:") + text: OctreeStats.processedPackets + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Processed Packets Elements:") + text: OctreeStats.processedPacketsElements + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Processed Packets Entities:") + text: OctreeStats.processedPacketsEntities + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Processed Packets Timing:") + text: OctreeStats.processedPacketsTiming + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Outbound Entity Packets:") + text: OctreeStats.outboundEditPackets + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Entity Update Time:") + text: OctreeStats.entityUpdateTime + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + TabletEntityStatisticsItem { + anchors.left: parent.left + anchors.right: parent.right + titleText: qsTr("Entity Updates:") + text: OctreeStats.entityUpdates + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + Repeater { + model: OctreeStats.serversNum + + delegate: Column { + id: serverColumn + width: scrollView.width - 10 + x: 5 + spacing: 5 + + state: "less" + + TabletEntityStatisticsItem { + id: serverStats + width: parent.width + titleText: qsTr("Entity Server ") + (index+1) + ":" + colorScheme: root.colorScheme + color: OctreeStats.getColor() + } + + Row { + id: buttonsRow + width: parent.width + height: 30 + spacing: 10 + + HifiControls.Button { + id: moreButton + color: hifi.buttons.blue + colorScheme: root.colorScheme + width: parent.width / 2 - 10 + height: 30 + onClicked: { + if (serverColumn.state === "less") { + serverColumn.state = "more" + } else if (serverColumn.state === "more") { + serverColumn.state = "most" + } else { + serverColumn.state = "more" + } + } + } + HifiControls.Button { + id: mostButton + color: hifi.buttons.blue + colorScheme: root.colorScheme + height: 30 + width: parent.width / 2 - 10 + onClicked: { + if (serverColumn.state === "less") { + serverColumn.state = "most" + } else if (serverColumn.state === "more") { + serverColumn.state = "less" + } else { + serverColumn.state = "less" + } + } + + } + } + states: [ + State { + name: "less" + PropertyChanges { target: moreButton; text: qsTr("more..."); } + PropertyChanges { target: mostButton; text: qsTr("most..."); } + PropertyChanges { target: serverStats; text: OctreeStats.servers[index*3]; } + }, + State { + name: "more" + PropertyChanges { target: moreButton; text: qsTr("most..."); } + PropertyChanges { target: mostButton; text: qsTr("less..."); } + PropertyChanges { target: serverStats; text: OctreeStats.servers[index*3] + + OctreeStats.servers[index*3 + 1]; } + }, + State { + name: "most" + PropertyChanges { target: moreButton; text: qsTr("less..."); } + PropertyChanges { target: mostButton; text: qsTr("least..."); } + PropertyChanges { target: serverStats; text: OctreeStats.servers[index*3] + + OctreeStats.servers[index*3 + 1] + + OctreeStats.servers[index*3 + 2]; } + } + ] + } //servers column + } //repeater + } //column + } //flickable +} diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml new file mode 100644 index 0000000000..894a4c1813 --- /dev/null +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatisticsItem.qml @@ -0,0 +1,49 @@ +// +// TabletEntityStatistics.qml +// +// Created by Vlad Stelmahovsky on 3/11/17 +// Copyright 2017 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../styles-uit" +import "../../controls-uit" as HifiControls + +Column { + id: root + property int colorScheme: hifi.colorSchemes.dark + + property alias titleText: titleLabel.text + property alias text: valueLabel.text + property alias color: valueLabel.color + + HifiConstants { id: hifi } + + anchors.left: parent.left + anchors.right: parent.right + spacing: 10 + + HifiControls.Label { + id: titleLabel + size: 20 + anchors.left: parent.left + anchors.right: parent.right + colorScheme: root.colorScheme + } + + RalewaySemiBold { + id: valueLabel + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + size: 16 + color: enabled ? (root.colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGray : hifi.colors.lightGrayText) + : (root.colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight); + } +} diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index b4a1ab01b6..dee0d0e21f 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -79,266 +79,297 @@ Rectangle { scripts.stopAllScripts(); } - Column { + Flickable { + id: flickable width: parent.width - HifiControls.TabletContentSection { - name: "Currently Running" - isFirst: true + height: parent.height - (keyboard.raised ? keyboard.raisedHeight : 0) + contentWidth: parent.width + contentHeight: column.childrenRect.height + clip: true - HifiControls.VerticalSpacer {} + Column { + id: column + width: parent.width + HifiControls.TabletContentSection { + id: firstSection + name: "Currently Running" + isFirst: true - Row { - spacing: hifi.dimensions.contentSpacing.x + HifiControls.VerticalSpacer {} - HifiControls.Button { - text: "Reload All" - color: hifi.buttons.black - onClicked: reloadAll() - } + Row { + spacing: hifi.dimensions.contentSpacing.x - HifiControls.Button { - text: "Remove All" - color: hifi.buttons.red - onClicked: stopAll() - } - } - - HifiControls.VerticalSpacer { - height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border - } - - HifiControls.Table { - model: runningScriptsModel - id: table - height: 185 - colorScheme: hifi.colorSchemes.dark - anchors.left: parent.left - anchors.right: parent.right - expandSelectedRow: true - - itemDelegate: Item { - anchors { - left: parent ? parent.left : undefined - leftMargin: hifi.dimensions.tablePadding - right: parent ? parent.right : undefined - rightMargin: hifi.dimensions.tablePadding + HifiControls.Button { + text: "Reload All" + color: hifi.buttons.black + onClicked: reloadAll() } - FiraSansSemiBold { - id: textItem - text: styleData.value - size: hifi.fontSizes.tableText - color: table.colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + HifiControls.Button { + text: "Remove All" + color: hifi.buttons.red + onClicked: stopAll() + } + } + + HifiControls.VerticalSpacer { + height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border + } + + HifiControls.Table { + model: runningScriptsModel + id: table + height: 185 + colorScheme: hifi.colorSchemes.dark + anchors.left: parent.left + anchors.right: parent.right + expandSelectedRow: true + + itemDelegate: Item { anchors { - left: parent.left - right: parent.right - top: parent.top - topMargin: 3 + left: parent ? parent.left : undefined + leftMargin: hifi.dimensions.tablePadding + right: parent ? parent.right : undefined + rightMargin: hifi.dimensions.tablePadding } - HiFiGlyphs { - id: reloadButton - text: hifi.glyphs.reloadSmall - color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + FiraSansSemiBold { + id: textItem + text: styleData.value + size: hifi.fontSizes.tableText + color: table.colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) anchors { - top: parent.top - right: stopButton.left - verticalCenter: parent.verticalCenter - } - MouseArea { - id: reloadButtonArea - anchors { fill: parent; margins: -2 } - onClicked: reloadScript(model.url) - } - } - - HiFiGlyphs { - id: stopButton - text: hifi.glyphs.closeSmall - color: stopButtonArea.pressed ? hifi.colors.white : parent.color - anchors { - top: parent.top + left: parent.left right: parent.right - verticalCenter: parent.verticalCenter + top: parent.top + topMargin: 3 } - MouseArea { - id: stopButtonArea - anchors { fill: parent; margins: -2 } - onClicked: stopScript(model.url) + + HiFiGlyphs { + id: reloadButton + text: hifi.glyphs.reloadSmall + color: reloadButtonArea.pressed ? hifi.colors.white : parent.color + anchors { + top: parent.top + right: stopButton.left + verticalCenter: parent.verticalCenter + } + MouseArea { + id: reloadButtonArea + anchors { fill: parent; margins: -2 } + onClicked: reloadScript(model.url) + } + } + + HiFiGlyphs { + id: stopButton + text: hifi.glyphs.closeSmall + color: stopButtonArea.pressed ? hifi.colors.white : parent.color + anchors { + top: parent.top + right: parent.right + verticalCenter: parent.verticalCenter + } + MouseArea { + id: stopButtonArea + anchors { fill: parent; margins: -2 } + onClicked: stopScript(model.url) + } + } + + } + + FiraSansSemiBold { + text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : "" + elide: Text.ElideMiddle + size: hifi.fontSizes.tableText + color: table.colorScheme == hifi.colorSchemes.light + ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + anchors { + top: textItem.bottom + left: parent.left + right: parent.right + } + visible: styleData.selected + } + } + + TableViewColumn { + role: "name" + } + } + + HifiControls.VerticalSpacer { + height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border + } + } + + HifiControls.TabletContentSection { + name: "Load Scripts" + + HifiControls.VerticalSpacer {} + + Row { + spacing: hifi.dimensions.contentSpacing.x + + HifiControls.Button { + text: "from URL" + color: hifi.buttons.black + height: 26 + onClicked: fromUrlTimer.running = true + + // For some reason trigginer an API that enters + // an internal event loop directly from the button clicked + // trigger below causes the appliction to behave oddly. + // Most likely because the button onClicked handling is never + // completed until the function returns. + // FIXME find a better way of handling the input dialogs that + // doesn't trigger this. + Timer { + id: fromUrlTimer + interval: 5 + repeat: false + running: false + onTriggered: ApplicationInterface.loadScriptURLDialog(); + } + } + + HifiControls.Button { + text: "from Disk" + color: hifi.buttons.black + height: 26 + onClicked: fromDiskTimer.running = true + + Timer { + id: fromDiskTimer + interval: 5 + repeat: false + running: false + onTriggered: ApplicationInterface.loadDialog(); + } + } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + height: 26 + onClicked: loadDefaults() + } + } + + HifiControls.VerticalSpacer {} + + HifiControls.TextField { + id: filterEdit + isSearchField: true + anchors.left: parent.left + anchors.right: parent.right + colorScheme: hifi.colorSchemes.dark + placeholderText: "Filter" + onTextChanged: scriptsModel.filterRegExp = new RegExp("^.*" + text + ".*$", "i") + Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i") + onActiveFocusChanged: { + // raise the keyboard + keyboard.raised = activeFocus; + + // scroll to the bottom of the content area. + if (activeFocus) { + flickable.contentY = (flickable.contentHeight - flickable.height); + } + } + } + + HifiControls.VerticalSpacer { + height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border + } + + HifiControls.Tree { + id: treeView + height: 155 + treeModel: scriptsModel + colorScheme: hifi.colorSchemes.dark + anchors.left: parent.left + anchors.right: parent.right + } + + HifiControls.VerticalSpacer { + height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border + } + + HifiControls.TextField { + id: selectedScript + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: loadButton.width + hifi.dimensions.contentSpacing.x + + colorScheme: hifi.colorSchemes.dark + readOnly: true + + Connections { + target: treeView + onCurrentIndexChanged: { + var path = scriptsModel.data(treeView.currentIndex, 0x100) + if (path) { + selectedScript.text = path + } else { + selectedScript.text = "" } } - - } - - FiraSansSemiBold { - text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : "" - elide: Text.ElideMiddle - size: hifi.fontSizes.tableText - color: table.colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - anchors { - top: textItem.bottom - left: parent.left - right: parent.right - } - visible: styleData.selected } } - TableViewColumn { - role: "name" - } - } - - HifiControls.VerticalSpacer { - height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border - } - } - - HifiControls.TabletContentSection { - name: "Load Scripts" - - HifiControls.VerticalSpacer {} - - Row { - spacing: hifi.dimensions.contentSpacing.x - - HifiControls.Button { - text: "from URL" - color: hifi.buttons.black - height: 26 - onClicked: fromUrlTimer.running = true - - // For some reason trigginer an API that enters - // an internal event loop directly from the button clicked - // trigger below causes the appliction to behave oddly. - // Most likely because the button onClicked handling is never - // completed until the function returns. - // FIXME find a better way of handling the input dialogs that - // doesn't trigger this. - Timer { - id: fromUrlTimer - interval: 5 - repeat: false - running: false - onTriggered: ApplicationInterface.loadScriptURLDialog(); - } - } - - HifiControls.Button { - text: "from Disk" - color: hifi.buttons.black - height: 26 - onClicked: fromDiskTimer.running = true - - Timer { - id: fromDiskTimer - interval: 5 - repeat: false - running: false - onTriggered: ApplicationInterface.loadDialog(); - } - } - - HifiControls.Button { - text: "Load Defaults" - color: hifi.buttons.black - height: 26 - onClicked: loadDefaults() - } - } - - HifiControls.VerticalSpacer {} - - HifiControls.TextField { - id: filterEdit - isSearchField: true - anchors.left: parent.left - anchors.right: parent.right - colorScheme: hifi.colorSchemes.dark - placeholderText: "Filter" - onTextChanged: scriptsModel.filterRegExp = new RegExp("^.*" + text + ".*$", "i") - Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i") - } - - HifiControls.VerticalSpacer { - height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border - } - - HifiControls.Tree { - id: treeView - height: 155 - treeModel: scriptsModel - colorScheme: hifi.colorSchemes.dark - anchors.left: parent.left - anchors.right: parent.right - } - - HifiControls.VerticalSpacer { - height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border - } - - HifiControls.TextField { - id: selectedScript - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: loadButton.width + hifi.dimensions.contentSpacing.x - - colorScheme: hifi.colorSchemes.dark - readOnly: true - - Connections { - target: treeView - onCurrentIndexChanged: { - var path = scriptsModel.data(treeView.currentIndex, 0x100) - if (path) { - selectedScript.text = path - } else { - selectedScript.text = "" - } - } - } - } - - Item { - // Take the loadButton out of the column flow. - id: loadButtonContainer - anchors.top: selectedScript.top - anchors.right: parent.right - - HifiControls.Button { - id: loadButton + Item { + // Take the loadButton out of the column flow. + id: loadButtonContainer + anchors.top: selectedScript.top anchors.right: parent.right - text: "Load" - color: hifi.buttons.blue - enabled: selectedScript.text != "" - onClicked: root.loadScript(selectedScript.text) + HifiControls.Button { + id: loadButton + anchors.right: parent.right + + text: "Load" + color: hifi.buttons.blue + enabled: selectedScript.text != "" + onClicked: root.loadScript(selectedScript.text) + } + } + + HifiControls.VerticalSpacer { + height: hifi.dimensions.controlInterlineHeight - (!isHMD ? 3 : 0) + } + + HifiControls.TextAction { + id: directoryButton + icon: hifi.glyphs.script + iconSize: 24 + text: "Reveal Scripts Folder" + onClicked: fileDialogHelper.openDirectory(scripts.defaultScriptsPath) + colorScheme: hifi.colorSchemes.dark + anchors.left: parent.left + visible: !isHMD + } + + HifiControls.VerticalSpacer { + height: hifi.dimensions.controlInterlineHeight - 3 + visible: !isHMD } } + } + } - HifiControls.VerticalSpacer { - height: hifi.dimensions.controlInterlineHeight - (!isHMD ? 3 : 0) - } - - HifiControls.TextAction { - id: directoryButton - icon: hifi.glyphs.script - iconSize: 24 - text: "Reveal Scripts Folder" - onClicked: fileDialogHelper.openDirectory(scripts.defaultScriptsPath) - colorScheme: hifi.colorSchemes.dark - anchors.left: parent.left - visible: !isHMD - } - - HifiControls.VerticalSpacer { - height: hifi.dimensions.controlInterlineHeight - 3 - visible: !isHMD - } + HifiControls.Keyboard { + id: keyboard + raised: false + numeric: false + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right } } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index f77ad0d4b8..bacc11228e 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -17,7 +17,7 @@ Item { id: root anchors.fill: parent objectName: "tabletMenuHandlerItem" - + StackView { anchors.fill: parent id: d @@ -54,6 +54,10 @@ Item { d.currentItem.focus = true; d.currentItem.forceActiveFocus(); breadcrumbText.text = d.currentItem.title; + if (typeof bgNavBar !== "undefined") { + d.currentItem.y = bgNavBar.height; + d.currentItem.height -= bgNavBar.height; + } } function popSource() { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 141b168f82..040ee46bb8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -173,6 +173,7 @@ #include "ui/overlays/Overlays.h" #include "Util.h" #include "InterfaceParentFinder.h" +#include "ui/OctreeStatsProvider.h" #include "FrameTimingsScriptingInterface.h" #include @@ -523,6 +524,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); return previousSessionCrashed; } @@ -550,7 +552,7 @@ const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f; const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true; const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false; const bool DEFAULT_TABLET_VISIBLE_TO_OTHERS = false; -const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false; +const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = true; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), @@ -1188,6 +1190,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // set the local loopback interface for local sounds AudioInjector::setLocalAudioInterface(audioIO.data()); AudioScriptingInterface::getInstance().setLocalAudioInterface(audioIO.data()); + connect(audioIO.data(), &AudioClient::noiseGateOpened, &AudioScriptingInterface::getInstance(), &AudioScriptingInterface::noiseGateOpened); + connect(audioIO.data(), &AudioClient::noiseGateClosed, &AudioScriptingInterface::getInstance(), &AudioScriptingInterface::noiseGateClosed); + connect(audioIO.data(), &AudioClient::inputReceived, &AudioScriptingInterface::getInstance(), &AudioScriptingInterface::inputReceived); + this->installEventFilter(this); @@ -1625,9 +1631,10 @@ QString Application::getUserAgent() { void Application::toggleTabletUI() const { auto tabletScriptingInterface = DependencyManager::get(); + auto hmd = DependencyManager::get(); TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); bool messageOpen = tablet->isMessageDialogOpen(); - if (!messageOpen) { + if (!messageOpen || (messageOpen && !hmd->getShouldShowTablet())) { auto HMD = DependencyManager::get(); HMD->toggleShouldShowTablet(); } @@ -1803,6 +1810,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); ResourceManager::cleanup(); @@ -1950,6 +1958,8 @@ void Application::initializeUi() { rootContext->setContextProperty("ApplicationInterface", this); rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); rootContext->setContextProperty("AudioStats", DependencyManager::get()->getStats().data()); + rootContext->setContextProperty("AudioScope", DependencyManager::get().data()); + rootContext->setContextProperty("Controller", DependencyManager::get().data()); rootContext->setContextProperty("Entities", DependencyManager::get().data()); _fileDownload = new FileScriptingInterface(engine); @@ -3178,7 +3188,23 @@ void Application::mousePressEvent(QMouseEvent* event) { } } -void Application::mouseDoublePressEvent(QMouseEvent* event) const { +void Application::mouseDoublePressEvent(QMouseEvent* event) { + auto offscreenUi = DependencyManager::get(); + auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); + QMouseEvent mappedEvent(event->type(), + transformedPos, + event->screenPos(), event->button(), + event->buttons(), event->modifiers()); + + if (!_aboutToQuit) { + getOverlays().mouseDoublePressEvent(&mappedEvent); + if (!_controllerScriptingInterface->areEntityClicksCaptured()) { + getEntities()->mouseDoublePressEvent(&mappedEvent); + } + } + + // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface->isMouseCaptured()) { return; @@ -5525,6 +5551,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get()->getStats().data()); + scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get().data()); // Caches scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); @@ -6375,6 +6402,18 @@ void Application::loadLODToolsDialog() { } + +void Application::loadEntityStatisticsDialog() { + auto tabletScriptingInterface = DependencyManager::get(); + auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); + if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !isHMDMode())) { + auto dialogsManager = DependencyManager::get(); + dialogsManager->octreeStatsDetails(); + } else { + tablet->pushOntoStack("../../hifi/dialogs/TabletEntityStatistics.qml"); + } + +} void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(nullptr, getLogger()); diff --git a/interface/src/Application.h b/interface/src/Application.h index 5c72f0fa90..2ed54a05d8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -404,6 +404,7 @@ public slots: Q_INVOKABLE void toggleMuteAudio(); void loadLODToolsDialog(); + void loadEntityStatisticsDialog(); private slots: void showDesktop(); @@ -497,7 +498,7 @@ private: void mouseMoveEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event); - void mouseDoublePressEvent(QMouseEvent* event) const; + void mouseDoublePressEvent(QMouseEvent* event); void mouseReleaseEvent(QMouseEvent* event); void touchBeginEvent(QTouchEvent* event); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 1d5e7c9448..2ae66af14b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -547,8 +547,10 @@ Menu::Menu() { // Developer > Entities >>> MenuWrapper* entitiesOptionsMenu = developerMenu->addMenu("Entities"); + addActionToQMenuAndActionHash(entitiesOptionsMenu, MenuOption::OctreeStats, 0, - dialogsManager.data(), SLOT(octreeStatsDetails())); + qApp, SLOT(loadEntityStatisticsDialog())); + addCheckableActionToQMenuAndActionHash(entitiesOptionsMenu, MenuOption::ShowRealtimeEntityStats); // Developer > Network >>> diff --git a/interface/src/audio/AudioScope.cpp b/interface/src/audio/AudioScope.cpp index 346fbd11f4..cf9984e32b 100644 --- a/interface/src/audio/AudioScope.cpp +++ b/interface/src/audio/AudioScope.cpp @@ -52,12 +52,14 @@ AudioScope::AudioScope() : connect(audioIO.data(), &AudioClient::inputReceived, this, &AudioScope::addInputToScope); } -void AudioScope::toggle() { - _isEnabled = !_isEnabled; - if (_isEnabled) { - allocateScope(); - } else { - freeScope(); +void AudioScope::setVisible(bool visible) { + if (_isEnabled != visible) { + _isEnabled = visible; + if (_isEnabled) { + allocateScope(); + } else { + freeScope(); + } } } diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index 0b716d7666..615bdaf17f 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -34,8 +34,14 @@ public: void render(RenderArgs* renderArgs, int width, int height); public slots: - void toggle(); + void toggle() { setVisible(!_isEnabled); } + void setVisible(bool visible); + bool getVisible() const { return _isEnabled; } + void togglePause() { _isPaused = !_isPaused; } + void setPause(bool paused) { _isPaused = paused; } + bool getPause() { return _isPaused; } + void selectAudioScopeFiveFrames(); void selectAudioScopeTwentyFrames(); void selectAudioScopeFiftyFrames(); @@ -74,7 +80,6 @@ private: int _inputID; int _outputLeftID; int _outputRightD; - }; #endif // hifi_AudioScope_h diff --git a/interface/src/ui/OctreeStatsProvider.cpp b/interface/src/ui/OctreeStatsProvider.cpp new file mode 100644 index 0000000000..5f40b9916d --- /dev/null +++ b/interface/src/ui/OctreeStatsProvider.cpp @@ -0,0 +1,377 @@ +// +// OctreeStatsProvider.cpp +// interface/src/ui +// +// Created by Vlad Stelmahovsky on 3/12/17. +// Copyright 2013 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 +// + +#include "Application.h" + +#include "../octree/OctreePacketProcessor.h" +#include "ui/OctreeStatsProvider.h" + +OctreeStatsProvider::OctreeStatsProvider(QObject* parent, NodeToOctreeSceneStats* model) : + QObject(parent), + _model(model) + , _statCount(0) + , _averageUpdatesPerSecond(SAMPLES_PER_SECOND) +{ + //schedule updates + connect(&_updateTimer, &QTimer::timeout, this, &OctreeStatsProvider::updateOctreeStatsData); + _updateTimer.setInterval(100); + //timer will be rescheduled on each new timeout + _updateTimer.setSingleShot(true); +} + +/* + * Start updates statistics +*/ +void OctreeStatsProvider::startUpdates() { + _updateTimer.start(); +} + +/* + * Stop updates statistics +*/ +void OctreeStatsProvider::stopUpdates() { + _updateTimer.stop(); +} + +QColor OctreeStatsProvider::getColor() const { + static int statIndex = 1; + static quint32 rotatingColors[] = { GREENISH, YELLOWISH, GREYISH }; + quint32 colorRGBA = rotatingColors[statIndex % (sizeof(rotatingColors)/sizeof(rotatingColors[0]))]; + quint32 rgb = colorRGBA >> 8; + const quint32 colorpart1 = 0xfefefeu; + const quint32 colorpart2 = 0xf8f8f8; + rgb = ((rgb & colorpart1) >> 1) + ((rgb & colorpart2) >> 3); + statIndex++; + return QColor::fromRgb(rgb); +} + +OctreeStatsProvider::~OctreeStatsProvider() { + _updateTimer.stop(); +} + +int OctreeStatsProvider::serversNum() const { + return m_serversNum; +} + +void OctreeStatsProvider::updateOctreeStatsData() { + + // Processed Entities Related stats + auto entities = qApp->getEntities(); + auto entitiesTree = entities->getTree(); + + // Do this ever paint event... even if we don't update + auto totalTrackedEdits = entitiesTree->getTotalTrackedEdits(); + + const quint64 SAMPLING_WINDOW = USECS_PER_SECOND / SAMPLES_PER_SECOND; + quint64 now = usecTimestampNow(); + quint64 sinceLastWindow = now - _lastWindowAt; + auto editsInLastWindow = totalTrackedEdits - _lastKnownTrackedEdits; + float sinceLastWindowInSeconds = (float)sinceLastWindow / (float)USECS_PER_SECOND; + float recentUpdatesPerSecond = (float)editsInLastWindow / sinceLastWindowInSeconds; + if (sinceLastWindow > SAMPLING_WINDOW) { + _averageUpdatesPerSecond.updateAverage(recentUpdatesPerSecond); + _lastWindowAt = now; + _lastKnownTrackedEdits = totalTrackedEdits; + } + + // Only refresh our stats every once in a while, unless asked for realtime + quint64 REFRESH_AFTER = Menu::getInstance()->isOptionChecked(MenuOption::ShowRealtimeEntityStats) ? 0 : USECS_PER_SECOND; + quint64 sinceLastRefresh = now - _lastRefresh; + if (sinceLastRefresh < REFRESH_AFTER) { + _updateTimer.start((REFRESH_AFTER - sinceLastRefresh)/1000); + return; + } + // Only refresh our stats every once in a while, unless asked for realtime + //if no realtime, then update once per second. Otherwise consider 60FPS update, ie 16ms interval + //int updateinterval = Menu::getInstance()->isOptionChecked(MenuOption::ShowRealtimeEntityStats) ? 16 : 1000; + _updateTimer.start(REFRESH_AFTER/1000); + + const int FLOATING_POINT_PRECISION = 3; + + m_localElementsMemory = QString("Elements RAM: %1MB").arg(OctreeElement::getTotalMemoryUsage() / 1000000.0f, 5, 'f', 4); + emit localElementsMemoryChanged(m_localElementsMemory); + + // Local Elements + m_localElements = QString("Total: %1 / Internal: %2 / Leaves: %3"). + arg(OctreeElement::getNodeCount()). + arg(OctreeElement::getInternalNodeCount()). + arg(OctreeElement::getLeafNodeCount()); + emit localElementsChanged(m_localElements); + + // iterate all the current octree stats, and list their sending modes, total their octree elements, etc... + int serverCount = 0; + int movingServerCount = 0; + unsigned long totalNodes = 0; + unsigned long totalInternal = 0; + unsigned long totalLeaves = 0; + + m_sendingMode.clear(); + NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); + sceneStats->withReadLock([&] { + for (NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) { + //const QUuid& uuid = i->first; + OctreeSceneStats& stats = i->second; + serverCount++; + + // calculate server node totals + totalNodes += stats.getTotalElements(); + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + + // Sending mode + if (serverCount > 1) { + m_sendingMode += ","; + } + if (stats.isMoving()) { + m_sendingMode += "M"; + movingServerCount++; + } else { + m_sendingMode += "S"; + } + if (stats.isFullScene()) { + m_sendingMode += "F"; + } else { + m_sendingMode += "p"; + } + } + }); + m_sendingMode += QString(" - %1 servers").arg(serverCount); + if (movingServerCount > 0) { + m_sendingMode += " "; + } else { + m_sendingMode += " "; + } + + emit sendingModeChanged(m_sendingMode); + + // Server Elements + m_serverElements = QString("Total: %1 / Internal: %2 / Leaves: %3"). + arg(totalNodes).arg(totalInternal).arg(totalLeaves); + emit serverElementsChanged(m_serverElements); + + + // Processed Packets Elements + auto averageElementsPerPacket = entities->getAverageElementsPerPacket(); + auto averageEntitiesPerPacket = entities->getAverageEntitiesPerPacket(); + + auto averageElementsPerSecond = entities->getAverageElementsPerSecond(); + auto averageEntitiesPerSecond = entities->getAverageEntitiesPerSecond(); + + auto averageWaitLockPerPacket = entities->getAverageWaitLockPerPacket(); + auto averageUncompressPerPacket = entities->getAverageUncompressPerPacket(); + auto averageReadBitstreamPerPacket = entities->getAverageReadBitstreamPerPacket(); + + const OctreePacketProcessor& entitiesPacketProcessor = qApp->getOctreePacketProcessor(); + + auto incomingPacketsDepth = entitiesPacketProcessor.packetsToProcessCount(); + auto incomingPPS = entitiesPacketProcessor.getIncomingPPS(); + auto processedPPS = entitiesPacketProcessor.getProcessedPPS(); + auto treeProcessedPPS = entities->getAveragePacketsPerSecond(); + + m_processedPackets = QString("Queue Size: %1 Packets / Network IN: %2 PPS / Queue OUT: %3 PPS / Tree IN: %4 PPS") + .arg(incomingPacketsDepth) + .arg(incomingPPS, 5, 'f', FLOATING_POINT_PRECISION) + .arg(processedPPS, 5, 'f', FLOATING_POINT_PRECISION) + .arg(treeProcessedPPS, 5, 'f', FLOATING_POINT_PRECISION); + emit processedPacketsChanged(m_processedPackets); + + m_processedPacketsElements = QString("%1 per packet / %2 per second") + .arg(averageElementsPerPacket, 5, 'f', FLOATING_POINT_PRECISION) + .arg(averageElementsPerSecond, 5, 'f', FLOATING_POINT_PRECISION); + emit processedPacketsElementsChanged(m_processedPacketsElements); + + m_processedPacketsEntities = QString("%1 per packet / %2 per second") + .arg(averageEntitiesPerPacket, 5, 'f', FLOATING_POINT_PRECISION) + .arg(averageEntitiesPerSecond, 5, 'f', FLOATING_POINT_PRECISION); + emit processedPacketsEntitiesChanged(m_processedPacketsEntities); + + m_processedPacketsTiming = QString("Lock Wait: %1 (usecs) / Uncompress: %2 (usecs) / Process: %3 (usecs)") + .arg(averageWaitLockPerPacket) + .arg(averageUncompressPerPacket) + .arg(averageReadBitstreamPerPacket); + emit processedPacketsTimingChanged(m_processedPacketsTiming); + + auto entitiesEditPacketSender = qApp->getEntityEditPacketSender(); + auto outboundPacketsDepth = entitiesEditPacketSender->packetsToSendCount(); + auto outboundQueuedPPS = entitiesEditPacketSender->getLifetimePPSQueued(); + auto outboundSentPPS = entitiesEditPacketSender->getLifetimePPS(); + + m_outboundEditPackets = QString("Queue Size: %1 packets / Queued IN: %2 PPS / Sent OUT: %3 PPS") + .arg(outboundPacketsDepth) + .arg(outboundQueuedPPS, 5, 'f', FLOATING_POINT_PRECISION) + .arg(outboundSentPPS, 5, 'f', FLOATING_POINT_PRECISION); + emit outboundEditPacketsChanged(m_outboundEditPackets); + + // Entity Edits update time + auto averageEditDelta = entitiesTree->getAverageEditDeltas(); + auto maxEditDelta = entitiesTree->getMaxEditDelta(); + + m_entityUpdateTime = QString("Average: %1 (usecs) / Max: %2 (usecs)") + .arg(averageEditDelta) + .arg(maxEditDelta); + emit entityUpdateTimeChanged(m_entityUpdateTime); + + // Entity Edits + auto bytesPerEdit = entitiesTree->getAverageEditBytes(); + + auto updatesPerSecond = _averageUpdatesPerSecond.getAverage(); + if (updatesPerSecond < 1) { + updatesPerSecond = 0; // we don't really care about small updates per second so suppress those + } + + m_entityUpdates = QString("%1 updates per second / %2 total updates / Average Size: %3 bytes") + .arg(updatesPerSecond, 5, 'f', FLOATING_POINT_PRECISION) + .arg(totalTrackedEdits) + .arg(bytesPerEdit); + emit entityUpdatesChanged(m_entityUpdates); + + updateOctreeServers(); +} + +void OctreeStatsProvider::updateOctreeServers() { + int serverCount = 0; + + showOctreeServersOfType(serverCount, NodeType::EntityServer, "Entity", + qApp->getEntityServerJurisdictions()); + if (m_serversNum != serverCount) { + m_serversNum = serverCount; + emit serversNumChanged(m_serversNum); + } +} + +void OctreeStatsProvider::showOctreeServersOfType(int& serverCount, NodeType_t serverType, const char* serverTypeName, + NodeToJurisdictionMap& serverJurisdictions) { + + m_servers.clear(); + + auto nodeList = DependencyManager::get(); + nodeList->eachNode([&](const SharedNodePointer& node) { + + // only send to the NodeTypes that are NodeType_t_VOXEL_SERVER + if (node->getType() == serverType) { + serverCount++; + + QString lesserDetails; + QString moreDetails; + QString mostDetails; + + if (node->getActiveSocket()) { + lesserDetails += "active "; + } else { + lesserDetails += "inactive "; + } + + QUuid nodeUUID = node->getUUID(); + + // lookup our nodeUUID in the jurisdiction map, if it's missing then we're + // missing at least one jurisdiction + serverJurisdictions.withReadLock([&] { + if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) { + lesserDetails += " unknown jurisdiction "; + return; + } + const JurisdictionMap& map = serverJurisdictions[nodeUUID]; + + auto rootCode = map.getRootOctalCode(); + + if (rootCode) { + QString rootCodeHex = octalCodeToHexString(rootCode.get()); + + VoxelPositionSize rootDetails; + voxelDetailsForCode(rootCode.get(), rootDetails); + AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); + lesserDetails += QString(" jurisdiction: %1 [%2, %3, %4: %5]") + .arg(rootCodeHex) + .arg(rootDetails.x) + .arg(rootDetails.y) + .arg(rootDetails.z) + .arg(rootDetails.s); + } else { + lesserDetails += " jurisdiction has no rootCode"; + } // root code + }); + + // now lookup stats details for this server... + NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); + sceneStats->withReadLock([&] { + if (sceneStats->find(nodeUUID) != sceneStats->end()) { + OctreeSceneStats& stats = sceneStats->at(nodeUUID); + + float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; + float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; + float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; + float lastFullPackets = stats.getLastFullTotalPackets(); + float lastFullPPS = lastFullPackets; + if (lastFullSendInSeconds > 0) { + lastFullPPS = lastFullPackets / lastFullSendInSeconds; + } + + mostDetails += QString("

Last Full Scene... Encode: %1 ms Send: %2 ms Packets: %3 Bytes: %4 Rate: %5 PPS") + .arg(lastFullEncode) + .arg(lastFullSend) + .arg(lastFullPackets) + .arg(stats.getLastFullTotalBytes()) + .arg(lastFullPPS); + + for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { + OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); + OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); + mostDetails += QString("
%1 %2") + .arg(itemInfo.caption).arg(stats.getItemValue(item)); + } + + moreDetails += "
Node UUID: " +nodeUUID.toString() + " "; + + moreDetails += QString("
Elements: %1 total %2 internal %3 leaves ") + .arg(stats.getTotalElements()) + .arg(stats.getTotalInternal()) + .arg(stats.getTotalLeaves()); + + const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); + qint64 clockSkewInUsecs = node->getClockSkewUsec(); + qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; + + moreDetails += QString("
Incoming Packets: %1/ Lost: %2/ Recovered: %3") + .arg(stats.getIncomingPackets()) + .arg(seqStats.getLost()) + .arg(seqStats.getRecovered()); + + moreDetails += QString("
Out of Order: %1/ Early: %2/ Late: %3/ Unreasonable: %4") + .arg(seqStats.getOutOfOrder()) + .arg(seqStats.getEarly()) + .arg(seqStats.getLate()) + .arg(seqStats.getUnreasonable()); + + moreDetails += QString("
Average Flight Time: %1 msecs") + .arg(stats.getIncomingFlightTimeAverage()); + + moreDetails += QString("
Average Ping Time: %1 msecs") + .arg(node->getPingMs()); + + moreDetails += QString("
Average Clock Skew: %1 msecs [%2]") + .arg(clockSkewInMS) + .arg(formatUsecTime(clockSkewInUsecs)); + + + moreDetails += QString("
Incoming Bytes: %1 Wasted Bytes: %2") + .arg(stats.getIncomingBytes()) + .arg(stats.getIncomingWastedBytes()); + } + }); + m_servers.append(lesserDetails); + m_servers.append(moreDetails); + m_servers.append(mostDetails); + } // is VOXEL_SERVER + }); + emit serversChanged(m_servers); +} + + diff --git a/interface/src/ui/OctreeStatsProvider.h b/interface/src/ui/OctreeStatsProvider.h new file mode 100644 index 0000000000..c919ca102f --- /dev/null +++ b/interface/src/ui/OctreeStatsProvider.h @@ -0,0 +1,154 @@ +// +// OctreeStatsProvider.h +// interface/src/ui +// +// Created by Vlad Stelmahovsky on 3/12/17. +// Copyright 2017 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 +// + +#ifndef hifi_OctreeStatsProvider_h +#define hifi_OctreeStatsProvider_h + +#include +#include +#include + +#include "DependencyManager.h" + +#define MAX_STATS 100 +#define MAX_VOXEL_SERVERS 50 +#define DEFAULT_COLOR 0 + +class OctreeStatsProvider : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + + Q_PROPERTY(int serversNum READ serversNum NOTIFY serversNumChanged) + Q_PROPERTY(QString serverElements READ serverElements NOTIFY serverElementsChanged) + Q_PROPERTY(QString localElements READ localElements NOTIFY localElementsChanged) + Q_PROPERTY(QString localElementsMemory READ localElementsMemory NOTIFY localElementsMemoryChanged) + Q_PROPERTY(QString sendingMode READ sendingMode NOTIFY sendingModeChanged) + Q_PROPERTY(QString processedPackets READ processedPackets NOTIFY processedPacketsChanged) + Q_PROPERTY(QString processedPacketsElements READ processedPacketsElements NOTIFY processedPacketsElementsChanged) + Q_PROPERTY(QString processedPacketsEntities READ processedPacketsEntities NOTIFY processedPacketsEntitiesChanged) + Q_PROPERTY(QString processedPacketsTiming READ processedPacketsTiming NOTIFY processedPacketsTimingChanged) + Q_PROPERTY(QString outboundEditPackets READ outboundEditPackets NOTIFY outboundEditPacketsChanged) + Q_PROPERTY(QString entityUpdateTime READ entityUpdateTime NOTIFY entityUpdateTimeChanged) + Q_PROPERTY(QString entityUpdates READ entityUpdates NOTIFY entityUpdatesChanged) + + Q_PROPERTY(QStringList servers READ servers NOTIFY serversChanged) + +public: + OctreeStatsProvider(QObject* parent, NodeToOctreeSceneStats* model); + ~OctreeStatsProvider(); + + int serversNum() const; + + QString serverElements() const { + return m_serverElements; + } + + QString localElements() const { + return m_localElements; + } + + QString localElementsMemory() const { + return m_localElementsMemory; + } + + QString sendingMode() const { + return m_sendingMode; + } + + QString processedPackets() const { + return m_processedPackets; + } + + QString processedPacketsElements() const { + return m_processedPacketsElements; + } + + QString processedPacketsEntities() const { + return m_processedPacketsEntities; + } + + QString processedPacketsTiming() const { + return m_processedPacketsTiming; + } + + QString outboundEditPackets() const { + return m_outboundEditPackets; + } + + QString entityUpdateTime() const { + return m_entityUpdateTime; + } + + QString entityUpdates() const { + return m_entityUpdates; + } + + QStringList servers() const { + return m_servers; + } + +signals: + + void serversNumChanged(int serversNum); + void serverElementsChanged(const QString &serverElements); + void localElementsChanged(const QString &localElements); + void sendingModeChanged(const QString &sendingMode); + void processedPacketsChanged(const QString &processedPackets); + void localElementsMemoryChanged(const QString &localElementsMemory); + void processedPacketsElementsChanged(const QString &processedPacketsElements); + void processedPacketsEntitiesChanged(const QString &processedPacketsEntities); + void processedPacketsTimingChanged(const QString &processedPacketsTiming); + void outboundEditPacketsChanged(const QString &outboundEditPackets); + void entityUpdateTimeChanged(const QString &entityUpdateTime); + void entityUpdatesChanged(const QString &entityUpdates); + + void serversChanged(const QStringList &servers); + +public slots: + void startUpdates(); + void stopUpdates(); + QColor getColor() const; + +private slots: + void updateOctreeStatsData(); +protected: + void updateOctreeServers(); + void showOctreeServersOfType(int& serverNumber, NodeType_t serverType, + const char* serverTypeName, NodeToJurisdictionMap& serverJurisdictions); + +private: + NodeToOctreeSceneStats* _model; + int _statCount; + + const int SAMPLES_PER_SECOND = 10; + SimpleMovingAverage _averageUpdatesPerSecond; + quint64 _lastWindowAt = usecTimestampNow(); + quint64 _lastKnownTrackedEdits = 0; + + quint64 _lastRefresh = 0; + + QTimer _updateTimer; + int m_serversNum {0}; + QString m_serverElements; + QString m_localElements; + QString m_localElementsMemory; + QString m_sendingMode; + QString m_processedPackets; + QString m_processedPacketsElements; + QString m_processedPacketsEntities; + QString m_processedPacketsTiming; + QString m_outboundEditPackets; + QString m_entityUpdateTime; + QString m_entityUpdates; + QStringList m_servers; +}; + +#endif // hifi_OctreeStatsProvider_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 1075bbdaa4..923d9f642d 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -198,15 +198,16 @@ void Stats::updateStats(bool force) { STAT_UPDATE(avatarMixerInPps, roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AvatarMixer))); STAT_UPDATE(avatarMixerOutKbps, roundf(bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AvatarMixer))); STAT_UPDATE(avatarMixerOutPps, roundf(bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AvatarMixer))); - STAT_UPDATE(myAvatarSendRate, avatarManager->getMyAvatarSendRate()); } else { STAT_UPDATE(avatarMixerInKbps, -1); STAT_UPDATE(avatarMixerInPps, -1); STAT_UPDATE(avatarMixerOutKbps, -1); STAT_UPDATE(avatarMixerOutPps, -1); - STAT_UPDATE(myAvatarSendRate, avatarManager->getMyAvatarSendRate()); } + STAT_UPDATE(myAvatarSendRate, avatarManager->getMyAvatarSendRate()); + SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); + auto audioClient = DependencyManager::get(); if (audioMixerNode || force) { STAT_UPDATE(audioMixerKbps, roundf( bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) + @@ -214,10 +215,30 @@ void Stats::updateStats(bool force) { STAT_UPDATE(audioMixerPps, roundf( bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer) + bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer))); + + STAT_UPDATE(audioMixerInKbps, roundf(bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer))); + STAT_UPDATE(audioMixerInPps, roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer))); + STAT_UPDATE(audioMixerOutKbps, roundf(bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer))); + STAT_UPDATE(audioMixerOutPps, roundf(bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer))); + STAT_UPDATE(audioMicOutboundPPS, audioClient->getMicAudioOutboundPPS()); + STAT_UPDATE(audioSilentOutboundPPS, audioClient->getSilentOutboundPPS()); + STAT_UPDATE(audioAudioInboundPPS, audioClient->getAudioInboundPPS()); + STAT_UPDATE(audioSilentInboundPPS, audioClient->getSilentInboundPPS()); } else { STAT_UPDATE(audioMixerKbps, -1); STAT_UPDATE(audioMixerPps, -1); + STAT_UPDATE(audioMixerInKbps, -1); + STAT_UPDATE(audioMixerInPps, -1); + STAT_UPDATE(audioMixerOutKbps, -1); + STAT_UPDATE(audioMixerOutPps, -1); + STAT_UPDATE(audioMicOutboundPPS, -1); + STAT_UPDATE(audioSilentOutboundPPS, -1); + STAT_UPDATE(audioAudioInboundPPS, -1); + STAT_UPDATE(audioSilentInboundPPS, -1); } + STAT_UPDATE(audioCodec, audioClient->getSelectedAudioFormat()); + STAT_UPDATE(audioNoiseGate, audioClient->getNoiseGateOpen() ? "Open" : "Closed"); + auto loadingRequests = ResourceCache::getLoadingRequests(); STAT_UPDATE(downloads, loadingRequests.size()); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 6be084100c..0ce113e0a0 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -70,8 +70,20 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, avatarMixerOutKbps, 0) STATS_PROPERTY(int, avatarMixerOutPps, 0) STATS_PROPERTY(float, myAvatarSendRate, 0) + + STATS_PROPERTY(int, audioMixerInKbps, 0) + STATS_PROPERTY(int, audioMixerInPps, 0) + STATS_PROPERTY(int, audioMixerOutKbps, 0) + STATS_PROPERTY(int, audioMixerOutPps, 0) STATS_PROPERTY(int, audioMixerKbps, 0) STATS_PROPERTY(int, audioMixerPps, 0) + STATS_PROPERTY(int, audioMicOutboundPPS, 0) + STATS_PROPERTY(int, audioSilentOutboundPPS, 0) + STATS_PROPERTY(int, audioAudioInboundPPS, 0) + STATS_PROPERTY(int, audioSilentInboundPPS, 0) + STATS_PROPERTY(QString, audioCodec, QString()) + STATS_PROPERTY(QString, audioNoiseGate, QString()) + STATS_PROPERTY(int, downloads, 0) STATS_PROPERTY(int, downloadLimit, 0) STATS_PROPERTY(int, downloadsPending, 0) @@ -180,8 +192,19 @@ signals: void avatarMixerOutKbpsChanged(); void avatarMixerOutPpsChanged(); void myAvatarSendRateChanged(); + void audioMixerInKbpsChanged(); + void audioMixerInPpsChanged(); + void audioMixerOutKbpsChanged(); + void audioMixerOutPpsChanged(); void audioMixerKbpsChanged(); void audioMixerPpsChanged(); + void audioMicOutboundPPSChanged(); + void audioSilentOutboundPPSChanged(); + void audioAudioInboundPPSChanged(); + void audioSilentInboundPPSChanged(); + void audioCodecChanged(); + void audioNoiseGateChanged(); + void downloadsChanged(); void downloadLimitChanged(); void downloadsPendingChanged(); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 9847961f5a..97a263c11d 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -768,6 +768,26 @@ bool Overlays::mousePressEvent(QMouseEvent* event) { return false; } +bool Overlays::mouseDoublePressEvent(QMouseEvent* event) { + PerformanceTimer perfTimer("Overlays::mouseDoublePressEvent"); + + PickRay ray = qApp->computePickRay(event->x(), event->y()); + RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray); + if (rayPickResult.intersects) { + _currentClickingOnOverlayID = rayPickResult.overlayID; + + // Only Web overlays can have focus. + auto thisOverlay = std::dynamic_pointer_cast(getOverlay(_currentClickingOnOverlayID)); + if (thisOverlay) { + auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press); + emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent); + return true; + } + } + emit mouseDoublePressOffOverlay(); + return false; +} + bool Overlays::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("Overlays::mouseReleaseEvent"); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 5c22e46880..c35c7c1ced 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -101,6 +101,7 @@ public: OverlayID addOverlay(Overlay::Pointer overlay); bool mousePressEvent(QMouseEvent* event); + bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); bool mouseMoveEvent(QMouseEvent* event); @@ -300,9 +301,11 @@ signals: void panelDeleted(OverlayID id); void mousePressOnOverlay(OverlayID overlayID, const PointerEvent& event); + void mouseDoublePressOnOverlay(OverlayID overlayID, const PointerEvent& event); void mouseReleaseOnOverlay(OverlayID overlayID, const PointerEvent& event); void mouseMoveOnOverlay(OverlayID overlayID, const PointerEvent& event); void mousePressOffOverlay(); + void mouseDoublePressOffOverlay(); void hoverEnterOverlay(OverlayID overlayID, const PointerEvent& event); void hoverOverOverlay(OverlayID overlayID, const PointerEvent& event); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 1a32c8a263..e239a5e456 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -44,6 +44,7 @@ #include "avatar/AvatarManager.h" #include "AudioClient.h" #include "LODManager.h" +#include "ui/OctreeStatsProvider.h" static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; @@ -185,6 +186,7 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getRootContext()->setContextProperty("Tablet", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("LODManager", DependencyManager::get().data()); + _webSurface->getRootContext()->setContextProperty("OctreeStats", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("pathToFonts", "../../"); tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data()); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9cd87d2e70..c32b5600d9 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -608,6 +608,13 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer message) { + + if (message->getType() == PacketType::SilentAudioFrame) { + _silentInbound.increment(); + } else { + _audioInbound.increment(); + } + auto nodeList = DependencyManager::get(); nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveFirstAudioPacket); @@ -1021,9 +1028,10 @@ void AudioClient::handleAudioInput() { // if we performed the noise gate we can get values from it instead of enumerating the samples again _lastInputLoudness = _inputGate.getLastLoudness(); - if (_inputGate.clippedInLastFrame()) { + if (_inputGate.clippedInLastBlock()) { _timeSinceLastClip = 0.0f; } + } else { float loudness = 0.0f; @@ -1041,6 +1049,12 @@ void AudioClient::handleAudioInput() { emit inputReceived({ reinterpret_cast(networkAudioSamples), numNetworkBytes }); + if (_inputGate.openedInLastBlock()) { + emit noiseGateOpened(); + } else if (_inputGate.closedInLastBlock()) { + emit noiseGateClosed(); + } + } else { // our input loudness is 0, since we're muted _lastInputLoudness = 0; @@ -1057,9 +1071,13 @@ void AudioClient::handleAudioInput() { // the output from the input gate (eventually, this could be crossfaded) // and allow the codec to properly encode down to silent/zero. If we still // have _lastInputLoudness of 0 in our NEXT frame, we will send a silent packet - if (_lastInputLoudness == 0 && !_inputGate.closedInLastFrame()) { + if (_lastInputLoudness == 0 && !_inputGate.closedInLastBlock()) { packetType = PacketType::SilentAudioFrame; + _silentOutbound.increment(); + } else { + _micAudioOutbound.increment(); } + Transform audioTransform; audioTransform.setTranslation(_positionGetter()); audioTransform.setRotation(_orientationGetter()); @@ -1084,6 +1102,7 @@ void AudioClient::handleAudioInput() { } } +// FIXME - should this go through the noise gate and honor mute and echo? void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { Transform audioTransform; audioTransform.setTranslation(_positionGetter()); @@ -1096,6 +1115,8 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) { encodedBuffer = audio; } + _micAudioOutbound.increment(); + // FIXME check a flag to see if we should echo audio? emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), _outgoingAvatarAudioSequenceNumber, audioTransform, avatarBoundingBoxCorner, avatarBoundingBoxScale, diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 5619051eaf..512b4bb3c1 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -46,6 +46,8 @@ #include #include +#include + #include #include "AudioIOStats.h" @@ -121,6 +123,13 @@ public: void negotiateAudioFormat(); void selectAudioFormat(const QString& selectedCodecName); + Q_INVOKABLE QString getSelectedAudioFormat() const { return _selectedCodecName; } + Q_INVOKABLE bool getNoiseGateOpen() const { return _inputGate.isOpen(); } + Q_INVOKABLE float getSilentOutboundPPS() const { return _silentOutbound.rate(); } + Q_INVOKABLE float getMicAudioOutboundPPS() const { return _micAudioOutbound.rate(); } + Q_INVOKABLE float getSilentInboundPPS() const { return _silentInbound.rate(); } + Q_INVOKABLE float getAudioInboundPPS() const { return _audioInbound.rate(); } + const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; } MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; } @@ -218,6 +227,8 @@ signals: void inputReceived(const QByteArray& inputSamples); void outputBytesToNetwork(int numBytes); void inputBytesFromNetwork(int numBytes); + void noiseGateOpened(); + void noiseGateClosed(); void changeDevice(const QAudioDeviceInfo& outputDeviceInfo); void deviceChanged(); @@ -382,6 +393,11 @@ private: Encoder* _encoder { nullptr }; // for outbound mic stream QThread* _checkDevicesThread { nullptr }; + + RateCounter<> _silentOutbound; + RateCounter<> _micAudioOutbound; + RateCounter<> _silentInbound; + RateCounter<> _audioInbound; }; diff --git a/libraries/audio-client/src/AudioNoiseGate.cpp b/libraries/audio-client/src/AudioNoiseGate.cpp index 98ce8cc9e8..8a9134b5dc 100644 --- a/libraries/audio-client/src/AudioNoiseGate.cpp +++ b/libraries/audio-client/src/AudioNoiseGate.cpp @@ -19,16 +19,16 @@ const float AudioNoiseGate::CLIPPING_THRESHOLD = 0.90f; AudioNoiseGate::AudioNoiseGate() : - _inputFrameCounter(0), + _inputBlockCounter(0), _lastLoudness(0.0f), - _quietestFrame(std::numeric_limits::max()), - _loudestFrame(0.0f), - _didClipInLastFrame(false), + _quietestBlock(std::numeric_limits::max()), + _loudestBlock(0.0f), + _didClipInLastBlock(false), _dcOffset(0.0f), _measuredFloor(0.0f), _sampleCounter(0), _isOpen(false), - _framesToClose(0) + _blocksToClose(0) { } @@ -37,7 +37,7 @@ void AudioNoiseGate::removeDCOffset(int16_t* samples, int numSamples) { // // DC Offset correction // - // Measure the DC offset over a trailing number of frames, and remove it from the input signal. + // Measure the DC offset over a trailing number of blocks, and remove it from the input signal. // This causes the noise background measurements and server muting to be more accurate. Many off-board // ADC's have a noticeable DC offset. // @@ -51,7 +51,7 @@ void AudioNoiseGate::removeDCOffset(int16_t* samples, int numSamples) { // Update measured DC offset measuredDcOffset /= numSamples; if (_dcOffset == 0.0f) { - // On first frame, copy over measured offset + // On first block, copy over measured offset _dcOffset = measuredDcOffset; } else { _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.0f - DC_OFFSET_AVERAGING) * measuredDcOffset; @@ -69,13 +69,13 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) { // // NOISE_GATE_HEIGHT: How loud you have to speak relative to noise background to open the gate. // Make this value lower for more sensitivity and less rejection of noise. - // NOISE_GATE_WIDTH: The number of samples in an audio frame for which the height must be exceeded + // NOISE_GATE_WIDTH: The number of samples in an audio block for which the height must be exceeded // to open the gate. - // NOISE_GATE_CLOSE_FRAME_DELAY: Once the noise is below the gate height for the frame, how many frames + // NOISE_GATE_CLOSE_BLOCK_DELAY: Once the noise is below the gate height for the block, how many blocks // will we wait before closing the gate. - // NOISE_GATE_FRAMES_TO_AVERAGE: How many audio frames should we average together to compute noise floor. + // NOISE_GATE_BLOCKS_TO_AVERAGE: How many audio blocks should we average together to compute noise floor. // More means better rejection but also can reject continuous things like singing. - // NUMBER_OF_NOISE_SAMPLE_FRAMES: How often should we re-evaluate the noise floor? + // NUMBER_OF_NOISE_SAMPLE_BLOCKS: How often should we re-evaluate the noise floor? float loudness = 0; int thisSample = 0; @@ -83,16 +83,16 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) { const float NOISE_GATE_HEIGHT = 7.0f; const int NOISE_GATE_WIDTH = 5; - const int NOISE_GATE_CLOSE_FRAME_DELAY = 5; - const int NOISE_GATE_FRAMES_TO_AVERAGE = 5; + const int NOISE_GATE_CLOSE_BLOCK_DELAY = 5; + const int NOISE_GATE_BLOCKS_TO_AVERAGE = 5; // Check clipping, and check if should open noise gate - _didClipInLastFrame = false; + _didClipInLastBlock = false; for (int i = 0; i < numSamples; i++) { thisSample = std::abs(samples[i]); if (thisSample >= ((float) AudioConstants::MAX_SAMPLE_VALUE * CLIPPING_THRESHOLD)) { - _didClipInLastFrame = true; + _didClipInLastBlock = true; } loudness += thisSample; @@ -104,54 +104,81 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) { _lastLoudness = fabs(loudness / numSamples); - if (_quietestFrame > _lastLoudness) { - _quietestFrame = _lastLoudness; + if (_quietestBlock > _lastLoudness) { + _quietestBlock = _lastLoudness; } - if (_loudestFrame < _lastLoudness) { - _loudestFrame = _lastLoudness; + if (_loudestBlock < _lastLoudness) { + _loudestBlock = _lastLoudness; } const int FRAMES_FOR_NOISE_DETECTION = 400; - if (_inputFrameCounter++ > FRAMES_FOR_NOISE_DETECTION) { - _quietestFrame = std::numeric_limits::max(); - _loudestFrame = 0.0f; - _inputFrameCounter = 0; + if (_inputBlockCounter++ > FRAMES_FOR_NOISE_DETECTION) { + _quietestBlock = std::numeric_limits::max(); + _loudestBlock = 0.0f; + _inputBlockCounter = 0; } // If Noise Gate is enabled, check and turn the gate on and off - float averageOfAllSampleFrames = 0.0f; - _sampleFrames[_sampleCounter++] = _lastLoudness; - if (_sampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) { + float averageOfAllSampleBlocks = 0.0f; + _sampleBlocks[_sampleCounter++] = _lastLoudness; + if (_sampleCounter == NUMBER_OF_NOISE_SAMPLE_BLOCKS) { float smallestSample = std::numeric_limits::max(); - for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i += NOISE_GATE_FRAMES_TO_AVERAGE) { + for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_BLOCKS - NOISE_GATE_BLOCKS_TO_AVERAGE; i += NOISE_GATE_BLOCKS_TO_AVERAGE) { float thisAverage = 0.0f; - for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) { - thisAverage += _sampleFrames[j]; - averageOfAllSampleFrames += _sampleFrames[j]; + for (int j = i; j < i + NOISE_GATE_BLOCKS_TO_AVERAGE; j++) { + thisAverage += _sampleBlocks[j]; + averageOfAllSampleBlocks += _sampleBlocks[j]; } - thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE; + thisAverage /= NOISE_GATE_BLOCKS_TO_AVERAGE; if (thisAverage < smallestSample) { smallestSample = thisAverage; } } - averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES; + averageOfAllSampleBlocks /= NUMBER_OF_NOISE_SAMPLE_BLOCKS; _measuredFloor = smallestSample; _sampleCounter = 0; } + _closedInLastBlock = false; + _openedInLastBlock = false; + if (samplesOverNoiseGate > NOISE_GATE_WIDTH) { + _openedInLastBlock = !_isOpen; _isOpen = true; - _framesToClose = NOISE_GATE_CLOSE_FRAME_DELAY; + _blocksToClose = NOISE_GATE_CLOSE_BLOCK_DELAY; } else { - if (--_framesToClose == 0) { - _closedInLastFrame = !_isOpen; + if (--_blocksToClose == 0) { + _closedInLastBlock = _isOpen; _isOpen = false; } } if (!_isOpen) { - memset(samples, 0, numSamples * sizeof(int16_t)); + // First block after being closed gets faded to silence, we fade across + // the entire block on fading out. All subsequent blocks are muted by being slammed + // to zeros + if (_closedInLastBlock) { + float fadeSlope = (1.0f / numSamples); + for (int i = 0; i < numSamples; i++) { + float fadedSample = (1.0f - ((float)i * fadeSlope)) * (float)samples[i]; + samples[i] = (int16_t)fadedSample; + } + } else { + memset(samples, 0, numSamples * sizeof(int16_t)); + } _lastLoudness = 0; } + + if (_openedInLastBlock) { + // would be nice to do a little crossfade from silence, but we only want to fade + // across the first 1/10th of the block, because we don't want to miss early + // transients. + int fadeSamples = numSamples / 10; // fade over 1/10th of the samples + float fadeSlope = (1.0f / fadeSamples); + for (int i = 0; i < fadeSamples; i++) { + float fadedSample = (float)i * fadeSlope * (float)samples[i]; + samples[i] = (int16_t)fadedSample; + } + } } diff --git a/libraries/audio-client/src/AudioNoiseGate.h b/libraries/audio-client/src/AudioNoiseGate.h index 774a4157bb..f72e92b0d5 100644 --- a/libraries/audio-client/src/AudioNoiseGate.h +++ b/libraries/audio-client/src/AudioNoiseGate.h @@ -14,7 +14,7 @@ #include -const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; +const int NUMBER_OF_NOISE_SAMPLE_BLOCKS = 300; class AudioNoiseGate { public: @@ -23,26 +23,29 @@ public: void gateSamples(int16_t* samples, int numSamples); void removeDCOffset(int16_t* samples, int numSamples); - bool clippedInLastFrame() const { return _didClipInLastFrame; } - bool closedInLastFrame() const { return _closedInLastFrame; } + bool clippedInLastBlock() const { return _didClipInLastBlock; } + bool closedInLastBlock() const { return _closedInLastBlock; } + bool openedInLastBlock() const { return _openedInLastBlock; } + bool isOpen() const { return _isOpen; } float getMeasuredFloor() const { return _measuredFloor; } float getLastLoudness() const { return _lastLoudness; } static const float CLIPPING_THRESHOLD; private: - int _inputFrameCounter; + int _inputBlockCounter; float _lastLoudness; - float _quietestFrame; - float _loudestFrame; - bool _didClipInLastFrame; + float _quietestBlock; + float _loudestBlock; + bool _didClipInLastBlock; float _dcOffset; float _measuredFloor; - float _sampleFrames[NUMBER_OF_NOISE_SAMPLE_FRAMES]; + float _sampleBlocks[NUMBER_OF_NOISE_SAMPLE_BLOCKS]; int _sampleCounter; bool _isOpen; - bool _closedInLastFrame { false }; - int _framesToClose; + bool _closedInLastBlock { false }; + bool _openedInLastBlock { false }; + int _blocksToClose; }; #endif // hifi_AudioNoiseGate_h \ No newline at end of file diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 7e2d78a837..668c732ca0 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -736,6 +736,52 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { } } +void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { + // If we don't have a tree, or we're in the process of shutting down, then don't + // process these events. + if (!_tree || _shuttingDown) { + return; + } + PerformanceTimer perfTimer("EntityTreeRenderer::mouseDoublePressEvent"); + PickRay ray = _viewState->computePickRay(event->x(), event->y()); + + bool precisionPicking = !_dontDoPrecisionPicking; + RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::Lock, precisionPicking); + if (rayPickResult.intersects) { + //qCDebug(entitiesrenderer) << "mouseDoublePressEvent over entity:" << rayPickResult.entityID; + + QString urlString = rayPickResult.properties.getHref(); + QUrl url = QUrl(urlString, QUrl::StrictMode); + if (url.isValid() && !url.isEmpty()){ + DependencyManager::get()->handleLookupString(urlString); + } + + glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); + PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID, + pos2D, rayPickResult.intersection, + rayPickResult.surfaceNormal, ray.direction, + toPointerButton(*event), toPointerButtons(*event), Qt::NoModifier); + + emit mouseDoublePressOnEntity(rayPickResult.entityID, pointerEvent); + + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseDoublePressOnEntity", pointerEvent); + } + + _currentClickingOnEntityID = rayPickResult.entityID; + emit clickDownOnEntity(_currentClickingOnEntityID, pointerEvent); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "doubleclickOnEntity", pointerEvent); + } + + _lastPointerEvent = pointerEvent; + _lastPointerEventValid = true; + + } else { + emit mouseDoublePressOffEntity(); + } +} + void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { // If we don't have a tree, or we're in the process of shutting down, then don't // process these events. diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index c11738c459..753f25310c 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -90,6 +90,7 @@ public: // event handles which may generate entity related events void mouseReleaseEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event); + void mouseDoublePressEvent(QMouseEvent* event); void mouseMoveEvent(QMouseEvent* event); /// connect our signals to anEntityScriptingInterface for firing of events related clicking, @@ -103,9 +104,11 @@ public: signals: void mousePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); + void mouseDoublePressOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); void mouseMoveOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); void mouseReleaseOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); void mousePressOffEntity(); + void mouseDoublePressOffEntity(); void clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); void holdingClickOnEntity(const EntityItemID& entityItemID, const PointerEvent& event); diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index f57be4eab3..363887de25 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -143,12 +143,35 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { } void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { - // first bump and prune contacts for all objects in the list + // bump and prune contacts for all objects in the list for (auto object : objects) { bumpAndPruneContacts(object); } - // then remove them + if (_activeStaticBodies.size() > 0) { + // _activeStaticBodies was not cleared last frame. + // The only way to get here is if a static object were moved but we did not actually step the simulation last + // frame (because the framerate is faster than our physics simulation rate). When this happens we must scan + // _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer. + for (auto object : objects) { + btRigidBody* body = object->getRigidBody(); + + std::vector::reverse_iterator itr = _activeStaticBodies.rbegin(); + while (itr != _activeStaticBodies.rend()) { + if (body == *itr) { + if (*itr != *(_activeStaticBodies.rbegin())) { + // swap with rbegin + *itr = *(_activeStaticBodies.rbegin()); + } + _activeStaticBodies.pop_back(); + break; + } + ++itr; + } + } + } + + // remove bodies for (auto object : objects) { btRigidBody* body = object->getRigidBody(); if (body) { diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 07a6b171f4..6cce78d48f 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -32,10 +32,13 @@ protected: Q_INVOKABLE void setStereoInput(bool stereo); signals: - void mutedByMixer(); - void environmentMuted(); - void receivedFirstPacket(); - void disconnected(); + void mutedByMixer(); /// the client has been muted by the mixer + void environmentMuted(); /// the entire environment has been muted by the mixer + void receivedFirstPacket(); /// the client has received its first packet from the audio mixer + void disconnected(); /// the client has been disconnected from the audio mixer + void noiseGateOpened(); /// the noise gate has opened + void noiseGateClosed(); /// the noise gate has closed + void inputReceived(const QByteArray& inputSamples); /// a frame of mic input audio has been received and processed private: AudioScriptingInterface(); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 73a79f1bc6..d721d1c86f 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -142,7 +142,7 @@ QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID) QString ScriptEngine::logException(const QScriptValue& exception) { auto message = formatException(exception); - scriptErrorMessage(qPrintable(message)); + scriptErrorMessage(message); return message; } @@ -453,7 +453,7 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) { } void ScriptEngine::scriptErrorMessage(const QString& message) { - qCCritical(scriptengine) << message; + qCCritical(scriptengine) << qPrintable(message); emit errorMessage(message); } diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp index 0833657886..1a64a5ddb1 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/shared/src/PointerEvent.cpp @@ -48,6 +48,9 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve case Press: obj.setProperty("type", "Press"); break; + case DoublePress: + obj.setProperty("type", "DoublePress"); + break; case Release: obj.setProperty("type", "Release"); break; @@ -131,6 +134,8 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve QString typeStr = type.isString() ? type.toString() : "Move"; if (typeStr == "Press") { event._type = Press; + } else if (typeStr == "DoublePress") { + event._type = DoublePress; } else if (typeStr == "Release") { event._type = Release; } else { diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index 980510b091..4c00ba3e69 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -28,9 +28,10 @@ public: }; enum EventType { - Press, // A button has just been pressed - Release, // A button has just been released - Move // The pointer has just moved + Press, // A button has just been pressed + DoublePress, // A button has just been double pressed + Release, // A button has just been released + Move // The pointer has just moved }; PointerEvent(); diff --git a/libraries/shared/src/shared/RateCounter.h b/libraries/shared/src/shared/RateCounter.h index d04d87493a..3cf509b6bf 100644 --- a/libraries/shared/src/shared/RateCounter.h +++ b/libraries/shared/src/shared/RateCounter.h @@ -24,29 +24,34 @@ public: RateCounter() { _rate = 0; } // avoid use of std::atomic copy ctor void increment(size_t count = 1) { - auto now = usecTimestampNow(); - float currentIntervalMs = (now - _start) / (float) USECS_PER_MSEC; - if (currentIntervalMs > (float) INTERVAL) { - float currentCount = _count; - float intervalSeconds = currentIntervalMs / (float) MSECS_PER_SECOND; - _rate = roundf(currentCount / intervalSeconds * _scale) / _scale; - _start = now; - _count = 0; - }; + checkRate(); _count += count; } - float rate() const { return _rate; } + float rate() const { checkRate(); return _rate; } uint8_t precision() const { return PRECISION; } uint32_t interval() const { return INTERVAL; } private: - uint64_t _start { usecTimestampNow() }; - size_t _count { 0 }; + mutable uint64_t _start { usecTimestampNow() }; + mutable size_t _count { 0 }; const float _scale { powf(10, PRECISION) }; - std::atomic _rate; + mutable std::atomic _rate; + + void checkRate() const { + auto now = usecTimestampNow(); + float currentIntervalMs = (now - _start) / (float)USECS_PER_MSEC; + if (currentIntervalMs > (float)INTERVAL) { + float currentCount = _count; + float intervalSeconds = currentIntervalMs / (float)MSECS_PER_SECOND; + _rate = roundf(currentCount / intervalSeconds * _scale) / _scale; + _start = now; + _count = 0; + }; + } + }; #endif diff --git a/script-archive/entityScripts/doubleClickExample.js b/script-archive/entityScripts/doubleClickExample.js new file mode 100644 index 0000000000..daff2668ed --- /dev/null +++ b/script-archive/entityScripts/doubleClickExample.js @@ -0,0 +1,19 @@ +(function() { + var _this; + function DoubleClickExample() { + _this = this; + return; + } + + DoubleClickExample.prototype = { + clickDownOnEntity: function() { + print("clickDownOnEntity"); + }, + + doubleclickOnEntity: function() { + print("doubleclickOnEntity"); + } + + }; + return new DoubleClickExample(); +}); \ No newline at end of file diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 27efab134a..aba2f35e6b 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -11,7 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var DEFAULT_SCRIPTS = [ +var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", "system/away.js", "system/audio.js", @@ -27,17 +27,13 @@ var DEFAULT_SCRIPTS = [ "system/tablet-users.js", "system/selectAudioDevice.js", "system/notifications.js", - "system/controllers/squeezeHands.js", - "system/controllers/controllerDisplayManager.js", - "system/controllers/handControllerGrab.js", - "system/controllers/handControllerPointer.js", - "system/controllers/grab.js", - "system/controllers/teleport.js", - "system/controllers/toggleAdvancedMovementForHandControllers.js", "system/dialTone.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js" ]; +var DEFAULT_SCRIPTS_SEPARATE = [ + "system/controllers/controllerScripts.js" +]; // add a menu item for debugging var MENU_CATEGORY = "Developer"; @@ -64,16 +60,24 @@ if (Menu.menuExists(MENU_CATEGORY) && !Menu.menuItemExists(MENU_CATEGORY, MENU_I }); } -function runDefaultsTogether() { - for (var j in DEFAULT_SCRIPTS) { - Script.include(DEFAULT_SCRIPTS[j]); +function loadSeparateDefaults() { + for (var i in DEFAULT_SCRIPTS_SEPARATE) { + Script.load(DEFAULT_SCRIPTS_SEPARATE[i]); } } -function runDefaultsSeparately() { - for (var i in DEFAULT_SCRIPTS) { - Script.load(DEFAULT_SCRIPTS[i]); +function runDefaultsTogether() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.include(DEFAULT_SCRIPTS_COMBINED[i]); } + loadSeparateDefaults(); +} + +function runDefaultsSeparately() { + for (var i in DEFAULT_SCRIPTS_COMBINED) { + Script.load(DEFAULT_SCRIPTS_COMBINED[i]); + } + loadSeparateDefaults(); } // start all scripts diff --git a/scripts/system/audioScope.js b/scripts/system/audioScope.js new file mode 100644 index 0000000000..81d8e8fbd4 --- /dev/null +++ b/scripts/system/audioScope.js @@ -0,0 +1,95 @@ +"use strict"; +// +// audioScope.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 3/10/2016 +// Copyright 2016 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 +// +/* global Script, Tablet, AudioScope, Audio */ + +(function () { // BEGIN LOCAL_SCOPE + + var scopeVisibile = AudioScope.getVisible(); + var scopePaused = AudioScope.getPause(); + var autoPause = false; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var showScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope.svg", + text: "Audio Scope", + isActive: scopeVisibile + }); + + var scopePauseImage = "icons/tablet-icons/scope-pause.svg"; + var scopePlayImage = "icons/tablet-icons/scope-play.svg"; + + var pauseScopeButton = tablet.addButton({ + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause", + isActive: scopePaused + }); + + var autoPauseScopeButton = tablet.addButton({ + icon: "icons/tablet-icons/scope-auto.svg", + text: "Auto Pause", + isActive: autoPause + }); + + function setScopePause(paused) { + scopePaused = paused; + pauseScopeButton.editProperties({ + isActive: scopePaused, + icon: scopePaused ? scopePlayImage : scopePauseImage, + text: scopePaused ? "Unpause" : "Pause" + }); + AudioScope.setPause(scopePaused); + } + + showScopeButton.clicked.connect(function () { + // toggle button active state + scopeVisibile = !scopeVisibile; + showScopeButton.editProperties({ + isActive: scopeVisibile + }); + + AudioScope.setVisible(scopeVisibile); + }); + + pauseScopeButton.clicked.connect(function () { + // toggle button active state + setScopePause(!scopePaused); + }); + + autoPauseScopeButton.clicked.connect(function () { + // toggle button active state + autoPause = !autoPause; + autoPauseScopeButton.editProperties({ + isActive: autoPause, + text: autoPause ? "Auto Pause" : "Manual" + }); + }); + + Script.scriptEnding.connect(function () { + tablet.removeButton(showScopeButton); + tablet.removeButton(pauseScopeButton); + tablet.removeButton(autoPauseScopeButton); + }); + + Audio.noiseGateOpened.connect(function(){ + if (autoPause) { + setScopePause(false); + } + }); + + Audio.noiseGateClosed.connect(function(){ + // noise gate closed + if (autoPause) { + setScopePause(true); + } + }); + +}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js new file mode 100644 index 0000000000..df11a1e5be --- /dev/null +++ b/scripts/system/controllers/controllerScripts.js @@ -0,0 +1,41 @@ +"use strict"; + +// controllerScripts.js +// +// Created by David Rowe on 15 Mar 2017. +// Copyright 2017 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 +// + +var CONTOLLER_SCRIPTS = [ + "squeezeHands.js", + "controllerDisplayManager.js", + "handControllerGrab.js", + "handControllerPointer.js", + "grab.js", + "teleport.js", + "toggleAdvancedMovementForHandControllers.js", +]; + +var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; + + +function runDefaultsTogether() { + for (var j in CONTOLLER_SCRIPTS) { + Script.include(CONTOLLER_SCRIPTS[j]); + } +} + +function runDefaultsSeparately() { + for (var i in CONTOLLER_SCRIPTS) { + Script.load(CONTOLLER_SCRIPTS[i]); + } +} + +if (Menu.isOptionChecked(DEBUG_MENU_ITEM)) { + runDefaultsSeparately(); +} else { + runDefaultsTogether(); +}