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();
+}