diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index df6a64e874..1df6c24a60 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -74,14 +74,22 @@ bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& return true; } +uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const { + // return the matching PacketSequenceNumber, or the default if we don't have it + auto nodeMatch = _lastBroadcastTimes.find(nodeUUID); + if (nodeMatch != _lastBroadcastTimes.end()) { + return nodeMatch->second; + } + return 0; +} + uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); if (nodeMatch != _lastBroadcastSequenceNumbers.end()) { return nodeMatch->second; - } else { - return 0; } + return 0; } void AvatarMixerClientData::ignoreOther(SharedNodePointer self, SharedNodePointer other) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 87f7bb9cc9..7eea31c9ab 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -43,6 +43,7 @@ public: int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } + AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); @@ -51,6 +52,12 @@ public: { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } + uint64_t getLastBroadcastTime(const QUuid& nodeUUID) const; + void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { + _lastBroadcastTimes[nodeUUID] = broadcastTime; + } + Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); } + uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } @@ -106,6 +113,9 @@ public: bool getRequestsDomainListData() { return _requestsDomainListData; } void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; } + ViewFrustum getViewFrustom() const { return _currentViewFrustum; } + + quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) { quint64 result = 0; if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) { @@ -134,6 +144,7 @@ private: uint16_t _lastReceivedSequenceNumber { 0 }; std::unordered_map _lastBroadcastSequenceNumbers; std::unordered_set _hasReceivedFirstPacketsFrom; + std::unordered_map _lastBroadcastTimes; // this is a map of the last time we encoded an "other" avatar for // sending to "this" node diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 46133a221f..4c95c96483 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -187,9 +187,75 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); + + QList avatarList; + std::unordered_map avatarDataToNodes; + + //qDebug() << "------------------------------"; + int listItem = 0; + std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { + const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + if (otherNodeData) { + listItem++; + AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); + avatarList << otherAvatar; + avatarDataToNodes[otherAvatar] = otherNode; + /* + qDebug() << "listItem [" << listItem << "] " + << "otherNode:" << otherNode.data() + << "otherNode->getUUID():" << otherNode->getUUID() + << "otherNodeData:" << otherNodeData + << "otherAvatar:" << otherAvatar.get(); + + qDebug() << "avatarDataToNodes[" << otherAvatar.get() << "]=" << otherNode.data(); + */ + } + + }); + /* + qDebug() << "------------------------------"; + qDebug() << "avatarList.size:" << avatarList.size(); + qDebug() << "avatarDataToNodes.size:" << avatarDataToNodes.size(); + */ + + ViewFrustum cameraView = nodeData->getViewFrustom(); + std::priority_queue sortedAvatars = AvatarData::sortAvatars( + avatarList, cameraView, + + [&](AvatarSharedPointer avatar)->uint64_t{ + auto avatarNode = avatarDataToNodes[avatar]; + if (avatarNode) { + return nodeData->getLastBroadcastTime(avatarNode->getUUID()); + } + return 0; // ??? + }, + + [this](AvatarSharedPointer avatar)->bool{ + // FIXME -- when to ignore this node + return false; + }); + + // this is an AGENT we have received head data from // send back a packet with other active node data to this node - std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { + //std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { + + int avatarRank = 0; + while (!sortedAvatars.empty()) { + AvatarPriority sortData = sortedAvatars.top(); + sortedAvatars.pop(); + const auto& avatarData = sortData.avatar; + avatarRank++; + + auto otherNode = avatarDataToNodes[avatarData]; + + //qDebug() << "otherNode (" << otherNode.data() << ")= avatarDataToNodes[" << avatarData.get() << "]"; + + if (!otherNode) { + //qDebug() << "For viewer:" << node->getUUID() << "... process other avatar [" << avatarRank << ":" << avatarData.get() << "... otherNode unknown!!"; + continue; + } + //qDebug() << "For viewer:" << node->getUUID() << "... process other avatar [" << avatarRank << "] avatarData: " << avatarData.get() << " otherNode:" << otherNode->getUUID() << " ... "; bool shouldConsider = false; quint64 startIgnoreCalculation = usecTimestampNow(); @@ -286,7 +352,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // are out of view, this also appears to disable this random distribution. if (distanceToAvatar != 0.0f && !getsOutOfView - && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { + // -- && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar) /// FIX ME... no longer doing random + ) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); @@ -395,6 +462,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), otherNodeData->getLastReceivedSequenceNumber()); + // remember the last time we sent details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + } } @@ -406,7 +476,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } } } - }); + }; quint64 startPacketSending = usecTimestampNow(); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index ed8f083a41..64ecb1dc29 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -334,11 +334,6 @@ void Avatar::updateAvatarEntities() { setAvatarEntityDataChanged(false); } -bool Avatar::shouldDie() const { - const qint64 AVATAR_SILENCE_THRESHOLD_USECS = 5 * USECS_PER_SECOND; - return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; -} - void Avatar::simulate(float deltaTime, bool inView) { PROFILE_RANGE(simulation, "simulate"); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 80d387fd33..53f0562bdd 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -178,7 +178,6 @@ public: uint64_t getLastRenderUpdateTime() const { return _lastRenderUpdateTime; } void setLastRenderUpdateTime(uint64_t time) { _lastRenderUpdateTime = time; } - bool shouldDie() const; void animateScaleChanges(float deltaTime); void setTargetScale(float targetScale) override; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c3fc974365..1a9866aae4 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -148,16 +148,6 @@ float AvatarManager::getAvatarSimulationRate(const QUuid& sessionID, const QStri } - -class AvatarPriority { -public: - AvatarPriority(AvatarSharedPointer a, float p) : avatar(a), priority(p) {} - AvatarSharedPointer avatar; - float priority; - // NOTE: we invert the less-than operator to sort high priorities to front - bool operator<(const AvatarPriority& other) const { return priority > other.priority; } -}; - void AvatarManager::updateOtherAvatars(float deltaTime) { // lock the hash for read to check the size QReadLocker lock(&_hashLock); @@ -173,57 +163,31 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { QList avatarList = avatarMap.values(); ViewFrustum cameraView; qApp->copyDisplayViewFrustum(cameraView); - glm::vec3 frustumCenter = cameraView.getPosition(); - const float OUT_OF_VIEW_PENALTY = -10.0; + std::priority_queue sortedAvatars = AvatarData::sortAvatars( + avatarList, cameraView, - std::priority_queue sortedAvatars; - { - PROFILE_RANGE(simulation, "sort"); - for (int32_t i = 0; i < avatarList.size(); ++i) { - const auto& avatar = std::static_pointer_cast(avatarList.at(i)); - if (avatar == _myAvatar || !avatar->isInitialized()) { + [](AvatarSharedPointer avatar)->uint64_t{ + return std::static_pointer_cast(avatar)->getLastRenderUpdateTime(); + }, + + [this](AvatarSharedPointer avatar)->bool{ + const auto& castedAvatar = std::static_pointer_cast(avatar); + if (castedAvatar == _myAvatar || !castedAvatar->isInitialized()) { // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update or fade out uninitialized Avatars - continue; + return true; // ignore it } if (avatar->shouldDie()) { removeAvatar(avatar->getID()); - continue; + return true; // ignore it } if (avatar->isDead()) { - continue; + return true; // ignore it } - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - // (d) TIME_PENALTY to help recently updated entries sort toward back - glm::vec3 avatarPosition = avatar->getPosition(); - glm::vec3 offset = avatarPosition - frustumCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = avatar->getBoundingRadius(); - const glm::vec3& forward = cameraView.getDirection(); - float apparentSize = radius / distance; - float cosineAngle = glm::length(offset - glm::dot(offset, forward) * forward) / distance; - const float TIME_PENALTY = 0.080f; // seconds - float age = (float)(startTime - avatar->getLastRenderUpdateTime()) / (float)(USECS_PER_SECOND) - TIME_PENALTY; - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units - // relative to the others. These weights are pure magic tuning and are hard coded in the - // relation below: (hint: unitary weights are not explicityly shown) - float priority = apparentSize + 0.25f * cosineAngle + age; - - // decrement priority of avatars outside keyhole - if (distance > cameraView.getCenterRadius()) { - if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - sortedAvatars.push(AvatarPriority(avatar, priority)); - } - } + return false; + }); render::PendingChanges pendingChanges; const uint64_t RENDER_UPDATE_BUDGET = 1500; // usec @@ -256,7 +220,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { uint64_t now = usecTimestampNow(); if (now < renderExpiry) { // we're within budget - const float OUT_OF_VIEW_THRESHOLD = 0.5f * OUT_OF_VIEW_PENALTY; + const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; avatar->simulate(deltaTime, inView); avatar->updateRenderItem(pendingChanges); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2263964e8f..dffdc8c1ac 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "AvatarLogging.h" @@ -2309,3 +2310,65 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra vec3FromScriptValue(intersection, value.intersection); } } + +const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; + +std::priority_queue AvatarData::sortAvatars( + QList avatarList, + const ViewFrustum& cameraView, + std::function lastUpdated, + std::function shouldIgnore) { + + uint64_t startTime = usecTimestampNow(); + + glm::vec3 frustumCenter = cameraView.getPosition(); + + std::priority_queue sortedAvatars; + { + PROFILE_RANGE(simulation, "sort"); + for (int32_t i = 0; i < avatarList.size(); ++i) { + const auto& avatar = avatarList.at(i); + + // FIXME - probably some lambda that allows the caller to reject some avatars + + if (shouldIgnore(avatar)) { + continue; + } + + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update + // (d) TIME_PENALTY to help recently updated entries sort toward back + glm::vec3 avatarPosition = avatar->getPosition(); + glm::vec3 offset = avatarPosition - frustumCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + + // FIXME - AvatarData has something equivolent to this + float radius = 1.0f; // avatar->getBoundingRadius(); + + const glm::vec3& forward = cameraView.getDirection(); + float apparentSize = radius / distance; + float cosineAngle = glm::length(glm::dot(offset, forward) * forward) / distance; + + // FIXME - probably some lambda that allows the caller to specify the "updated since" + uint64_t lastUpdatedTime = lastUpdated(avatar); // avatar->getLastRenderUpdateTime() + float age = (float)(startTime - lastUpdatedTime) / (float)(USECS_PER_SECOND); + + // NOTE: we are adding values of different units to get a single measure of "priority". + // Thus we multiply each component by a conversion "weight" that scales its units + // relative to the others. These weights are pure magic tuning and are hard coded in the + // relation below: (hint: unitary weights are not explicityly shown) + float priority = apparentSize + 0.25f * cosineAngle + age; + + // decrement priority of avatars outside keyhole + if (distance > cameraView.getCenterRadius()) { + if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { + priority += OUT_OF_VIEW_PENALTY; + } + } + sortedAvatars.push(AvatarPriority(avatar, priority)); + } + } + return sortedAvatars; +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 264da75de2..71ade436ff 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -14,6 +14,8 @@ #include #include +#include + /* VS2010 defines stdint.h, but not inttypes.h */ #if defined(_MSC_VER) typedef signed char int8_t; @@ -57,6 +59,7 @@ typedef unsigned long long quint64; #include #include #include +#include #include "AABox.h" #include "HeadData.h" @@ -304,6 +307,14 @@ public: RateCounter<> jointDataRate; }; +class AvatarPriority { +public: + AvatarPriority(AvatarSharedPointer a, float p) : avatar(a), priority(p) {} + AvatarSharedPointer avatar; + float priority; + // NOTE: we invert the less-than operator to sort high priorities to front + bool operator<(const AvatarPriority& other) const { return priority < other.priority; } +}; class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT @@ -539,7 +550,7 @@ public: void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } - const AABox& getLocalAABox() const { return _localAABox; } + //const AABox& getLocalAABox() const { return _localAABox; } int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); } int getAverageBytesReceivedPerSecond() const; @@ -578,6 +589,20 @@ public: } + bool shouldDie() const { + const qint64 AVATAR_SILENCE_THRESHOLD_USECS = 5 * USECS_PER_SECOND; + return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; + } + + static const float OUT_OF_VIEW_PENALTY; + + static std::priority_queue sortAvatars( + QList avatarList, + const ViewFrustum& cameraView, + std::function lastUpdated, + std::function shouldIgnore); + + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 00c515a635..cdbf5f2a85 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -190,3 +190,4 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol _lastOwnerSessionUUID = oldUUID; emit avatarSessionChangedEvent(sessionUUID, oldUUID); } + diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index dd097aef22..104ac83261 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -27,7 +27,6 @@ #include "AvatarData.h" - class AvatarHashMap : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY