send traits in bulk to avatar mixer client

This commit is contained in:
Stephen Birarda 2018-08-07 14:02:07 -07:00
parent f23a036f4a
commit 26a1f03314
8 changed files with 188 additions and 11 deletions

View file

@ -17,7 +17,8 @@
#include <NodeList.h>
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;
}

View file

@ -22,6 +22,7 @@
#include <QtCore/QUrl>
#include <AvatarData.h>
#include <AvatarTraits.h>
#include <NodeData.h>
#include <NumericalConstants.h>
#include <udt/PacketHeaders.h>
@ -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<QSharedPointer<ReceivedMessage>> {
QWeakPointer<Node> 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<Node::LocalID, TraitsCheckTimestamp> _lastSentTraitsTimestamps;
std::unordered_map<Node::LocalID, AvatarTraits::SimpleTraitVersions> _sentSimpleTraitVersions;
};
#endif // hifi_AvatarMixerClientData_h

View file

@ -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<AvatarTraits::TraitType>(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);

View file

@ -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);

View file

@ -13,15 +13,25 @@
#define hifi_AvatarTraits_h
#include <cstdint>
#include <set>
#include <vector>
namespace AvatarTraits {
enum Trait : uint8_t {
enum TraitType : int8_t {
NullTrait = -1,
SkeletonModelURL,
TotalTraits
TotalTraitTypes
};
using TraitTypeSet = std::set<TraitType>;
const TraitTypeSet SimpleTraitTypes = { SkeletonModelURL };
using TraitVersion = uint32_t;
const TraitVersion DEFAULT_TRAIT_VERSION = 0;
using TraitWireSize = uint16_t;
using SimpleTraitVersions = std::vector<TraitVersion>;
}
#endif // hifi_AvatarTraits_h

View file

@ -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();

View file

@ -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<AvatarTraits::Trait> _changedTraits;
AvatarTraits::TraitTypeSet _changedTraits;
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
bool _performInitialSend { false };
};

View file

@ -133,7 +133,8 @@ public:
EntityClone,
EntityQueryInitialResultsComplete,
BulkAvatarTraits,
NUM_PACKET_TYPE
};