diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4cc24e2110..049e7d0ede 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -25,16 +25,19 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include #include +#include #include #include @@ -49,12 +52,12 @@ #include #include // TODO: consider moving to scriptengine.h +#include "AssignmentDynamicFactory.h" #include "entities/AssignmentParentFinder.h" #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" - static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; Agent::Agent(ReceivedMessage& message) : @@ -63,6 +66,18 @@ Agent::Agent(ReceivedMessage& message) : _audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO), _avatarAudioTimer(this) { + DependencyManager::set(); + + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(false); + + DependencyManager::registerInheritance(); + DependencyManager::set(); + + DependencyManager::set(); + DependencyManager::set(); + _entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT); DependencyManager::get()->setPacketSender(&_entityEditSender); @@ -99,7 +114,6 @@ Agent::Agent(ReceivedMessage& message) : this, "handleOctreePacket"); packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); - // 100Hz timer for audio const int TARGET_INTERVAL_MSEC = 10; // 10ms connect(&_avatarAudioTimer, &QTimer::timeout, this, &Agent::processAgentAvatarAudio); @@ -439,7 +453,7 @@ void Agent::executeScript() { encodedBuffer = audio; } - AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, + AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false, audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0), packetType, _selectedCodecName); }); @@ -842,6 +856,9 @@ void Agent::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + + DependencyManager::destroy(); + QMetaObject::invokeMethod(&_avatarAudioTimer, "stop"); // cleanup codec & encoder diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index d761699285..426f3ce6fc 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -21,10 +21,7 @@ #include #include #include -#include #include -#include -#include #include #include #include @@ -32,16 +29,12 @@ #include #include #include -#include -#include -#include + #include #include #include "AssignmentClientLogging.h" -#include "AssignmentDynamicFactory.h" #include "AssignmentFactory.h" -#include "avatars/ScriptableAvatar.h" const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -57,21 +50,11 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::set(); DependencyManager::set(); - auto scriptableAvatar = DependencyManager::set(); auto addressManager = DependencyManager::set(); // create a NodeList as an unassigned client, must be after addressManager auto nodeList = DependencyManager::set(NodeType::Unassigned, listenPort); - auto animationCache = DependencyManager::set(); - DependencyManager::set(); - auto entityScriptingInterface = DependencyManager::set(false); - - DependencyManager::registerInheritance(); - auto dynamicFactory = DependencyManager::set(); - DependencyManager::set(); - DependencyManager::set(); - nodeList->startThread(); // set the logging target to the the CHILD_TARGET_NAME LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index b139870e6e..228102ee53 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -39,7 +39,8 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; AvatarMixer::AvatarMixer(ReceivedMessage& message) : - ThreadedAssignment(message) + ThreadedAssignment(message), + _slavePool(&_slaveSharedData) { // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -338,17 +339,7 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { sendIdentity = true; qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } - if (nodeData && nodeData->getAvatarSkeletonModelUrlMustChange()) { // never true for an empty _avatarWhitelist - nodeData->setAvatarSkeletonModelUrlMustChange(false); - AvatarData& avatar = nodeData->getAvatar(); - static const QUrl emptyURL(""); - QUrl url = avatar.cannonicalSkeletonModelURL(emptyURL); - if (!isAvatarInWhitelist(url)) { - qCDebug(avatars) << "Forbidden avatar" << nodeData->getNodeID() << avatar.getSkeletonModelURL() << "replaced with" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar); - avatar.setSkeletonModelURL(_replacementAvatar); - sendIdentity = true; - } - } + if (sendIdentity && !node->isUpstream()) { sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar. // since this packet includes a change to either the skeleton model URL or the display name @@ -360,22 +351,6 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) { } } -bool AvatarMixer::isAvatarInWhitelist(const QUrl& url) { - // The avatar is in the whitelist if: - // 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND - // 2. The avatar's URL's path starts with the path of that same URL in the whitelist - for (const auto& whiteListedPrefix : _avatarWhitelist) { - auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix); - // check if this script URL matches the whitelist domain and, optionally, is beneath the path - if (url.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 && - url.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) { - return true; - } - } - - return false; -} - void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { // throttle using a modified proportional-integral controller const float FRAME_TIME = USECS_PER_SECOND / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; @@ -588,8 +563,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes // parse the identity packet and update the change timestamp if appropriate bool identityChanged = false; bool displayNameChanged = false; - bool skeletonModelUrlChanged = false; - avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged); + avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); @@ -597,9 +571,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes if (displayNameChanged) { nodeData->setAvatarSessionDisplayNameMustChange(true); } - if (skeletonModelUrlChanged && !_avatarWhitelist.isEmpty()) { - nodeData->setAvatarSkeletonModelUrlMustChange(true); - } } } } @@ -992,20 +963,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight << "and a maximum avatar height of" << _domainMaximumHeight; - const QString AVATAR_WHITELIST_DEFAULT{ "" }; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; - _avatarWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION].toString(AVATAR_WHITELIST_DEFAULT).split(',', QString::KeepEmptyParts); + _slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION] + .toString().split(',', QString::KeepEmptyParts); static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar"; - _replacementAvatar = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION].toString(REPLACEMENT_AVATAR_DEFAULT); + _slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION] + .toString(); - if ((_avatarWhitelist.count() == 1) && _avatarWhitelist[0].isEmpty()) { - _avatarWhitelist.clear(); // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok). + if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) { + // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok). + _slaveSharedData.skeletonURLWhitelist.clear(); } - if (_avatarWhitelist.isEmpty()) { + if (_slaveSharedData.skeletonURLWhitelist.isEmpty()) { qCDebug(avatars) << "All avatars are allowed."; } else { - qCDebug(avatars) << "Avatars other than" << _avatarWhitelist << "will be replaced by" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar); + qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString()); } } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 14f84c9e7b..8ae7fc9931 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -59,7 +59,6 @@ private slots: void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void start(); - private: AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node); std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp); @@ -69,11 +68,6 @@ private: void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); void manageIdentityData(const SharedNodePointer& node); - bool isAvatarInWhitelist(const QUrl& url); - - const QString REPLACEMENT_AVATAR_DEFAULT{ "" }; - QStringList _avatarWhitelist { }; - QString _replacementAvatar { REPLACEMENT_AVATAR_DEFAULT }; void optionallyReplicatePacket(ReceivedMessage& message, const Node& node); @@ -83,7 +77,6 @@ private: float _trailingMixRatio { 0.0f }; float _throttlingRatio { 0.0f }; - int _sumListeners { 0 }; int _numStatFrames { 0 }; int _numTightLoopFrames { 0 }; @@ -127,6 +120,7 @@ private: RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs AvatarMixerSlavePool _slavePool; + SlaveSharedData _slaveSharedData; }; #endif // hifi_AvatarMixer_h diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index f279d76450..552fe9a58b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,6 +16,8 @@ #include #include +#include "AvatarMixerSlave.h" + AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : NodeData(nodeID), _receivedSimpleTraitVersions(AvatarTraits::SimpleTraitTypes.size()) @@ -48,7 +50,7 @@ void AvatarMixerClientData::queuePacket(QSharedPointer message, _packetQueue.push(message); } -int AvatarMixerClientData::processPackets() { +int AvatarMixerClientData::processPackets(SlaveSharedData* slaveSharedData) { int packetsProcessed = 0; SharedNodePointer node = _packetQueue.node; assert(_packetQueue.empty() || node); @@ -64,7 +66,7 @@ int AvatarMixerClientData::processPackets() { parseData(*packet); break; case PacketType::SetAvatarTraits: - processSetTraitsMessage(*packet); + processSetTraitsMessage(*packet, slaveSharedData, *node); break; default: Q_UNREACHABLE(); @@ -92,7 +94,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); } -void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) { +void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, SlaveSharedData* slaveSharedData, Node& sendingNode) { // pull the trait version from the message AvatarTraits::TraitVersion packetTraitVersion; message.readPrimitive(&packetTraitVersion); @@ -111,6 +113,11 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) { if (packetTraitVersion > _receivedSimpleTraitVersions[traitType]) { _avatar->processTrait(traitType, message.readWithoutCopy(traitSize)); _receivedSimpleTraitVersions[traitType] = packetTraitVersion; + + if (traitType == AvatarTraits::SkeletonModelURL) { + // special handling for skeleton model URL, since we need to make sure it is in the whitelist + checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion); + } anyTraitsChanged = true; } else { @@ -123,6 +130,46 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) { } } +void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(SlaveSharedData *slaveSharedData, Node& sendingNode, + AvatarTraits::TraitVersion traitVersion) { + const auto& whitelist = slaveSharedData->skeletonURLWhitelist; + + if (!whitelist.isEmpty()) { + bool inWhitelist = false; + auto avatarURL = _avatar->getSkeletonModelURL(); + + // The avatar is in the whitelist if: + // 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND + // 2. The avatar's URL's path starts with the path of that same URL in the whitelist + for (const auto& whiteListedPrefix : whitelist) { + auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix); + // check if this script URL matches the whitelist domain and, optionally, is beneath the path + if (avatarURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 && + avatarURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) { + inWhitelist = true; + + break; + } + } + + if (!inWhitelist) { + // we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change + _avatar->setSkeletonModelURL(slaveSharedData->skeletonReplacementURL); + + qDebug() << "Sending overwritten" << _avatar->getSkeletonModelURL() << "back to sending avatar"; + + auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true); + + // the returned set traits packet uses the trait version from the incoming packet + // so the client knows they should not overwrite if they have since changed the trait + _avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), sendingNode); + } + } +} + uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastTimes.find(nodeUUID); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 8792ecfa5d..96b420afc1 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -34,6 +34,8 @@ const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps"; const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; +struct SlaveSharedData; + class AvatarMixerClientData : public NodeData { Q_OBJECT public: @@ -66,8 +68,6 @@ public: void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); } bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; } void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; } - bool getAvatarSkeletonModelUrlMustChange() const { return _avatarSkeletonModelUrlMustChange; } - void setAvatarSkeletonModelUrlMustChange(bool set = true) { _avatarSkeletonModelUrlMustChange = set; } void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; } void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; } @@ -119,9 +119,11 @@ public: QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; } void queuePacket(QSharedPointer message, SharedNodePointer node); - int processPackets(); // returns number of packets processed + int processPackets(SlaveSharedData* slaveSharedData); // returns number of packets processed - void processSetTraitsMessage(ReceivedMessage& message); + void processSetTraitsMessage(ReceivedMessage& message, SlaveSharedData* slaveSharedData, Node& sendingNode); + void checkSkeletonURLAgainstWhitelist(SlaveSharedData* slaveSharedData, Node& sendingNode, + AvatarTraits::TraitVersion traitVersion); using TraitsCheckTimestamp = std::chrono::steady_clock::time_point; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index c996008a48..88e394bc95 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -59,7 +59,7 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { auto nodeData = dynamic_cast(node->getLinkedData()); if (nodeData) { _stats.nodesProcessed++; - _stats.packetsProcessed += nodeData->processPackets(); + _stats.packetsProcessed += nodeData->processPackets(_sharedData); } auto end = usecTimestampNow(); _stats.processIncomingPacketsElapsedTime += (end - start); @@ -108,20 +108,10 @@ void AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* liste if (lastReceivedVersion > lastSentVersion) { // there is an update to this trait, add it to the traits packet - // write the trait type and the trait version - traitsPacketList.writePrimitive(traitType); - traitsPacketList.writePrimitive(lastReceivedVersion); - - // update the last sent version since we're adding this to the packet + // update the last sent version listeningNodeData->setLastSentSimpleTraitVersion(otherNodeLocalID, traitType, lastReceivedVersion); - if (traitType == AvatarTraits::SkeletonModelURL) { - // get an encoded version of the URL, write its size and then the data itself - auto encodedSkeletonURL = sendingAvatar->getSkeletonModelURL().toEncoded(); - - traitsPacketList.writePrimitive(uint16_t(encodedSkeletonURL.size())); - traitsPacketList.write(encodedSkeletonURL); - } + sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); } } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index ed27709e3e..5112faae29 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -78,11 +78,16 @@ public: jobElapsedTime += rhs.jobElapsedTime; return *this; } +}; +struct SlaveSharedData { + QStringList skeletonURLWhitelist; + QUrl skeletonReplacementURL; }; class AvatarMixerSlave { public: + AvatarMixerSlave(SlaveSharedData* sharedData) : _sharedData(sharedData) {}; using ConstIter = NodeList::const_iterator; void configure(ConstIter begin, ConstIter end); @@ -115,6 +120,7 @@ private: float _throttlingRatio { 0.0f }; AvatarMixerSlaveStats _stats; + SlaveSharedData* _sharedData; }; #endif // hifi_AvatarMixerSlave_h diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 962bba21d2..cf842ac792 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -168,7 +168,7 @@ void AvatarMixerSlavePool::resize(int numThreads) { if (numThreads > _numThreads) { // start new slaves for (int i = 0; i < numThreads - _numThreads; ++i) { - auto slave = new AvatarMixerSlaveThread(*this); + auto slave = new AvatarMixerSlaveThread(*this, _slaveSharedData); slave->start(); _slaves.emplace_back(slave); } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index 15bd681b2c..71a9ace0d3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -32,7 +32,8 @@ class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave { using Lock = std::unique_lock; public: - AvatarMixerSlaveThread(AvatarMixerSlavePool& pool) : _pool(pool) {} + AvatarMixerSlaveThread(AvatarMixerSlavePool& pool, SlaveSharedData* slaveSharedData) : + AvatarMixerSlave(slaveSharedData), _pool(pool) {}; void run() override final; @@ -59,7 +60,8 @@ class AvatarMixerSlavePool { public: using ConstIter = NodeList::const_iterator; - AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); } + AvatarMixerSlavePool(SlaveSharedData* slaveSharedData, int numThreads = QThread::idealThreadCount()) : + _slaveSharedData(slaveSharedData) { setNumThreads(numThreads); } ~AvatarMixerSlavePool() { resize(0); } // Jobs the slave pool can do... @@ -98,6 +100,8 @@ private: Queue _queue; ConstIter _begin; ConstIter _end; + + SlaveSharedData* _slaveSharedData; }; #endif // hifi_AvatarMixerSlavePool_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 396c6cbcac..e556fd734f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6393,8 +6393,8 @@ void Application::nodeActivated(SharedNodePointer node) { if (_avatarOverrideUrl.isValid()) { getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl); } - static const QUrl empty{}; - if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) { + + if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->getSkeletonModelURL()) { getMyAvatar()->resetFullAvatarURL(); } getMyAvatar()->markIdentityDataChanged(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index ddfeb4df24..c9d8f7bb1e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1750,12 +1750,6 @@ glm::quat AvatarData::getOrientationOutbound() const { return (getLocalOrientation()); } -static const QUrl emptyURL(""); -QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { - // We don't put file urls on the wire, but instead convert to empty. - return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; -} - void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { @@ -1836,6 +1830,27 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } } +void AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, int64_t traitVersion) { + destination.writePrimitive(traitType); + + if (traitVersion > 0) { + AvatarTraits::TraitVersion typedVersion = traitVersion; + destination.writePrimitive(typedVersion); + } + + if (traitType == AvatarTraits::SkeletonModelURL) { + QByteArray encodedSkeletonURL; + if (_skeletonModelURL.scheme() != "file" && _skeletonModelURL.scheme() != "qrc") { + encodedSkeletonURL = _skeletonModelURL.toEncoded(); + } + + AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); + destination.writePrimitive(encodedURLSize); + + destination.write(encodedSkeletonURL); + } +} + void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { if (traitType == AvatarTraits::SkeletonModelURL) { // get the URL from the binary data diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 12e8370b86..619f8e1722 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -426,7 +426,6 @@ public: virtual ~AvatarData(); static const QUrl& defaultFullAvatarModelUrl(); - QUrl cannonicalSkeletonModelURL(const QUrl& empty) const; virtual bool isMyAvatar() const { return false; } @@ -958,6 +957,7 @@ public: // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + void packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, int64_t traitVersion = -1); void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); QByteArray identityByteArray(bool setIsReplicated = false) const; diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 90258982c3..121e1057c6 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -29,6 +29,9 @@ namespace AvatarTraits { using TraitVersion = uint32_t; const TraitVersion DEFAULT_TRAIT_VERSION = 0; + using NullableTraitVersion = int64_t; + const NullableTraitVersion NULL_TRAIT_VERSION = -1; + using TraitWireSize = uint16_t; using SimpleTraitVersions = std::vector; diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index 5d1f2e4926..2518dedf37 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -27,6 +27,8 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) : resetForNewMixer(); } }); + + nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride"); } void ClientTraitsHandler::resetForNewMixer() { @@ -63,19 +65,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { if (_performInitialSend || changedTraitsCopy.count(AvatarTraits::SkeletonModelURL)) { traitsPacketList->startSegment(); - - traitsPacketList->writePrimitive(AvatarTraits::SkeletonModelURL); - - auto encodedSkeletonURL = _owningAvatar->getSkeletonModelURL().toEncoded(); - - AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); - traitsPacketList->writePrimitive(encodedURLSize); - - qDebug() << "Sending trait of size" << encodedURLSize; - - traitsPacketList->write(encodedSkeletonURL); - + _owningAvatar->packTrait(AvatarTraits::SkeletonModelURL, *traitsPacketList); traitsPacketList->endSegment(); + + // keep track of our skeleton version in case we get an override back + _currentSkeletonVersion = _currentTraitVersion; } nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer); @@ -84,3 +78,33 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { _performInitialSend = false; } } + +void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { + if (sendingNode->getType() == NodeType::AvatarMixer) { + while (message->getBytesLeftToRead()) { + AvatarTraits::TraitType traitType; + message->readPrimitive(&traitType); + + AvatarTraits::TraitVersion traitVersion; + message->readPrimitive(&traitVersion); + + AvatarTraits::TraitWireSize traitBinarySize; + message->readPrimitive(&traitBinarySize); + + // only accept an override if this is for a trait type we override + // and the version matches what we last sent for skeleton + if (traitType == AvatarTraits::SkeletonModelURL + && traitVersion == _currentSkeletonVersion + && !hasTraitChanged(AvatarTraits::SkeletonModelURL)) { + // override the skeleton URL but do not mark the trait as having changed + // so that we don't unecessarily sent a new trait packet to the mixer with the overriden URL + auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize)); + _owningAvatar->setSkeletonModelURL(encodedSkeletonURL); + + _changedTraits.erase(AvatarTraits::SkeletonModelURL); + } else { + message->seek(message->getPosition() + traitBinarySize); + } + } + } +} diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 4aea0bb433..3a2b70776c 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -12,13 +12,15 @@ #ifndef hifi_ClientTraitsHandler_h #define hifi_ClientTraitsHandler_h -#include +#include #include "AvatarTraits.h" +#include "Node.h" class AvatarData; -class ClientTraitsHandler { +class ClientTraitsHandler : public QObject { + Q_OBJECT public: ClientTraitsHandler(AvatarData* owningAvatar); @@ -31,11 +33,17 @@ public: void resetForNewMixer(); +public slots: + void processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode); + private: AvatarData* _owningAvatar; AvatarTraits::TraitTypeSet _changedTraits; AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; + + AvatarTraits::NullableTraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION }; + bool _performInitialSend { false }; }; diff --git a/libraries/networking/src/ExtendedIODevice.h b/libraries/networking/src/ExtendedIODevice.h new file mode 100644 index 0000000000..7df1af74b6 --- /dev/null +++ b/libraries/networking/src/ExtendedIODevice.h @@ -0,0 +1,39 @@ +// +// ExtendedIODevice.h +// libraries/networking/src +// +// Created by Stephen Birarda on 8/7/18. +// Copyright 2018 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_ExtendedIODevice_h +#define hifi_ExtendedIODevice_h + +#include + +class ExtendedIODevice : public QIODevice { +public: + ExtendedIODevice(QObject* parent = nullptr) : QIODevice(parent) {}; + + template qint64 peekPrimitive(T* data); + template qint64 readPrimitive(T* data); + template qint64 writePrimitive(const T& data); +}; + +template qint64 ExtendedIODevice::peekPrimitive(T* data) { + return peek(reinterpret_cast(data), sizeof(T)); +} + +template qint64 ExtendedIODevice::readPrimitive(T* data) { + return read(reinterpret_cast(data), sizeof(T)); +} + +template qint64 ExtendedIODevice::writePrimitive(const T& data) { + static_assert(!std::is_pointer::value, "T must not be a pointer"); + return write(reinterpret_cast(&data), sizeof(T)); +} + +#endif // hifi_ExtendedIODevice_h diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp index 0540e60a0e..92ccdd6117 100644 --- a/libraries/networking/src/udt/BasePacket.cpp +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -78,7 +78,7 @@ BasePacket::BasePacket(std::unique_ptr data, qint64 size, const HifiSock } BasePacket::BasePacket(const BasePacket& other) : - QIODevice() + ExtendedIODevice() { *this = other; } diff --git a/libraries/networking/src/udt/BasePacket.h b/libraries/networking/src/udt/BasePacket.h index d9b624b595..9c3244e08b 100644 --- a/libraries/networking/src/udt/BasePacket.h +++ b/libraries/networking/src/udt/BasePacket.h @@ -16,16 +16,15 @@ #include -#include - #include #include "../HifiSockAddr.h" #include "Constants.h" +#include "../ExtendedIODevice.h" namespace udt { -class BasePacket : public QIODevice { +class BasePacket : public ExtendedIODevice { Q_OBJECT public: static const qint64 PACKET_WRITE_ERROR; @@ -85,10 +84,6 @@ public: void setReceiveTime(p_high_resolution_clock::time_point receiveTime) { _receiveTime = receiveTime; } p_high_resolution_clock::time_point getReceiveTime() const { return _receiveTime; } - - template qint64 peekPrimitive(T* data); - template qint64 readPrimitive(T* data); - template qint64 writePrimitive(const T& data); protected: BasePacket(qint64 size); @@ -116,19 +111,6 @@ protected: p_high_resolution_clock::time_point _receiveTime; // captures the time the packet received (only used on receiving end) }; - -template qint64 BasePacket::peekPrimitive(T* data) { - return peek(reinterpret_cast(data), sizeof(T)); -} - -template qint64 BasePacket::readPrimitive(T* data) { - return read(reinterpret_cast(data), sizeof(T)); -} - -template qint64 BasePacket::writePrimitive(const T& data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return write(reinterpret_cast(&data), sizeof(T)); -} } // namespace udt diff --git a/libraries/networking/src/udt/PacketList.h b/libraries/networking/src/udt/PacketList.h index ff1860c3d1..b9bd6a8c15 100644 --- a/libraries/networking/src/udt/PacketList.h +++ b/libraries/networking/src/udt/PacketList.h @@ -14,8 +14,7 @@ #include -#include - +#include "../ExtendedIODevice.h" #include "Packet.h" #include "PacketHeaders.h" @@ -25,7 +24,7 @@ namespace udt { class Packet; -class PacketList : public QIODevice { +class PacketList : public ExtendedIODevice { Q_OBJECT public: using MessageNumber = uint32_t; @@ -59,9 +58,6 @@ public: virtual bool isSequential() const override { return false; } virtual qint64 size() const override { return getDataSize(); } - template qint64 readPrimitive(T* data); - template qint64 writePrimitive(const T& data); - qint64 writeString(const QString& string); protected: @@ -105,16 +101,6 @@ private: QByteArray _extendedHeader; }; -template qint64 PacketList::readPrimitive(T* data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return read(reinterpret_cast(data), sizeof(T)); -} - -template qint64 PacketList::writePrimitive(const T& data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return write(reinterpret_cast(&data), sizeof(T)); -} - template std::unique_ptr PacketList::takeFront() { static_assert(std::is_base_of::value, "T must derive from Packet.");