diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 19016ae80e..cf30aca1a7 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -17,7 +17,8 @@ #include AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : - NodeData(nodeID) + NodeData(nodeID), + _receivedSimpleTraitVersions(AvatarTraits::SimpleTraitTypes.size()) { // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); @@ -92,7 +93,41 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { } void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) { - qDebug() << "Pulling a traits message of" << message.getSize(); + // pull the trait version from the message + AvatarTraits::TraitVersion packetTraitVersion; + message.readPrimitive(&packetTraitVersion); + + bool anyTraitsChanged = false; + + while (message.getBytesLeftToRead() > 0) { + // for each trait in the packet, apply it if the trait version is newer than what we have + + AvatarTraits::TraitType traitType; + message.readPrimitive(&traitType); + + AvatarTraits::TraitWireSize traitSize; + message.readPrimitive(&traitSize); + + if (packetTraitVersion > _receivedSimpleTraitVersions[traitType]) { + if (traitType == AvatarTraits::SkeletonModelURL) { + // get the URL from the binary data + auto skeletonModelURL = QUrl::fromEncoded(message.read(traitSize)); + _avatar->setSkeletonModelURL(skeletonModelURL); + + qDebug() << "Set skeleton URL to" << skeletonModelURL << "for trait packet version" << packetTraitVersion; + + _receivedSimpleTraitVersions[traitType] = packetTraitVersion; + + anyTraitsChanged = true; + } + } else { + message.seek(message.getPosition() + traitSize); + } + } + + if (anyTraitsChanged) { + _lastReceivedTraitsChange = std::chrono::steady_clock::now(); + } } uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { @@ -172,3 +207,39 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView; jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView; } + +AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const { + auto it = _lastSentTraitsTimestamps.find(otherAvatar); + + if (it != _lastSentTraitsTimestamps.end()) { + return it->second; + } else { + return TraitsCheckTimestamp(); + } +} + +AvatarTraits::TraitVersion AvatarMixerClientData::getLastSentSimpleTraitVersion(Node::LocalID otherAvatar, + AvatarTraits::TraitType traitType) const { + auto it = _sentSimpleTraitVersions.find(otherAvatar); + + if (it != _sentSimpleTraitVersions.end()) { + return it->second[traitType]; + } + + return AvatarTraits::DEFAULT_TRAIT_VERSION; +} + +void AvatarMixerClientData::setLastSentSimpleTraitVersion(Node::LocalID otherAvatar, AvatarTraits::TraitType traitType, AvatarTraits::TraitVersion traitVersion) { + + auto it = _sentSimpleTraitVersions.find(otherAvatar); + + if (it == _sentSimpleTraitVersions.end()) { + auto pair = _sentSimpleTraitVersions.insert({ + otherAvatar, { AvatarTraits::TotalTraitTypes, AvatarTraits::DEFAULT_TRAIT_VERSION } + }); + + it = pair.first; + } + + it->second[traitType] = traitVersion; +} diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index ceb66a9d22..8792ecfa5d 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -122,6 +123,20 @@ public: void processSetTraitsMessage(ReceivedMessage& message); + using TraitsCheckTimestamp = std::chrono::steady_clock::time_point; + + TraitsCheckTimestamp getLastReceivedTraitsChange() const { return _lastReceivedTraitsChange; } + AvatarTraits::TraitVersion getLastReceivedSimpleTraitVersion(AvatarTraits::TraitType traitType) const + { return _receivedSimpleTraitVersions[traitType]; } + + TraitsCheckTimestamp getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const; + void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint) + { _lastSentTraitsTimestamps[otherAvatar] = sendPoint; } + + AvatarTraits::TraitVersion getLastSentSimpleTraitVersion(Node::LocalID otherAvatar, AvatarTraits::TraitType traitType) const; + void setLastSentSimpleTraitVersion(Node::LocalID otherAvatar, AvatarTraits::TraitType traitType, + AvatarTraits::TraitVersion traitVersion); + private: struct PacketQueue : public std::queue> { QWeakPointer node; @@ -158,6 +173,12 @@ private: int _recentOtherAvatarsOutOfView { 0 }; QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary. bool _requestsDomainListData { false }; + + AvatarTraits::SimpleTraitVersions _receivedSimpleTraitVersions; + TraitsCheckTimestamp _lastReceivedTraitsChange; + + std::unordered_map _lastSentTraitsTimestamps; + std::unordered_map _sentSimpleTraitVersions; }; #endif // hifi_AvatarMixerClientData_h diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 563aac879f..c996008a48 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -79,6 +79,61 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, } } +void AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList) { + + auto otherNodeLocalID = sendingNodeData->getNodeLocalID(); + + // Perform a simple check with two server clock time points + // to see if there is any new traits data for this avatar that we need to send + auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID); + auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange(); + + if (timeOfLastTraitsChange > timeOfLastTraitsSent) { + // there is definitely new traits data to send + + // add the avatar ID to mark the beginning of traits for this avatar + traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122()); + + auto sendingAvatar = sendingNodeData->getAvatarSharedPointer(); + + // compare trait versions so we can see what exactly needs to go out + for (int i = 0; i < AvatarTraits::TotalTraitTypes; ++i) { + AvatarTraits::TraitType traitType = static_cast(i); + + auto lastSentVersion = listeningNodeData->getLastSentSimpleTraitVersion(otherNodeLocalID, traitType); + auto lastReceivedVersion = sendingNodeData->getLastReceivedSimpleTraitVersion(traitType); + + 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 + 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); + } + } + } + + // write a null trait type to mark the end of trait data for this avatar + traitsPacketList.writePrimitive(AvatarTraits::NullTrait); + + // since we send all traits for this other avatar, update the time of last traits sent + // to match the time of last traits change + listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange); + } +} + int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) { if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) { QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); @@ -326,6 +381,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // loop through our sorted avatars and allocate our bandwidth to them accordingly int remainingAvatars = (int)sortedAvatars.size(); + auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); while (!sortedAvatars.empty()) { const auto avatarData = sortedAvatars.top().getAvatar(); sortedAvatars.pop(); @@ -392,11 +448,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 start = usecTimestampNow(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, + &lastSentJointsForOther); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; + static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID; if (bytes.size() > maxAvatarDataBytes) { qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID() << "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data"; @@ -445,6 +502,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + + // use helper to add any changed traits to our packet list + addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); } quint64 startPacketSending = usecTimestampNow(); @@ -461,6 +521,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // record the bytes sent for other avatar data in the AvatarMixerClientData nodeData->recordSentAvatarData(numAvatarDataBytes); + // close the current traits packet list + traitsPacketList->closeCurrentPacket(); + + if (traitsPacketList->getNumPackets() >= 1) { + // send the traits packet list + nodeList->sendPacketList(std::move(traitsPacketList), *node); + } + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 7be119c4b2..ed27709e3e 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -99,6 +99,10 @@ private: int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); + void addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList); + void broadcastAvatarDataToAgent(const SharedNodePointer& node); void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node); diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index c35bfae95c..90258982c3 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -13,15 +13,25 @@ #define hifi_AvatarTraits_h #include +#include +#include namespace AvatarTraits { - enum Trait : uint8_t { + enum TraitType : int8_t { + NullTrait = -1, SkeletonModelURL, - TotalTraits + TotalTraitTypes }; + using TraitTypeSet = std::set; + const TraitTypeSet SimpleTraitTypes = { SkeletonModelURL }; + using TraitVersion = uint32_t; const TraitVersion DEFAULT_TRAIT_VERSION = 0; + + using TraitWireSize = uint16_t; + + using SimpleTraitVersions = std::vector; } #endif // hifi_AvatarTraits_h diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index c4685461a0..5d1f2e4926 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -68,9 +68,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { auto encodedSkeletonURL = _owningAvatar->getSkeletonModelURL().toEncoded(); - uint16_t encodedURLSize = encodedSkeletonURL.size(); + AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); traitsPacketList->writePrimitive(encodedURLSize); + qDebug() << "Sending trait of size" << encodedURLSize; + traitsPacketList->write(encodedSkeletonURL); traitsPacketList->endSegment(); diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 9fd3104e7e..4aea0bb433 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -25,16 +25,16 @@ public: void sendChangedTraitsToMixer(); bool hasChangedTraits() { return _changedTraits.size(); } - void markTraitChanged(AvatarTraits::Trait changedTrait) { _changedTraits.insert(changedTrait); } + void markTraitChanged(AvatarTraits::TraitType changedTrait) { _changedTraits.insert(changedTrait); } - bool hasTraitChanged(AvatarTraits::Trait checkTrait) { return _changedTraits.count(checkTrait) > 0; } + bool hasTraitChanged(AvatarTraits::TraitType checkTrait) { return _changedTraits.count(checkTrait) > 0; } void resetForNewMixer(); private: AvatarData* _owningAvatar; - std::set _changedTraits; + AvatarTraits::TraitTypeSet _changedTraits; AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION }; bool _performInitialSend { false }; }; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3990afa79a..616694c8da 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -133,7 +133,8 @@ public: EntityClone, EntityQueryInitialResultsComplete, - + BulkAvatarTraits, + NUM_PACKET_TYPE };