mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-23 00:34:26 +02:00
hacking on using sorted avatars for bandwidth budget
This commit is contained in:
parent
06d78d488f
commit
2017ea4491
10 changed files with 199 additions and 64 deletions
|
@ -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) {
|
||||
|
|
|
@ -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<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
|
||||
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
|
||||
std::unordered_map<QUuid, uint64_t> _lastBroadcastTimes;
|
||||
|
||||
// this is a map of the last time we encoded an "other" avatar for
|
||||
// sending to "this" node
|
||||
|
|
|
@ -187,9 +187,75 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) {
|
|||
// setup a PacketList for the avatarPackets
|
||||
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
|
||||
|
||||
|
||||
QList<AvatarSharedPointer> avatarList;
|
||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||
|
||||
//qDebug() << "------------------------------";
|
||||
int listItem = 0;
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(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<AvatarPriority> 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();
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<AvatarSharedPointer> avatarList = avatarMap.values();
|
||||
ViewFrustum cameraView;
|
||||
qApp->copyDisplayViewFrustum(cameraView);
|
||||
glm::vec3 frustumCenter = cameraView.getPosition();
|
||||
|
||||
const float OUT_OF_VIEW_PENALTY = -10.0;
|
||||
std::priority_queue<AvatarPriority> sortedAvatars = AvatarData::sortAvatars(
|
||||
avatarList, cameraView,
|
||||
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
{
|
||||
PROFILE_RANGE(simulation, "sort");
|
||||
for (int32_t i = 0; i < avatarList.size(); ++i) {
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(avatarList.at(i));
|
||||
if (avatar == _myAvatar || !avatar->isInitialized()) {
|
||||
[](AvatarSharedPointer avatar)->uint64_t{
|
||||
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
|
||||
},
|
||||
|
||||
[this](AvatarSharedPointer avatar)->bool{
|
||||
const auto& castedAvatar = std::static_pointer_cast<Avatar>(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);
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <shared/JSONHelpers.h>
|
||||
#include <ShapeInfo.h>
|
||||
#include <AudioHelpers.h>
|
||||
#include <Profile.h>
|
||||
|
||||
#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<AvatarPriority> AvatarData::sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::function<uint64_t(AvatarSharedPointer)> lastUpdated,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
|
||||
|
||||
uint64_t startTime = usecTimestampNow();
|
||||
|
||||
glm::vec3 frustumCenter = cameraView.getPosition();
|
||||
|
||||
std::priority_queue<AvatarPriority> 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;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
/* 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 <ThreadSafeValueCache.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <shared/RateCounter.h>
|
||||
#include <ViewFrustum.h>
|
||||
|
||||
#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<Node>& 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<AvatarPriority> sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::function<uint64_t(AvatarSharedPointer)> lastUpdated,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore);
|
||||
|
||||
|
||||
public slots:
|
||||
void sendAvatarDataPacket();
|
||||
void sendIdentityPacket();
|
||||
|
|
|
@ -190,3 +190,4 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol
|
|||
_lastOwnerSessionUUID = oldUUID;
|
||||
emit avatarSessionChangedEvent(sessionUUID, oldUUID);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
#include "AvatarData.h"
|
||||
|
||||
|
||||
class AvatarHashMap : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
|
Loading…
Reference in a new issue