Merge branch 'master' of github.com:highfidelity/hifi into dk/letsHover

This commit is contained in:
David Kelly 2017-01-09 08:51:42 -07:00
commit fef8c8900f
30 changed files with 326 additions and 143 deletions

View file

@ -68,6 +68,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
} }
@ -221,6 +222,20 @@ void AudioMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet,
} }
} }
void AudioMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
auto nodeList = DependencyManager::get<NodeList>();
nodeList->getOrCreateLinkedData(senderNode);
if (senderNode->getLinkedData()) {
AudioMixerClientData* nodeData = dynamic_cast<AudioMixerClientData*>(senderNode->getLinkedData());
if (nodeData != nullptr) {
bool isRequesting;
message->readPrimitive(&isRequesting);
nodeData->setRequestsDomainListData(isRequesting);
}
}
}
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) { void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
sendingNode->parseIgnoreRequestMessage(packet); sendingNode->parseIgnoreRequestMessage(packet);
} }

View file

@ -61,6 +61,7 @@ private slots:
void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleMuteEnvironmentPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void handleNodeKilled(SharedNodePointer killedNode); void handleNodeKilled(SharedNodePointer killedNode);
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode); void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);

View file

@ -92,6 +92,8 @@ public:
glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); } glm::vec3 getPosition() { return getAvatarAudioStream() ? getAvatarAudioStream()->getPosition() : glm::vec3(0); }
glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); } glm::vec3 getAvatarBoundingBoxCorner() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxCorner() : glm::vec3(0); }
glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); } glm::vec3 getAvatarBoundingBoxScale() { return getAvatarAudioStream() ? getAvatarAudioStream()->getAvatarBoundingBoxScale() : glm::vec3(0); }
bool getRequestsDomainListData() { return _requestsDomainListData; }
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
signals: signals:
void injectorStreamFinished(const QUuid& streamIdentifier); void injectorStreamFinished(const QUuid& streamIdentifier);
@ -122,6 +124,7 @@ private:
bool _shouldFlushEncoder { false }; bool _shouldFlushEncoder { false };
bool _shouldMuteClient { false }; bool _shouldMuteClient { false };
bool _requestsDomainListData { false };
}; };
#endif // hifi_AudioMixerClientData_h #endif // hifi_AudioMixerClientData_h

View file

@ -209,8 +209,13 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
// and that it isn't being ignored by our listening node // and that it isn't being ignored by our listening node
// and that it isn't ignoring our listening node // and that it isn't ignoring our listening node
AudioMixerClientData* otherData = static_cast<AudioMixerClientData*>(otherNode->getLinkedData()); AudioMixerClientData* otherData = static_cast<AudioMixerClientData*>(otherNode->getLinkedData());
// When this is true, the AudioMixer will send Audio data to a client about avatars that have ignored them
bool getsAnyIgnored = nodeData->getRequestsDomainListData() && node->getCanKick();
if (otherData if (otherData
&& !node->isIgnoringNodeWithID(otherNode->getUUID()) && !otherNode->isIgnoringNodeWithID(node->getUUID())) { && (!node->isIgnoringNodeWithID(otherNode->getUUID()) || (otherData->getRequestsDomainListData() && otherNode->getCanKick()))
&& (!otherNode->isIgnoringNodeWithID(node->getUUID()) || getsAnyIgnored)) {
// check to see if we're ignoring in radius // check to see if we're ignoring in radius
bool insideIgnoreRadius = false; bool insideIgnoreRadius = false;
@ -219,7 +224,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& node) {
// We'll always be inside the radius in that case. // We'll always be inside the radius in that case.
insideIgnoreRadius = true; insideIgnoreRadius = true;
// Check to see if the space bubble is enabled // Check to see if the space bubble is enabled
} else if ((node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) && (*otherNode != *node)) { } else if ((node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled())) {
// Define the minimum bubble size // Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData()); AudioMixerClientData* nodeData = reinterpret_cast<AudioMixerClientData*>(node->getLinkedData());

View file

@ -299,7 +299,7 @@ void AvatarMixer::broadcastAvatarData() {
AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData()); AvatarMixerClientData* otherData = reinterpret_cast<AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData()); AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(node->getLinkedData());
// Check to see if the space bubble is enabled // Check to see if the space bubble is enabled
if ((node->isIgnoreRadiusEnabled() && !getsIgnoredByMe) || (otherNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) {
// Define the minimum bubble size // Define the minimum bubble size
static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f);
// Define the scale of the box for the current node // Define the scale of the box for the current node
@ -326,11 +326,11 @@ void AvatarMixer::broadcastAvatarData() {
// Perform the collision check between the two bounding boxes // Perform the collision check between the two bounding boxes
if (nodeBox.touches(otherNodeBox)) { if (nodeBox.touches(otherNodeBox)) {
nodeData->ignoreOther(node, otherNode); nodeData->ignoreOther(node, otherNode);
return false; return getsAnyIgnored;
} }
} }
// Not close enough to ignore // Not close enough to ignore
nodeData->removeFromRadiusIgnoringSet(otherNode->getUUID()); nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID());
return true; return true;
} }
}, },

View file

@ -57,6 +57,15 @@ void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointe
} }
} }
void AvatarMixerClientData::removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other) {
if (isRadiusIgnoring(other)) {
_radiusIgnoredOthers.erase(other);
auto exitingSpaceBubblePacket = NLPacket::create(PacketType::ExitingSpaceBubble, NUM_BYTES_RFC4122_UUID);
exitingSpaceBubblePacket->write(other.toRfc4122());
DependencyManager::get<NodeList>()->sendUnreliablePacket(*exitingSpaceBubblePacket, *self);
}
}
void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
_currentViewFrustum.fromByteArray(message); _currentViewFrustum.fromByteArray(message);
} }

View file

@ -89,7 +89,7 @@ public:
glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
void removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); } void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other);
void ignoreOther(SharedNodePointer self, SharedNodePointer other); void ignoreOther(SharedNodePointer self, SharedNodePointer other);
void readViewFrustumPacket(const QByteArray& message); void readViewFrustumPacket(const QByteArray& message);

View file

@ -83,6 +83,18 @@ Item {
text: "Missed Frame Count: " + root.appdropped; text: "Missed Frame Count: " + root.appdropped;
visible: root.appdropped > 0; visible: root.appdropped > 0;
} }
StatText {
text: "Long Render Count: " + root.longrenders;
visible: root.longrenders > 0;
}
StatText {
text: "Long Submit Count: " + root.longsubmits;
visible: root.longsubmits > 0;
}
StatText {
text: "Long Frame Count: " + root.longframes;
visible: root.longframes > 0;
}
StatText { StatText {
text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount
} }

View file

@ -0,0 +1,52 @@
//
// LetterboxMessage.qml
// qml/hifi
//
// Created by Zach Fox and Howard Stearns on 1/5/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
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import "../styles-uit"
Item {
property alias text: popupText.text
property real radius: hifi.dimensions.borderRadius
visible: false
id: letterbox
anchors.fill: parent
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.5
radius: radius
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: popupText.contentHeight*1.5
anchors.centerIn: parent
radius: radius
color: "white"
FiraSansSemiBold {
id: popupText
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
letterbox.visible = false
}
}
}

View file

