mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-29 20:23:04 +02:00
Use separate priority-queues for hero & other avatars
This commit is contained in:
parent
167ddbecff
commit
13cda8d212
1 changed files with 146 additions and 125 deletions
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue