diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 51c4e25410..7ba9242306 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -93,6 +93,7 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : this, "handleNodeAudioPacket"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat"); + packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); } @@ -324,7 +325,8 @@ bool AudioMixer::prepareMixForListeningNode(Node* node) { // loop through all other nodes that have sufficient audio to mix DependencyManager::get()->eachNode([&](const SharedNodePointer& otherNode){ - if (otherNode->getLinkedData()) { + // make sure that we have audio data for this other node and that it isn't being ignored by our listening node + if (otherNode->getLinkedData() && !node->isIgnoringNodeWithID(otherNode->getUUID())) { AudioMixerClientData* otherNodeClientData = (AudioMixerClientData*) otherNode->getLinkedData(); // enumerate the ARBs attached to the otherNode and add all that should be added to mix @@ -554,6 +556,10 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) { }); } +void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + sendingNode->parseIgnoreRequestMessage(packet); +} + void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) { auto injectorClientData = qobject_cast(sender()); if (injectorClientData) { diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 4b2a27120d..9b26989847 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -47,6 +47,7 @@ private slots: void handleMuteEnvironmentPacket(QSharedPointer packet, SharedNodePointer sendingNode); void handleNegotiateAudioFormat(QSharedPointer message, SharedNodePointer sendingNode); void handleNodeKilled(SharedNodePointer killedNode); + void handleNodeIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void removeHRTFsForFinishedInjector(const QUuid& streamID); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 46599396ca..65989b389e 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -45,6 +45,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); + packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); @@ -227,14 +228,15 @@ void AvatarMixer::broadcastAvatarData() { // send back a packet with other active node data to this node nodeList->eachMatchingNode( [&](const SharedNodePointer& otherNode)->bool { - if (!otherNode->getLinkedData()) { + // make sure we have data for this avatar, that it isn't the same node, + // and isn't an avatar that the viewing node has ignored + if (!otherNode->getLinkedData() + || otherNode->getUUID() == node->getUUID() + || node->isIgnoringNodeWithID(otherNode->getUUID())) { return false; + } else { + return true; } - if (otherNode->getUUID() == node->getUUID()) { - return false; - } - - return true; }, [&](const SharedNodePointer& otherNode) { ++numOtherAvatars; @@ -431,6 +433,10 @@ void AvatarMixer::handleKillAvatarPacket(QSharedPointer message DependencyManager::get()->processKillNode(*message); } +void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { + senderNode->parseIgnoreRequestMessage(message); +} + void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index d1a9249c83..00cf457d40 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -37,9 +37,11 @@ private slots: void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); + void handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + private: void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 48bbccc3b6..0e0f56438e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -98,6 +98,7 @@ #include #include #include +#include #include #include #include @@ -440,6 +441,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -4755,6 +4757,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface()); scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); } bool Application::canAcceptURL(const QString& urlString) const { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 31a77df0cf..bd76d2bd81 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "Application.h" @@ -69,10 +70,15 @@ AvatarManager::AvatarManager(QObject* parent) : // register a meta type for the weak pointer we'll use for the owning avatar mixer for each avatar qRegisterMetaType >("NodeWeakPointer"); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); + + // 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 + connect(nodeList.data(), &NodeList::ignoredNode, this, &AvatarManager::removeAvatar); } AvatarManager::~AvatarManager() { @@ -85,7 +91,8 @@ void AvatarManager::init() { _avatarHash.insert(MY_AVATAR_KEY, _myAvatar); } - connect(DependencyManager::get().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); + connect(DependencyManager::get().data(), &SceneScriptingInterface::shouldRenderAvatarsChanged, + this, &AvatarManager::updateAvatarRenderStatus, Qt::QueuedConnection); render::ScenePointer scene = qApp->getMain3DScene(); render::PendingChanges pendingChanges; diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index c49d566aa8..f09aa9791c 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -78,6 +78,9 @@ public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); +private slots: + virtual void removeAvatar(const QUuid& sessionUUID) override; + private: explicit AvatarManager(QObject* parent = 0); explicit AvatarManager(const AvatarManager& other); @@ -88,7 +91,6 @@ private: virtual AvatarSharedPointer newSharedAvatar() override; virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) override; - virtual void removeAvatar(const QUuid& sessionUUID) override; virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) override; QVector _avatarFades; diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp index 82332b3187..175a7fd539 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.cpp +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -14,7 +14,7 @@ class QmlWrapper : public QObject { Q_OBJECT public: QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) - : QObject(parent), _qmlObject(qmlObject) { + : QObject(parent), _qmlObject(qmlObject) { } Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { @@ -91,6 +91,10 @@ public: return new ToolbarButtonProxy(rawButton, this); } + + Q_INVOKABLE void removeButton(const QVariant& name) { + QMetaObject::invokeMethod(_qmlObject, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, name)); + } }; @@ -112,4 +116,4 @@ QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) { } -#include "ToolbarScriptingInterface.moc" \ No newline at end of file +#include "ToolbarScriptingInterface.moc" diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d153cfd977..48b701d142 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -19,7 +19,9 @@ #include "AvatarHashMap.h" AvatarHashMap::AvatarHashMap() { - connect(DependencyManager::get().data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); + auto nodeList = DependencyManager::get(); + + connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } QVector AvatarHashMap::getAvatarIdentifiers() { @@ -105,7 +107,10 @@ void AvatarHashMap::processAvatarDataPacket(QSharedPointer mess QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); - if (sessionUUID != _lastOwnerSessionUUID) { + // make sure this isn't our own avatar data or for a previously ignored node + auto nodeList = DependencyManager::get(); + + if (sessionUUID != _lastOwnerSessionUUID && !nodeList->isIgnoringNode(sessionUUID)) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); // have the matching (or new) avatar parse the data from the packet @@ -124,9 +129,13 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer AvatarData::Identity identity; AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); - // mesh URL for a UUID, find avatar in our list - auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); - avatar->processAvatarIdentity(identity); + // make sure this isn't for an ignored avatar + auto nodeList = DependencyManager::get(); + if (!nodeList->isIgnoringNode(identity.uuid)) { + // mesh URL for a UUID, find avatar in our list + auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); + avatar->processAvatarIdentity(identity); + } } void AvatarHashMap::processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode) { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 9d3ebb60f5..a59cc4fa96 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -19,12 +19,14 @@ #include #include +#include + #include #include #include #include "AvatarData.h" -#include + class AvatarHashMap : public QObject, public Dependency { Q_OBJECT diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a03fa43093..e2d6b277a7 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -425,12 +425,8 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& } int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer message, SharedNodePointer sendingNode) { - QMutexLocker locker(&sendingNode->getMutex()); - NodeData* linkedData = sendingNode->getLinkedData(); - if (!linkedData && linkedDataCreateCallback) { - linkedDataCreateCallback(sendingNode.data()); - } + NodeData* linkedData = getOrCreateLinkedData(sendingNode); if (linkedData) { QMutexLocker linkedDataLocker(&linkedData->getMutex()); @@ -440,6 +436,17 @@ int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointergetMutex()); + + NodeData* linkedData = node->getLinkedData(); + if (!linkedData && linkedDataCreateCallback) { + linkedDataCreateCallback(node.data()); + } + + return node->getLinkedData(); +} + SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID) { QReadLocker readLocker(&_nodeMutex); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index c9054ac6d7..03e82f053f 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -149,6 +149,7 @@ public: void processKillNode(ReceivedMessage& message); int updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer matchingNode); + NodeData* getOrCreateLinkedData(SharedNodePointer node); unsigned int broadcastToNodes(std::unique_ptr packet, const NodeSet& destinationNodeTypes); SharedNodePointer soloNodeOfType(NodeType_t nodeType); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 7201b2fd9a..406498b025 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -12,15 +12,17 @@ #include #include -#include - -#include "Node.h" -#include "SharedUtil.h" -#include "NodePermissions.h" - #include #include +#include + +#include "NetworkLogging.h" +#include "NodePermissions.h" +#include "SharedUtil.h" + +#include "Node.h" + const QString UNKNOWN_NodeType_t_NAME = "Unknown"; int NodePtrMetaTypeId = qRegisterMetaType("Node*"); @@ -78,6 +80,27 @@ void Node::updateClockSkewUsec(qint64 clockSkewSample) { _clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile(); } +void Node::parseIgnoreRequestMessage(QSharedPointer message) { + while (message->getBytesLeftToRead()) { + // parse out the UUID being ignored from the packet + QUuid ignoredUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + addIgnoredNode(ignoredUUID); + } +} + +void Node::addIgnoredNode(const QUuid& otherNodeID) { + if (!otherNodeID.isNull() && otherNodeID != _uuid) { + qCDebug(networking) << "Adding" << uuidStringWithoutCurlyBraces(otherNodeID) << "to ignore set for" + << uuidStringWithoutCurlyBraces(_uuid); + + // add the session UUID to the set of ignored ones for this listening node + _ignoredNodeIDSet.insert(otherNodeID); + } else { + qCWarning(networking) << "Node::addIgnoredNode called with null ID or ID of ignoring node."; + } +} + QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; out << node._uuid; diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index b277ac0083..469a0c9755 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -21,6 +21,10 @@ #include #include +#include + +#include + #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NodeData.h" @@ -66,6 +70,10 @@ public: bool getCanRezTmp() const { return _permissions.canRezTemporaryEntities; } bool getCanWriteToAssetServer() const { return _permissions.canWriteToAssetServer; } + void parseIgnoreRequestMessage(QSharedPointer message); + void addIgnoredNode(const QUuid& otherNodeID); + bool isIgnoringNodeWithID(const QUuid& nodeID) const { return _ignoredNodeIDSet.find(nodeID) != _ignoredNodeIDSet.cend(); } + friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -84,6 +92,7 @@ private: QMutex _mutex; MovingPercentile _clockSkewMovingPercentile; NodePermissions _permissions; + tbb::concurrent_unordered_set _ignoredNodeIDSet; }; Q_DECLARE_METATYPE(Node*) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index d3fc93b991..a73537aad0 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -93,6 +93,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // anytime we get a new node we will want to attempt to punch to it connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); + + // anytime we get a new node we may need to re-send our set of ignored node IDs to it + connect(this, &LimitedNodeList::nodeActivated, this, &NodeList::maybeSendIgnoreSetToNode); // setup our timer to send keepalive pings (it's started and stopped on domain connect/disconnect) _keepAlivePingTimer.setInterval(KEEPALIVE_PING_INTERVAL_MS); @@ -215,6 +218,11 @@ void NodeList::reset() { _numNoReplyDomainCheckIns = 0; + // lock and clear our set of ignored IDs + _ignoredSetLock.lockForWrite(); + _ignoredNodeIDs.clear(); + _ignoredSetLock.unlock(); + // refresh the owner UUID to the NULL UUID setSessionUUID(QUuid()); @@ -692,3 +700,67 @@ void NodeList::sendKeepAlivePings() { sendPacket(constructPingPacket(), *node); }); } + +void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) { + // enumerate the nodes to send a reliable ignore packet to each that can leverage it + + if (!nodeID.isNull() && _sessionUUID != nodeID) { + eachMatchingNode([&nodeID](const SharedNodePointer& node)->bool { + if (node->getType() == NodeType::AudioMixer || node->getType() == NodeType::AvatarMixer) { + return true; + } else { + return false; + } + }, [&nodeID, this](const SharedNodePointer& destinationNode) { + // create a reliable NLPacket with space for the ignore UUID + auto ignorePacket = NLPacket::create(PacketType::NodeIgnoreRequest, NUM_BYTES_RFC4122_UUID, true); + + // write the node ID to the packet + ignorePacket->write(nodeID.toRfc4122()); + + qDebug() << "Sending packet to ignore node" << uuidStringWithoutCurlyBraces(nodeID); + + // send off this ignore packet reliably to the matching node + sendPacket(std::move(ignorePacket), *destinationNode); + }); + + QReadLocker setLocker { &_ignoredSetLock }; + + // add this nodeID to our set of ignored IDs + _ignoredNodeIDs.insert(nodeID); + + emit ignoredNode(nodeID); + + } else { + qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID."; + } +} + +bool NodeList::isIgnoringNode(const QUuid& nodeID) const { + QReadLocker setLocker { &_ignoredSetLock }; + return _ignoredNodeIDs.find(nodeID) != _ignoredNodeIDs.cend(); +} + +void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) { + if (newNode->getType() == NodeType::AudioMixer || newNode->getType() == NodeType::AvatarMixer) { + // this is a mixer that we just added - it's unlikely it knows who we were previously ignoring in this session, + // so send that list along now (assuming it isn't empty) + + QReadLocker setLocker { &_ignoredSetLock }; + + if (_ignoredNodeIDs.size() > 0) { + // setup a packet list so we can send the stream of ignore IDs + auto ignorePacketList = NLPacketList::create(PacketType::NodeIgnoreRequest, QByteArray(), true); + + // enumerate the ignored IDs and write them to the packet list + auto it = _ignoredNodeIDs.cbegin(); + while (it != _ignoredNodeIDs.end()) { + ignorePacketList->write(it->toRfc4122()); + ++it; + } + + // send this NLPacketList to the new node + sendPacketList(std::move(ignorePacketList), *newNode); + } + } +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 3fbc86c736..ff994ce612 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -20,6 +20,8 @@ #include // not on windows, not needed for mac or windows #endif +#include + #include #include #include @@ -68,6 +70,9 @@ public: void setIsShuttingDown(bool isShuttingDown) { _isShuttingDown = isShuttingDown; } + void ignoreNodeBySessionID(const QUuid& nodeID); + bool isIgnoringNode(const QUuid& nodeID) const; + public slots: void reset(); void sendDomainServerCheckIn(); @@ -92,6 +97,8 @@ public slots: signals: void limitOfSilentDomainCheckInsReached(); void receivedDomainServerList(); + void ignoredNode(const QUuid& nodeID); + private slots: void stopKeepalivePingTimer(); void sendPendingDSPathQuery(); @@ -103,6 +110,8 @@ private slots: void pingPunchForDomainServer(); void sendKeepAlivePings(); + + void maybeSendIgnoreSetToNode(SharedNodePointer node); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile @@ -129,6 +138,9 @@ private: bool _isShuttingDown { false }; QTimer _keepAlivePingTimer; + mutable QReadWriteLock _ignoredSetLock; + tbb::concurrent_unordered_set _ignoredNodeIDs; + #if (PR_BUILD || DEV_BUILD) bool _shouldSendNewerVersion { false }; #endif diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 6359ad0aff..fca006ae87 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -40,8 +40,6 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode << PacketType::DomainServerRemovedNode; -const QSet RELIABLE_PACKETS = QSet(); - PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { case PacketType::DomainList: @@ -62,6 +60,8 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetUpload: // Removal of extension from Asset requests return 18; + case PacketType::NodeIgnoreRequest: + return 18; // Introduction of node ignore request (which replaced an unused packet tpye) case PacketType::DomainConnectionDenied: return static_cast(DomainConnectionDeniedVersion::IncludesReasonCode); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 296ebb8e68..9581f3ca20 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -61,7 +61,7 @@ public: AssignmentClientStatus, NoisyMute, AvatarIdentity, - TYPE_UNUSED_1, + NodeIgnoreRequest, DomainConnectRequest, DomainServerRequireDTLS, NodeJsonStats, @@ -109,7 +109,6 @@ typedef char PacketVersion; extern const QSet NON_VERIFIED_PACKETS; extern const QSet NON_SOURCED_PACKETS; -extern const QSet RELIABLE_PACKETS; PacketVersion versionForPacketType(PacketType packetType); QByteArray protocolVersionsSignature(); /// returns a unqiue signature for all the current protocols diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 4b8c0b187c..3684e5ba07 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -65,7 +65,9 @@ void PacketQueue::queuePacket(PacketPointer packet) { } void PacketQueue::queuePacketList(PacketListPointer packetList) { - packetList->preparePackets(getNextMessageNumber()); + if (packetList->isOrdered()) { + packetList->preparePackets(getNextMessageNumber()); + } LockGuard locker(_packetsLock); _channels.push_back(std::move(packetList->_packets)); diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp new file mode 100644 index 0000000000..ff7ccb0164 --- /dev/null +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -0,0 +1,19 @@ +// +// UsersScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Stephen Birarda on 2016-07-11. +// 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 +// + +#include "UsersScriptingInterface.h" + +#include + +void UsersScriptingInterface::ignore(const QUuid& nodeID) { + // ask the NodeList to ignore this user (based on the session ID of their node) + DependencyManager::get()->ignoreNodeBySessionID(nodeID); +} diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h new file mode 100644 index 0000000000..0dc62c088c --- /dev/null +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -0,0 +1,28 @@ +// +// UsersScriptingInterface.h +// libraries/script-engine/src +// +// Created by Stephen Birarda on 2016-07-11. +// 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 +// + +#pragma once + +#ifndef hifi_UsersScriptingInterface_h +#define hifi_UsersScriptingInterface_h + +#include + +class UsersScriptingInterface : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public slots: + void ignore(const QUuid& nodeID); +}; + + +#endif // hifi_UsersScriptingInterface_h diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 711e64f938..6880de99b5 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -17,6 +17,7 @@ Script.load("system/goto.js"); Script.load("system/hmd.js"); Script.load("system/examples.js"); Script.load("system/edit.js"); +Script.load("system/ignore.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); Script.load("system/controllers/handControllerGrab.js"); diff --git a/scripts/system/assets/images/ignore-target-01.svg b/scripts/system/assets/images/ignore-target-01.svg new file mode 100644 index 0000000000..98cee89ca1 --- /dev/null +++ b/scripts/system/assets/images/ignore-target-01.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/tools/ignore.svg b/scripts/system/assets/images/tools/ignore.svg new file mode 100644 index 0000000000..f315c5f249 --- /dev/null +++ b/scripts/system/assets/images/tools/ignore.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/ignore.js b/scripts/system/ignore.js new file mode 100644 index 0000000000..39405a2e57 --- /dev/null +++ b/scripts/system/ignore.js @@ -0,0 +1,210 @@ +// +// ignore.js +// scripts/system/ +// +// Created by Stephen Birarda on 07/11/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 +// + +// grab the toolbar +var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + +// setup the ignore button and add it to the toolbar +var button = toolbar.addButton({ + objectName: 'ignore', + imageURL: Script.resolvePath("assets/images/tools/ignore.svg"), + visible: true, + buttonState: 1, + alpha: 0.9 +}); + +var isShowingOverlays = false; +var ignoreOverlays = {}; + +function removeOverlays() { + // enumerate the overlays and remove them + var ignoreOverlayKeys = Object.keys(ignoreOverlays); + + for (i = 0; i < ignoreOverlayKeys.length; ++i) { + var avatarID = ignoreOverlayKeys[i]; + Overlays.deleteOverlay(ignoreOverlays[avatarID]); + } + + ignoreOverlays = {}; +} + +// handle clicks on the toolbar button +function buttonClicked(){ + if (isShowingOverlays) { + removeOverlays(); + isShowingOverlays = false; + } else { + isShowingOverlays = true; + } + + button.writeProperty('buttonState', isShowingOverlays ? 0 : 1); +} + +button.clicked.connect(buttonClicked); + +function updateOverlays() { + if (isShowingOverlays) { + + var identifiers = AvatarList.getAvatarIdentifiers(); + + for (i = 0; i < identifiers.length; ++i) { + var avatarID = identifiers[i]; + + if (avatarID === null) { + // this is our avatar, skip it + continue; + } + + // get the position for this avatar + var avatar = AvatarList.getAvatar(avatarID); + var avatarPosition = avatar && avatar.position; + + if (!avatarPosition) { + // we don't have a valid position for this avatar, skip it + continue; + } + + // setup a position for the overlay that is just above this avatar's head + var overlayPosition = avatar.getJointPosition("Head"); + overlayPosition.y += 0.45; + + if (avatarID in ignoreOverlays) { + // keep the overlay above the current position of this avatar + Overlays.editOverlay(ignoreOverlays[avatarID], { + position: overlayPosition + }); + } else { + // add the overlay above this avatar + var newOverlay = Overlays.addOverlay("image3d", { + url: Script.resolvePath("assets/images/ignore-target-01.svg"), + position: overlayPosition, + size: 0.4, + scale: 0.4, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + solid: true, + isFacingAvatar: true, + drawInFront: true + }); + + // push this overlay to our array of overlays + ignoreOverlays[avatarID] = newOverlay; + } + } + } +} + +Script.update.connect(updateOverlays); + +AvatarList.avatarRemovedEvent.connect(function(avatarID){ + if (isShowingOverlays) { + // we are currently showing overlays and an avatar just went away + + // first remove the rendered overlay + Overlays.deleteOverlay(ignoreOverlays[avatarID]); + + // delete the saved ID of the overlay from our ignored overlays object + delete ignoreOverlays[avatarID]; + } +}); + +function handleSelectedOverlay(clickedOverlay) { + // see this is one of our ignore overlays + + var ignoreOverlayKeys = Object.keys(ignoreOverlays) + for (i = 0; i < ignoreOverlayKeys.length; ++i) { + var avatarID = ignoreOverlayKeys[i]; + var ignoreOverlay = ignoreOverlays[avatarID]; + + if (clickedOverlay.overlayID == ignoreOverlay) { + // matched to an overlay, ask for the matching avatar to be ignored + Users.ignore(avatarID); + + // cleanup of the overlay is handled by the connection to avatarRemovedEvent + } + } +} + +Controller.mousePressEvent.connect(function(event){ + if (isShowingOverlays) { + // handle click events so we can detect when our overlays are clicked + + if (!event.isLeftButton) { + // if another mouse button than left is pressed ignore it + return false; + } + + // compute the pick ray from the event + var pickRay = Camera.computePickRay(event.x, event.y); + + // grab the clicked overlay for the given pick ray + var clickedOverlay = Overlays.findRayIntersection(pickRay); + if (clickedOverlay.intersects) { + handleSelectedOverlay(clickedOverlay); + } + } +}); + +// We get mouseMoveEvents from the handControllers, via handControllerPointer. +// But we dont' get mousePressEvents. +var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + +var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; +var triggered = false; +var activeHand = Controller.Standard.RightHand; + +function controllerComputePickRay() { + var controllerPose = Controller.getPoseValue(activeHand); + if (controllerPose.valid && triggered) { + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + return { origin: controllerPosition, direction: controllerDirection }; + } +} + +function makeTriggerHandler(hand) { + return function (value) { + if (isShowingOverlays) { + if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth? + triggered = true; + if (activeHand !== hand) { + // No switching while the other is already triggered, so no need to release. + activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + } + + var pickRay = controllerComputePickRay(); + if (pickRay) { + var overlayIntersection = Overlays.findRayIntersection(pickRay); + if (overlayIntersection.intersects) { + handleSelectedOverlay(overlayIntersection); + } + } + } else if (triggered && (value < TRIGGER_OFF_VALUE)) { + triggered = false; + } + } + }; +} +triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + +triggerMapping.enable(); + +// cleanup the toolbar button and overlays when script is stopped +Script.scriptEnding.connect(function() { + toolbar.removeButton('ignore'); + removeOverlays(); + triggerMapping.disable(); +});