@ -11,21 +11,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
/* TODO:
prototype:
- only show kick/mute when canKick
- margins everywhere
- column head centering
- column head font
- proper button .svg on toolbar
mvp:
- Show all participants, including ignored, and populate initial ignore/mute status.
- If name is elided, hover should scroll name left so the full name can be read.
*/
import QtQuick 2.5 import QtQuick 2.5
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import "../styles-uit" import "../styles-uit"
@ -326,6 +311,11 @@ Item {
visible: iAmAdmin visible: iAmAdmin
color: hifi.colors.lightGrayText color: hifi.colors.lightGrayText
} }
function letterbox(message) {
letterboxMessage.text = message;
letterboxMessage.visible = true
}
// This Rectangle refers to the [?] popup button next to "NAMES" // This Rectangle refers to the [?] popup button next to "NAMES"
Rectangle { Rectangle {
color: hifi.colors.tableBackgroundLight color: hifi.colors.tableBackgroundLight
@ -349,7 +339,9 @@ Item {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
hoverEnabled: true hoverEnabled: true
onClicked: namesPopup.visible = true onClicked: letterbox("Bold names in the list are Avatar Display Names.\n" +
"If a Display Name isn't set, a unique Session Display Name is assigned." +
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present.")
onEntered: helpText.color = hifi.colors.baseGrayHighlight onEntered: helpText.color = hifi.colors.baseGrayHighlight
onExited: helpText.color = hifi.colors.darkGray onExited: helpText.color = hifi.colors.darkGray
} }
@ -378,87 +370,14 @@ Item {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
hoverEnabled: true hoverEnabled: true
onClicked: adminPopup.visible = true onClicked: letterbox('Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
"Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page.)")
onEntered: adminHelpText.color = "#94132e" onEntered: adminHelpText.color = "#94132e"
onExited: adminHelpText.color = hifi.colors.redHighlight onExited: adminHelpText.color = hifi.colors.redHighlight
} }
} }
// Explanitory popup upon clicking "[?]" next to "NAMES" LetterboxMessage {
Item { id: letterboxMessage
visible: false
id: namesPopup
anchors.fill: pal
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.5
radius: hifi.dimensions.borderRadius
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: popupText.contentHeight*1.5
anchors.centerIn: parent
radius: hifi.dimensions.borderRadius
color: "white"
FiraSansSemiBold {
id: popupText
text: "Bold names in the list are Avatar Display Names.\n" +
"If a Display Name isn't set, a unique Session Display Name is assigned." +
"\n\nAdministrators of this domain can also see the Username or Machine ID associated with each avatar present."
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
namesPopup.visible = false
}
}
}
// Explanitory popup upon clicking "[?]" next to "ADMIN"
Item {
visible: false
id: adminPopup
anchors.fill: pal
Rectangle {
anchors.fill: parent
color: "black"
opacity: 0.5
radius: hifi.dimensions.borderRadius
}
Rectangle {
width: Math.max(parent.width * 0.75, 400)
height: adminPopupText.contentHeight*1.5
anchors.centerIn: parent
radius: hifi.dimensions.borderRadius
color: "white"
FiraSansSemiBold {
id: adminPopupText
text: 'Silencing a user mutes their microphone. Silenced users can unmute themselves by clicking the "UNMUTE" button on their HUD.\n\n' +
"Banning a user will remove them from this domain and prevent them from returning. You can un-ban users from your domain's settings page."
size: hifi.fontSizes.textFieldInput
color: hifi.colors.darkGray
horizontalAlignment: Text.AlignHCenter
anchors.fill: parent
anchors.leftMargin: 15
anchors.rightMargin: 15
wrapMode: Text.WordWrap
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
onClicked: {
adminPopup.visible = false
}
}
} }
function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml
@ -495,14 +414,20 @@ Item {
sortModel(); sortModel();
break; break;
case 'select': case 'select':
var sessionId = message.params[0]; var sessionIds = message.params[0];
var selected = message.params[1]; var selected = message.params[1];
var userIndex = findSessionIndex(sessionId); var userIndex = findSessionIndex(sessionIds[0]);
if (selected) { if (sessionIds.length > 1) {
table.selection.clear(); // for now, no multi-select letterbox('Only one user can be selected at a time.');
table.selection.select(userIndex); } else if (userIndex < 0) {
letterbox('The last editor is not among this list of users.');
} else { } else {
table.selection.deselect(userIndex); if (selected) {
table.selection.clear(); // for now, no multi-select
table.selection.select(userIndex);
} else {
table.selection.deselect(userIndex);
}
} }
break; break;
// Received an "updateUsername()" request from the JS // Received an "updateUsername()" request from the JS

View file

@ -6144,7 +6144,7 @@ void Application::loadScriptURLDialog() const {
void Application::toggleLogDialog() { void Application::toggleLogDialog() {
if (! _logDialog) { if (! _logDialog) {
_logDialog = new LogDialog(_glWidget, getLogger()); _logDialog = new LogDialog(nullptr, getLogger());
} }
if (_logDialog->isVisible()) { if (_logDialog->isVisible()) {

View file

@ -1339,7 +1339,10 @@ void Avatar::addToScene(AvatarSharedPointer myHandle) {
render::ScenePointer scene = qApp->getMain3DScene(); render::ScenePointer scene = qApp->getMain3DScene();
if (scene) { if (scene) {
render::PendingChanges pendingChanges; render::PendingChanges pendingChanges;
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars() && !DependencyManager::get<NodeList>()->isIgnoringNode(getSessionUUID())) { auto nodelist = DependencyManager::get<NodeList>();
if (DependencyManager::get<SceneScriptingInterface>()->shouldRenderAvatars()
&& !nodelist->isIgnoringNode(getSessionUUID())
&& !nodelist->isRadiusIgnoringNode(getSessionUUID())) {
addToScene(myHandle, scene, pendingChanges); addToScene(myHandle, scene, pendingChanges);
} }
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);

View file

@ -79,6 +79,7 @@ AvatarManager::AvatarManager(QObject* parent) :
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
packetReceiver.registerListener(PacketType::ExitingSpaceBubble, this, "processExitingSpaceBubble");
// when we hear that the user has ignored an avatar by session UUID // when we hear that the user has ignored an avatar by session UUID
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
@ -257,6 +258,9 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) { if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius(); emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
} }
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble || removalReason == YourAvatarEnteredTheirBubble) {
DependencyManager::get<NodeList>()->radiusIgnoreNodeBySessionID(avatar->getSessionUUID(), true);
}
_avatarFades.push_back(removedAvatar); _avatarFades.push_back(removedAvatar);
} }

View file

@ -44,7 +44,7 @@ const QString HIGHLIGHT_COLOR = "#3366CC";
int qTextCursorMeta = qRegisterMetaType<QTextCursor>("QTextCursor"); int qTextCursorMeta = qRegisterMetaType<QTextCursor>("QTextCursor");
int qTextBlockMeta = qRegisterMetaType<QTextBlock>("QTextBlock"); int qTextBlockMeta = qRegisterMetaType<QTextBlock>("QTextBlock");
LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Dialog) { LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog(parent, Qt::Window) {
_logger = logger; _logger = logger;
setWindowTitle("Log"); setWindowTitle("Log");

View file

@ -127,12 +127,18 @@ void Stats::updateStats(bool force) {
auto displayPlugin = qApp->getActiveDisplayPlugin(); auto displayPlugin = qApp->getActiveDisplayPlugin();
auto stats = displayPlugin->getHardwareStats(); auto stats = displayPlugin->getHardwareStats();
STAT_UPDATE(appdropped, stats["app_dropped_frame_count"].toInt()); STAT_UPDATE(appdropped, stats["app_dropped_frame_count"].toInt());
STAT_UPDATE(longrenders, stats["long_render_count"].toInt());
STAT_UPDATE(longsubmits, stats["long_submit_count"].toInt());
STAT_UPDATE(longframes, stats["long_frame_count"].toInt());
STAT_UPDATE(renderrate, displayPlugin->renderRate()); STAT_UPDATE(renderrate, displayPlugin->renderRate());
STAT_UPDATE(presentrate, displayPlugin->presentRate()); STAT_UPDATE(presentrate, displayPlugin->presentRate());
STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate()); STAT_UPDATE(presentnewrate, displayPlugin->newFramePresentRate());
STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate()); STAT_UPDATE(presentdroprate, displayPlugin->droppedFrameRate());
STAT_UPDATE(stutterrate, displayPlugin->stutterRate()); STAT_UPDATE(stutterrate, displayPlugin->stutterRate());
} else { } else {
STAT_UPDATE(appdropped, -1);
STAT_UPDATE(longrenders, -1);
STAT_UPDATE(longsubmits, -1);
STAT_UPDATE(presentrate, -1); STAT_UPDATE(presentrate, -1);
STAT_UPDATE(presentnewrate, -1); STAT_UPDATE(presentnewrate, -1);
STAT_UPDATE(presentdroprate, -1); STAT_UPDATE(presentdroprate, -1);

View file

@ -40,6 +40,9 @@ class Stats : public QQuickItem {
STATS_PROPERTY(float, stutterrate, 0) STATS_PROPERTY(float, stutterrate, 0)
STATS_PROPERTY(int, appdropped, 0) STATS_PROPERTY(int, appdropped, 0)
STATS_PROPERTY(int, longsubmits, 0)
STATS_PROPERTY(int, longrenders, 0)
STATS_PROPERTY(int, longframes, 0)
STATS_PROPERTY(float, presentnewrate, 0) STATS_PROPERTY(float, presentnewrate, 0)
STATS_PROPERTY(float, presentdroprate, 0) STATS_PROPERTY(float, presentdroprate, 0)
@ -137,6 +140,9 @@ public slots:
void forceUpdateStats() { updateStats(true); } void forceUpdateStats() { updateStats(true); }
signals: signals:
void longsubmitsChanged();
void longrendersChanged();
void longframesChanged();
void appdroppedChanged(); void appdroppedChanged();
void framerateChanged(); void framerateChanged();
void expandedChanged(); void expandedChanged();

View file

@ -161,6 +161,13 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, S
removeAvatar(sessionUUID, reason); removeAvatar(sessionUUID, reason);
} }
void AvatarHashMap::processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// read the node id
QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
auto nodeList = DependencyManager::get<NodeList>();
nodeList->radiusIgnoreNodeBySessionID(sessionUUID, false);
}
void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
QWriteLocker locker(&_hashLock); QWriteLocker locker(&_hashLock);

View file

@ -57,6 +57,7 @@ private slots:
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
void processExitingSpaceBubble(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
protected: protected:
AvatarHashMap(); AvatarHashMap();

View file

@ -238,6 +238,10 @@ void NodeList::reset() {
_numNoReplyDomainCheckIns = 0; _numNoReplyDomainCheckIns = 0;
// lock and clear our set of radius ignored IDs
_radiusIgnoredSetLock.lockForWrite();
_radiusIgnoredNodeIDs.clear();
_radiusIgnoredSetLock.unlock();
// lock and clear our set of ignored IDs // lock and clear our set of ignored IDs
_ignoredSetLock.lockForWrite(); _ignoredSetLock.lockForWrite();
_ignoredNodeIDs.clear(); _ignoredNodeIDs.clear();
@ -781,6 +785,22 @@ void NodeList::sendIgnoreRadiusStateToNode(const SharedNodePointer& destinationN
sendPacket(std::move(ignorePacket), *destinationNode); sendPacket(std::move(ignorePacket), *destinationNode);
} }
void NodeList::radiusIgnoreNodeBySessionID(const QUuid& nodeID, bool radiusIgnoreEnabled) {
if (radiusIgnoreEnabled) {
QReadLocker radiusIgnoredSetLocker{ &_radiusIgnoredSetLock }; // read lock for insert
// add this nodeID to our set of ignored IDs
_radiusIgnoredNodeIDs.insert(nodeID);
} else {
QWriteLocker radiusIgnoredSetLocker{ &_radiusIgnoredSetLock }; // write lock for unsafe_erase
_radiusIgnoredNodeIDs.unsafe_erase(nodeID);
}
}
bool NodeList::isRadiusIgnoringNode(const QUuid& nodeID) const {
QReadLocker radiusIgnoredSetLocker{ &_radiusIgnoredSetLock }; // read lock for reading
return _radiusIgnoredNodeIDs.find(nodeID) != _radiusIgnoredNodeIDs.cend();
}
void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) { void NodeList::ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled) {
// enumerate the nodes to send a reliable ignore packet to each that can leverage it // enumerate the nodes to send a reliable ignore packet to each that can leverage it
if (!nodeID.isNull() && _sessionUUID != nodeID) { if (!nodeID.isNull() && _sessionUUID != nodeID) {
@ -1020,12 +1040,12 @@ void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> messag
} }
void NodeList::setRequestsDomainListData(bool isRequesting) { void NodeList::setRequestsDomainListData(bool isRequesting) {
// Tell the avatar mixer whether I want to receive any additional data to which I might be entitled // Tell the avatar mixer and audio mixer whether I want to receive any additional data to which I might be entitled
if (_requestsDomainListData == isRequesting) { if (_requestsDomainListData == isRequesting) {
return; return;
} }
eachMatchingNode([](const SharedNodePointer& node)->bool { eachMatchingNode([](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::AvatarMixer; return (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer);
}, [this, isRequesting](const SharedNodePointer& destinationNode) { }, [this, isRequesting](const SharedNodePointer& destinationNode) {
auto packet = NLPacket::create(PacketType::RequestsDomainListData, sizeof(bool), true); // reliable auto packet = NLPacket::create(PacketType::RequestsDomainListData, sizeof(bool), true); // reliable
packet->writePrimitive(isRequesting); packet->writePrimitive(isRequesting);

View file

@ -76,6 +76,8 @@ public:
void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); } void toggleIgnoreRadius() { ignoreNodesInRadius(!getIgnoreRadiusEnabled()); }
void enableIgnoreRadius() { ignoreNodesInRadius(true); } void enableIgnoreRadius() { ignoreNodesInRadius(true); }
void disableIgnoreRadius() { ignoreNodesInRadius(false); } void disableIgnoreRadius() { ignoreNodesInRadius(false); }
void radiusIgnoreNodeBySessionID(const QUuid& nodeID, bool radiusIgnoreEnabled);
bool isRadiusIgnoringNode(const QUuid& other) const;
void ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled); void ignoreNodeBySessionID(const QUuid& nodeID, bool ignoreEnabled);
bool isIgnoringNode(const QUuid& nodeID) const; bool isIgnoringNode(const QUuid& nodeID) const;
void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled); void personalMuteNodeBySessionID(const QUuid& nodeID, bool muteEnabled);
@ -159,6 +161,8 @@ private:
QTimer _keepAlivePingTimer; QTimer _keepAlivePingTimer;
bool _requestsDomainListData; bool _requestsDomainListData;
mutable QReadWriteLock _radiusIgnoredSetLock;
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _radiusIgnoredNodeIDs;
mutable QReadWriteLock _ignoredSetLock; mutable QReadWriteLock _ignoredSetLock;
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs; tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
mutable QReadWriteLock _personalMutedSetLock; mutable QReadWriteLock _personalMutedSetLock;

View file

@ -105,7 +105,8 @@ public:
UsernameFromIDReply, UsernameFromIDReply,
ViewFrustum, ViewFrustum,
RequestsDomainListData, RequestsDomainListData,
LAST_PACKET_TYPE = RequestsDomainListData ExitingSpaceBubble,
LAST_PACKET_TYPE = ExitingSpaceBubble
}; };
}; };

View file

@ -31,6 +31,10 @@ OculusDisplayPlugin::OculusDisplayPlugin() {
bool OculusDisplayPlugin::internalActivate() { bool OculusDisplayPlugin::internalActivate() {
bool result = Parent::internalActivate(); bool result = Parent::internalActivate();
_longSubmits = 0;
_longRenders = 0;
_longFrames = 0;
currentDebugMode = ovrPerfHud_Off; currentDebugMode = ovrPerfHud_Off;
if (result && _session) { if (result && _session) {
ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode); ovr_SetInt(_session, OVR_PERF_HUD_MODE, currentDebugMode);
@ -112,35 +116,43 @@ void OculusDisplayPlugin::uncustomizeContext() {
Parent::uncustomizeContext(); Parent::uncustomizeContext();
} }
static const uint64_t FRAME_BUDGET = (11 * USECS_PER_MSEC);
static const uint64_t FRAME_OVER_BUDGET = (15 * USECS_PER_MSEC);
void OculusDisplayPlugin::hmdPresent() { void OculusDisplayPlugin::hmdPresent() {
static uint64_t lastSubmitEnd = 0;
if (!_customized) { if (!_customized) {
return; return;
} }
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex) PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
int curIndex; {
ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex); PROFILE_RANGE_EX(render, "Oculus Blit", 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
GLuint curTexId; int curIndex;
ovr_GetTextureSwapChainBufferGL(_session, _textureSwapChain, curIndex, &curTexId); ovr_GetTextureSwapChainCurrentIndex(_session, _textureSwapChain, &curIndex);
GLuint curTexId;
ovr_GetTextureSwapChainBufferGL(_session, _textureSwapChain, curIndex, &curTexId);
// Manually bind the texture to the FBO // Manually bind the texture to the FBO
// FIXME we should have a way of wrapping raw GL ids in GPU objects without // FIXME we should have a way of wrapping raw GL ids in GPU objects without
// taking ownership of the object // taking ownership of the object
auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer); auto fbo = getGLBackend()->getFramebufferID(_outputFramebuffer);
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, curTexId, 0);
render([&](gpu::Batch& batch) { render([&](gpu::Batch& batch) {
batch.enableStereo(false); batch.enableStereo(false);
batch.setFramebuffer(_outputFramebuffer); batch.setFramebuffer(_outputFramebuffer);
batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize())); batch.setViewportTransform(ivec4(uvec2(), _outputFramebuffer->getSize()));
batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize())); batch.setStateScissorRect(ivec4(uvec2(), _outputFramebuffer->getSize()));
batch.resetViewTransform(); batch.resetViewTransform();
batch.setProjectionTransform(mat4()); batch.setProjectionTransform(mat4());
batch.setPipeline(_presentPipeline); batch.setPipeline(_presentPipeline);
batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0)); batch.setResourceTexture(0, _compositeFramebuffer->getRenderBuffer(0));
batch.draw(gpu::TRIANGLE_STRIP, 4); batch.draw(gpu::TRIANGLE_STRIP, 4);
}); });
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0); glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, 0, 0);
}
{ {
auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain); auto result = ovr_CommitTextureSwapChain(_session, _textureSwapChain);
@ -148,8 +160,33 @@ void OculusDisplayPlugin::hmdPresent() {
_sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime; _sceneLayer.SensorSampleTime = _currentPresentFrameInfo.sensorSampleTime;
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose); _sceneLayer.RenderPose[ovrEyeType::ovrEye_Left] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
_sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose); _sceneLayer.RenderPose[ovrEyeType::ovrEye_Right] = ovrPoseFromGlm(_currentPresentFrameInfo.renderPose);
auto submitStart = usecTimestampNow();
uint64_t nonSubmitInterval = 0;
if (lastSubmitEnd != 0) {
nonSubmitInterval = submitStart - lastSubmitEnd;
if (nonSubmitInterval > FRAME_BUDGET) {
++_longRenders;
}
}
ovrLayerHeader* layers = &_sceneLayer.Header; ovrLayerHeader* layers = &_sceneLayer.Header;
result = ovr_SubmitFrame(_session, _currentFrame->frameIndex, &_viewScaleDesc, &layers, 1); {
PROFILE_RANGE_EX(render, "Oculus Submit", 0xff00ff00, (uint64_t)_currentFrame->frameIndex)
result = ovr_SubmitFrame(_session, _currentFrame->frameIndex, &_viewScaleDesc, &layers, 1);
}
lastSubmitEnd = usecTimestampNow();
if (nonSubmitInterval != 0) {
auto submitInterval = lastSubmitEnd - submitStart;
if (nonSubmitInterval < FRAME_BUDGET && submitInterval > FRAME_BUDGET) {
++_longSubmits;
}
if ((nonSubmitInterval + submitInterval) > FRAME_OVER_BUDGET) {
++_longFrames;
}
}
if (!OVR_SUCCESS(result)) { if (!OVR_SUCCESS(result)) {
logWarning("Failed to present"); logWarning("Failed to present");
} }
@ -168,6 +205,7 @@ void OculusDisplayPlugin::hmdPresent() {
_appDroppedFrames.store(appDroppedFrames); _appDroppedFrames.store(appDroppedFrames);
_compositorDroppedFrames.store(compositorDroppedFrames); _compositorDroppedFrames.store(compositorDroppedFrames);
} }
_presentRate.increment(); _presentRate.increment();
} }
@ -176,6 +214,9 @@ QJsonObject OculusDisplayPlugin::getHardwareStats() const {
QJsonObject hardwareStats; QJsonObject hardwareStats;
hardwareStats["app_dropped_frame_count"] = _appDroppedFrames.load(); hardwareStats["app_dropped_frame_count"] = _appDroppedFrames.load();
hardwareStats["compositor_dropped_frame_count"] = _compositorDroppedFrames.load(); hardwareStats["compositor_dropped_frame_count"] = _compositorDroppedFrames.load();
hardwareStats["long_render_count"] = _longRenders.load();
hardwareStats["long_submit_count"] = _longSubmits.load();
hardwareStats["long_frame_count"] = _longFrames.load();
return hardwareStats; return hardwareStats;
} }

View file

@ -41,5 +41,8 @@ private:
std::atomic_int _compositorDroppedFrames; std::atomic_int _compositorDroppedFrames;
std::atomic_int _appDroppedFrames; std::atomic_int _appDroppedFrames;
std::atomic_int _longSubmits;
std::atomic_int _longRenders;
std::atomic_int _longFrames;
}; };

View file

@ -18,7 +18,7 @@ var DEFAULT_SCRIPTS = [
"system/mute.js", "system/mute.js",
"system/goto.js", "system/goto.js",
"system/hmd.js", "system/hmd.js",
"system/marketplaces/marketplace.js", "system/marketplaces/marketplaces.js",
"system/edit.js", "system/edit.js",
"system/pal.js", //"system/mod.js", // older UX, if you prefer "system/pal.js", //"system/mod.js", // older UX, if you prefer
"system/selectAudioDevice.js", "system/selectAudioDevice.js",

View file

@ -20,6 +20,9 @@
// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand // When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand
// controller beam intersects the HUD. // controller beam intersects the HUD.
var systemLaserOn = false;
Script.include("../libraries/controllers.js"); Script.include("../libraries/controllers.js");
// UTILITIES ------------- // UTILITIES -------------
@ -121,6 +124,12 @@ function ignoreMouseActivity() {
if (!Reticle.allowMouseCapture) { if (!Reticle.allowMouseCapture) {
return true; return true;
} }
// if the lasers are on, then reticle/mouse should be hidden and we can ignore it for seeking or depth updating
if (systemLaserOn) {
return true;
}
var pos = Reticle.position; var pos = Reticle.position;
if (!pos || (pos.x == -1 && pos.y == -1)) { if (!pos || (pos.x == -1 && pos.y == -1)) {
return true; return true;
@ -261,6 +270,12 @@ var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20; var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 20;
function isShakingMouse() { // True if the person is waving the mouse around trying to find it. function isShakingMouse() { // True if the person is waving the mouse around trying to find it.
var now = Date.now(), mouse = Reticle.position, isShaking = false; var now = Date.now(), mouse = Reticle.position, isShaking = false;
// if the lasers are on, then we ignore mouse shaking
if (systemLaserOn) {
return false;
}
if (lastIntegration && (lastIntegration !== now)) { if (lastIntegration && (lastIntegration !== now)) {
var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration); var velocity = Vec3.length(Vec3.subtract(mouse, lastMouse)) / (now - lastIntegration);
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity); averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * velocity);
@ -275,9 +290,14 @@ function isShakingMouse() { // True if the person is waving the mouse around try
var NON_LINEAR_DIVISOR = 2; var NON_LINEAR_DIVISOR = 2;
var MINIMUM_SEEK_DISTANCE = 0.1; var MINIMUM_SEEK_DISTANCE = 0.1;
function updateSeeking(doNotStartSeeking) { function updateSeeking(doNotStartSeeking) {
// if the lasers are on, then we never do seeking
if (systemLaserOn) {
isSeeking = false;
return;
}
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) { if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
if (!isSeeking) { if (!isSeeking) {
print('Start seeking mouse.');
isSeeking = true; isSeeking = true;
} }
} // e.g., if we're about to turn it on with first movement. } // e.g., if we're about to turn it on with first movement.
@ -287,7 +307,6 @@ function updateSeeking(doNotStartSeeking) {
averageMouseVelocity = lastIntegration = 0; averageMouseVelocity = lastIntegration = 0;
var lookAt2D = HMD.getHUDLookAtPosition2D(); var lookAt2D = HMD.getHUDLookAtPosition2D();
if (!lookAt2D) { // If this happens, something has gone terribly wrong. if (!lookAt2D) { // If this happens, something has gone terribly wrong.
print('Cannot seek without lookAt position');
isSeeking = false; isSeeking = false;
return; // E.g., if parallel to location in HUD return; // E.g., if parallel to location in HUD
} }
@ -303,7 +322,6 @@ function updateSeeking(doNotStartSeeking) {
} }
var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit. var okX = !updateDimension('x'), okY = !updateDimension('y'); // Evaluate both. Don't short-circuit.
if (okX && okY) { if (okX && okY) {
print('Finished seeking mouse');
isSeeking = false; isSeeking = false;
} else { } else {
Reticle.setPosition(copy); // Not setReticlePosition Reticle.setPosition(copy); // Not setReticlePosition
@ -322,7 +340,7 @@ function updateMouseActivity(isClick) {
return; return;
} // Bug: mouse clicks should keep going. Just not hand controller clicks } // Bug: mouse clicks should keep going. Just not hand controller clicks
handControllerLockOut.update(now); handControllerLockOut.update(now);
Reticle.visible = true; Reticle.visible = !systemLaserOn;
} }
function expireMouseCursor(now) { function expireMouseCursor(now) {
if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) { if (!isPointingAtOverlay() && mouseCursorActivity.expired(now)) {
@ -474,7 +492,6 @@ var LASER_ALPHA = 0.5;
var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA};
var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA};
var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1};
var systemLaserOn = false;
function clearSystemLaser() { function clearSystemLaser() {
if (!systemLaserOn) { if (!systemLaserOn) {
return; return;

View file

@ -25,6 +25,7 @@
<input type="button" id="visible" class="glyph" value="&#xe007;" /> <input type="button" id="visible" class="glyph" value="&#xe007;" />
</div> </div>
<input type="button" id="teleport" value="Jump To Selection" /> <input type="button" id="teleport" value="Jump To Selection" />
<input type="button" id="pal" class="glyph" value="&#xe00c;" />
<input type="button" class="red" id="delete" value="Delete" /> <input type="button" class="red" id="delete" value="Delete" />
</div> </div>
<div id="entity-list"> <div id="entity-list">
@ -94,4 +95,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View file

@ -39,6 +39,7 @@ function loaded() {
elInView = document.getElementById("in-view") elInView = document.getElementById("in-view")
elRadius = document.getElementById("radius"); elRadius = document.getElementById("radius");
elTeleport = document.getElementById("teleport"); elTeleport = document.getElementById("teleport");
elPal = document.getElementById("pal");
elEntityTable = document.getElementById("entity-table"); elEntityTable = document.getElementById("entity-table");
elInfoToggle = document.getElementById("info-toggle"); elInfoToggle = document.getElementById("info-toggle");
elInfoToggleGlyph = elInfoToggle.firstChild; elInfoToggleGlyph = elInfoToggle.firstChild;
@ -274,6 +275,9 @@ function loaded() {
elTeleport.onclick = function () { elTeleport.onclick = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'teleport' }));
} }
elPal.onclick = function () {
EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' }));
}
elDelete.onclick = function() { elDelete.onclick = function() {
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
refreshEntities(); refreshEntities();

View file

@ -98,7 +98,6 @@ EntityListTool = function(opts) {
webView.emitScriptEvent(JSON.stringify(data)); webView.emitScriptEvent(JSON.stringify(data));
} }
webView.webEventReceived.connect(function(data) { webView.webEventReceived.connect(function(data) {
data = JSON.parse(data); data = JSON.parse(data);
if (data.type == "selectionUpdate") { if (data.type == "selectionUpdate") {
@ -120,6 +119,23 @@ EntityListTool = function(opts) {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {
MyAvatar.position = selectionManager.worldPosition; MyAvatar.position = selectionManager.worldPosition;
} }
} else if (data.type == "pal") {
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
selectionManager.selections.forEach(function (id) {
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
if (lastEditedBy) {
sessionIds[lastEditedBy] = true;
}
});
var dedupped = Object.keys(sessionIds);
if (!selectionManager.selections.length) {
Window.alert('No objects selected.');
} else if (!dedupped.length) {
Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.');
} else {
// No need to subscribe if we're just sending.
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true]}), 'local');
}
} else if (data.type == "delete") { } else if (data.type == "delete") {
deleteSelectedEntities(); deleteSelectedEntities();
} else if (data.type == "toggleLocked") { } else if (data.type == "toggleLocked") {
@ -140,4 +156,4 @@ EntityListTool = function(opts) {
}); });
return that; return that;
}; };

View file

@ -355,7 +355,7 @@ function removeOverlays() {
function handleClick(pickRay) { function handleClick(pickRay) {
ExtendedOverlay.applyPickRay(pickRay, function (overlay) { ExtendedOverlay.applyPickRay(pickRay, function (overlay) {
// Don't select directly. Tell qml, who will give us back a list of ids. // Don't select directly. Tell qml, who will give us back a list of ids.
var message = {method: 'select', params: [overlay.key, !overlay.selected]}; var message = {method: 'select', params: [[overlay.key], !overlay.selected]};
pal.sendToQml(message); pal.sendToQml(message);
return true; return true;
}); });
@ -438,6 +438,31 @@ function onClicked() {
pal.setVisible(!pal.visible); pal.setVisible(!pal.visible);
} }
//
// Message from other scripts, such as edit.js
//
var CHANNEL = 'com.highfidelity.pal';
function receiveMessage(channel, messageString, senderID) {
if ((channel !== CHANNEL) ||
(senderID !== MyAvatar.sessionUUID)) {
return;
}
var message = JSON.parse(messageString);
switch (message.method) {
case 'select':
if (!pal.visible) {
onClicked();
}
pal.sendToQml(message); // Accepts objects, not just strings.
break;
default:
print('Unrecognized PAL message', messageString);
}
}
Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(receiveMessage);
var AVERAGING_RATIO = 0.05; var AVERAGING_RATIO = 0.05;
var LOUDNESS_FLOOR = 11.0; var LOUDNESS_FLOOR = 11.0;
var LOUDNESS_SCALE = 2.8 / 5.0; var LOUDNESS_SCALE = 2.8 / 5.0;
@ -517,6 +542,8 @@ Script.scriptEnding.connect(function () {
Users.usernameFromIDReply.disconnect(usernameFromIDReply); Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL); Window.domainChanged.disconnect(clearIgnoredInQMLAndClosePAL);
Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL); Window.domainConnectionRefused.disconnect(clearIgnoredInQMLAndClosePAL);
Messages.unsubscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
off(); off();
}); });