From 13cda8d212154e16565553d661da07275d35dde5 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 27 Feb 2019 13:14:08 -0800 Subject: [PATCH] Use separate priority-queues for hero & other avatars --- .../src/avatars/AvatarMixerSlave.cpp | 271 ++++++++++-------- 1 file changed, 146 insertions(+), 125 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 5d2efdf830..7627f6a2e5 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -307,24 +307,24 @@ namespace { } // Close anonymous namespace. -// Specialize computePriority() for avatars: -namespace PrioritySortUtil { -template<> float PriorityQueue::computePriority(const SortableAvatar& thing) const { - static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. - - float priority = std::numeric_limits::min(); - - for (const auto& view : _views) { - priority = std::max(priority, computePriority(view, thing)); - } - - if (thing.getAvatar()->getPriorityAvatar()) { - priority += AVATAR_HERO_BONUS; - } - - return priority; -} -} +//// Specialize computePriority() for avatars: +//namespace PrioritySortUtil { +//template<> float PriorityQueue::computePriority(const SortableAvatar& thing) const { +// static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. +// +// float priority = std::numeric_limits::min(); +// +// for (const auto& view : _views) { +// priority = std::max(priority, computePriority(view, thing)); +// } +// +// if (thing.getAvatar()->getPriorityAvatar()) { +// priority += AVATAR_HERO_BONUS; +// } +// +// return priority; +//} +//} void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { const float AVATAR_HERO_FRACTION { 0.4f }; @@ -388,11 +388,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // prepare to sort const auto& cameraViews = nodeData->getViewFrustums(); - PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, - AvatarData::_avatarSortCoefficientSize, - AvatarData::_avatarSortCoefficientCenter, - AvatarData::_avatarSortCoefficientAge); - sortedAvatars.reserve(_end - _begin); + + using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue; + // Keep two independent queues, one for heroes and one for the riff-raff. + enum PriorityVariants { kHero, kNonhero }; + AvatarPriorityQueue avatarPriorityQueues[2] = { {cameraViews, + AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}, + {cameraViews, + AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge} + }; + + //PrioritySortUtil::PriorityQueue sortedAvatars(cameraViews, + // AvatarData::_avatarSortCoefficientSize, + // AvatarData::_avatarSortCoefficientCenter, + // AvatarData::_avatarSortCoefficientAge); + //sortedAvatars.reserve(_end - _begin); + + avatarPriorityQueues[kNonhero].reserve(_end - _begin); for (auto listedNode = _begin; listedNode != _end; ++listedNode) { Node* otherNodeRaw = (*listedNode).data(); @@ -475,7 +487,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const MixerAvatar* avatarNodeData = avatarClientNodeData->getConstAvatarData(); auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); - sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); + avatarPriorityQueues[avatarNodeData->getPriorityAvatar() ? kHero : kNonhero].push( + SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); } // If Avatar A's PAL WAS open but is no longer open, AND @@ -501,124 +514,132 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // loop through our sorted avatars and allocate our bandwidth to them accordingly - int remainingAvatars = (int)sortedAvatars.size(); + int remainingAvatars = (int)avatarPriorityQueues[kHero].size() + (int)avatarPriorityQueues[kNonhero].size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; int numPacketsSent = 0; + int numAvatarsSent = 0; auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); - for (const auto& sortedAvatar : sortedAvatarVector) { - const Node* otherNode = sortedAvatar.getNode(); - auto lastEncodeForOther = sortedAvatar.getTimestamp(); + for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) { + const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst); + for (const auto& sortedAvatar : sortedAvatarVector) { + const Node* otherNode = sortedAvatar.getNode(); + auto lastEncodeForOther = sortedAvatar.getTimestamp(); - assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map + assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - AvatarData::AvatarDataDetail detail = AvatarData::NoData; + AvatarData::AvatarDataDetail detail = AvatarData::NoData; - // NOTE: Here's where we determine if we are over budget and drop remaining avatars, - // or send minimal avatar data in uncommon case of PALIsOpen. - int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; - auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes; - bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame; - if (overBudget) { - if (PALIsOpen) { - _stats.overBudgetAvatars++; - detail = AvatarData::PALMinimum; - } else { - _stats.overBudgetAvatars += remainingAvatars; - break; - } - } - - bool overHeroBudget = frameByteEstimate > maxHeroBytesPerFrame; - - auto startAvatarDataPacking = chrono::high_resolution_clock::now(); - - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - const MixerAvatar* otherAvatar = otherNodeData->getConstAvatarData(); - - if (overHeroBudget && otherAvatar->getPriorityAvatar()) { - continue; // No more heroes (this frame). - } - - // Typically all out-of-view avatars but such avatars' priorities will rise with time: - bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; - - if (isLowerPriority) { - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; - nodeData->incrementAvatarOutOfView(); - } else if (!overBudget) { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; - nodeData->incrementAvatarInView(); - - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (otherAvatar->hasProcessedFirstIdentity() - && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); - - // remember the last time we sent identity details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); - } - } - - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); - - const bool distanceAdjust = true; - const bool dropFaceTracking = false; - AvatarDataPacket::SendStatus sendStatus; - sendStatus.sendUUID = true; - - do { - auto startSerialize = chrono::high_resolution_clock::now(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - sendStatus, dropFaceTracking, distanceAdjust, myPosition, - &lastSentJointsForOther, avatarSpaceAvailable); - auto endSerialize = chrono::high_resolution_clock::now(); - _stats.toByteArrayElapsedTime += - (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); - - avatarPacket->write(bytes); - avatarSpaceAvailable -= bytes.size(); - numAvatarDataBytes += bytes.size(); - if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { - // Weren't able to fit everything. - nodeList->sendPacket(std::move(avatarPacket), *destinationNode); - ++numPacketsSent; - avatarPacket = NLPacket::create(PacketType::BulkAvatarData); - avatarSpaceAvailable = avatarPacketCapacity; - } - } while (!sendStatus); - - if (detail != AvatarData::NoData) { - _stats.numOthersIncluded++; - if (otherAvatar->getPriorityAvatar()) { - _stats.numHeroesIncluded++; + // NOTE: Here's where we determine if we are over budget and drop remaining avatars, + // or send minimal avatar data in uncommon case of PALIsOpen. + int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; + auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes; + bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame; + if (overBudget) { + if (PALIsOpen) { + _stats.overBudgetAvatars++; + detail = AvatarData::PALMinimum; + } else { + _stats.overBudgetAvatars += remainingAvatars; + break; + } } - // increment the number of avatars sent to this receiver - nodeData->incrementNumAvatarsSentLastFrame(); + bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame; + if (overHeroBudget) { + break; // No more heroes (this frame). + } - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), - otherNodeData->getLastReceivedSequenceNumber()); - nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); + auto startAvatarDataPacking = chrono::high_resolution_clock::now(); + + const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + const MixerAvatar* otherAvatar = otherNodeData->getConstAvatarData(); + + // Typically all out-of-view avatars but such avatars' priorities will rise with time: + bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; + + if (isLowerPriority) { + detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; + nodeData->incrementAvatarOutOfView(); + } else if (!overBudget) { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; + nodeData->incrementAvatarInView(); + + // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (otherAvatar->hasProcessedFirstIdentity() + && nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); + + // remember the last time we sent identity details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); + } + } + + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); + + const bool distanceAdjust = true; + const bool dropFaceTracking = false; + AvatarDataPacket::SendStatus sendStatus; + sendStatus.sendUUID = true; + + do { + auto startSerialize = chrono::high_resolution_clock::now(); + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + sendStatus, dropFaceTracking, distanceAdjust, myPosition, + &lastSentJointsForOther, avatarSpaceAvailable); + auto endSerialize = chrono::high_resolution_clock::now(); + _stats.toByteArrayElapsedTime += + (quint64)chrono::duration_cast(endSerialize - startSerialize).count(); + + avatarPacket->write(bytes); + avatarSpaceAvailable -= bytes.size(); + numAvatarDataBytes += bytes.size(); + if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) { + // Weren't able to fit everything. + nodeList->sendPacket(std::move(avatarPacket), *destinationNode); + ++numPacketsSent; + avatarPacket = NLPacket::create(PacketType::BulkAvatarData); + avatarSpaceAvailable = avatarPacketCapacity; + } + } while (!sendStatus); + + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; + if (otherAvatar->getPriorityAvatar()) { + _stats.numHeroesIncluded++; + } + + // increment the number of avatars sent to this receiver + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), + otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); + } + + auto endAvatarDataPacking = chrono::high_resolution_clock::now(); + _stats.avatarDataPackingElapsedTime += + (quint64)chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); + + if (!overBudget) { + // use helper to add any changed traits to our packet list + traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + } + numAvatarsSent++; + remainingAvatars--; } - auto endAvatarDataPacking = chrono::high_resolution_clock::now(); - _stats.avatarDataPackingElapsedTime += - (quint64) chrono::duration_cast(endAvatarDataPacking - startAvatarDataPacking).count(); - - if (!overBudget) { - // use helper to add any changed traits to our packet list - traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); + if (currentVariant == kHero) { // Dump any remaining heroes into the commoners. + for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) { + avatarPriorityQueues[kNonhero].push(*avIter); + } } - - remainingAvatars--; } if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) {