Use separate priority-queues for hero & other avatars

This commit is contained in:
Simon Walton 2019-02-27 13:14:08 -08:00
parent 167ddbecff
commit 13cda8d212

View file

@ -307,24 +307,24 @@ namespace {
} // Close anonymous namespace. } // Close anonymous namespace.
// Specialize computePriority() for avatars: //// Specialize computePriority() for avatars:
namespace PrioritySortUtil { //namespace PrioritySortUtil {
template<> float PriorityQueue<SortableAvatar>::computePriority(const SortableAvatar& thing) const { //template<> float PriorityQueue<SortableAvatar>::computePriority(const SortableAvatar& thing) const {
static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority. // static constexpr float AVATAR_HERO_BONUS { 25.0f }; // Higher than any normal priority.
//
float priority = std::numeric_limits<float>::min(); // float priority = std::numeric_limits<float>::min();
//
for (const auto& view : _views) { // for (const auto& view : _views) {
priority = std::max(priority, computePriority(view, thing)); // priority = std::max(priority, computePriority(view, thing));
} // }
//
if (thing.getAvatar()->getPriorityAvatar()) { // if (thing.getAvatar()->getPriorityAvatar()) {
priority += AVATAR_HERO_BONUS; // priority += AVATAR_HERO_BONUS;
} // }
//
return priority; // return priority;
} //}
} //}
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
const float AVATAR_HERO_FRACTION { 0.4f }; const float AVATAR_HERO_FRACTION { 0.4f };
@ -388,11 +388,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// prepare to sort // prepare to sort
const auto& cameraViews = nodeData->getViewFrustums(); const auto& cameraViews = nodeData->getViewFrustums();
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
AvatarData::_avatarSortCoefficientSize, using AvatarPriorityQueue = PrioritySortUtil::PriorityQueue<SortableAvatar>;
AvatarData::_avatarSortCoefficientCenter, // Keep two independent queues, one for heroes and one for the riff-raff.
AvatarData::_avatarSortCoefficientAge); enum PriorityVariants { kHero, kNonhero };
sortedAvatars.reserve(_end - _begin); AvatarPriorityQueue avatarPriorityQueues[2] = { {cameraViews,
AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge},
{cameraViews,
AvatarData::_avatarSortCoefficientSize, AvatarData::_avatarSortCoefficientCenter, AvatarData::_avatarSortCoefficientAge}
};
//PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraViews,
// AvatarData::_avatarSortCoefficientSize,
// AvatarData::_avatarSortCoefficientCenter,
// AvatarData::_avatarSortCoefficientAge);
//sortedAvatars.reserve(_end - _begin);
avatarPriorityQueues[kNonhero].reserve(_end - _begin);
for (auto listedNode = _begin; listedNode != _end; ++listedNode) { for (auto listedNode = _begin; listedNode != _end; ++listedNode) {
Node* otherNodeRaw = (*listedNode).data(); Node* otherNodeRaw = (*listedNode).data();
@ -475,7 +487,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
const MixerAvatar* avatarNodeData = avatarClientNodeData->getConstAvatarData(); const MixerAvatar* avatarNodeData = avatarClientNodeData->getConstAvatarData();
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); 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 // 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 // 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 traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
int avatarSpaceAvailable = avatarPacketCapacity; int avatarSpaceAvailable = avatarPacketCapacity;
int numPacketsSent = 0; int numPacketsSent = 0;
int numAvatarsSent = 0;
auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst); for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) {
for (const auto& sortedAvatar : sortedAvatarVector) { const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst);
const Node* otherNode = sortedAvatar.getNode(); for (const auto& sortedAvatar : sortedAvatarVector) {
auto lastEncodeForOther = sortedAvatar.getTimestamp(); 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, // 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. // or send minimal avatar data in uncommon case of PALIsOpen.
int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars;
auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes; auto frameByteEstimate = identityBytesSent + traitBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes;
bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame; bool overBudget = frameByteEstimate > maxAvatarBytesPerFrame;
if (overBudget) { if (overBudget) {
if (PALIsOpen) { if (PALIsOpen) {
_stats.overBudgetAvatars++; _stats.overBudgetAvatars++;
detail = AvatarData::PALMinimum; detail = AvatarData::PALMinimum;
} else { } else {
_stats.overBudgetAvatars += remainingAvatars; _stats.overBudgetAvatars += remainingAvatars;
break; break;
} }
}
bool overHeroBudget = frameByteEstimate > maxHeroBytesPerFrame;
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(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<JointData>& 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<chrono::microseconds>(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 bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame;
nodeData->incrementNumAvatarsSentLastFrame(); if (overHeroBudget) {
break; // No more heroes (this frame).
}
// set the last sent sequence number for this sender on the receiver auto startAvatarDataPacking = chrono::high_resolution_clock::now();
nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(),
otherNodeData->getLastReceivedSequenceNumber()); const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); 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<JointData>& 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<chrono::microseconds>(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<chrono::microseconds>(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(); if (currentVariant == kHero) { // Dump any remaining heroes into the commoners.
_stats.avatarDataPackingElapsedTime += for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) {
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count(); avatarPriorityQueues[kNonhero].push(*avIter);
}
if (!overBudget) {
// use helper to add any changed traits to our packet list
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
} }
remainingAvatars--;
} }
if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) {