Follow dynamic updates to hero zones; make reserved fraction a domain setting

This commit is contained in:
Simon Walton 2019-03-22 12:30:49 -07:00
parent 4ac25121e0
commit a1660dad95
9 changed files with 104 additions and 29 deletions

View file

@ -253,10 +253,26 @@ void AvatarMixer::start() {
int lockWait, nodeTransform, functor;
// Set our query each frame
{
_entityViewer.queryOctree();
}
// Dirty the hero status if there's been an entity change.
{
if (_dirtyHeroStatus) {
_dirtyHeroStatus = false;
nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) {
std::for_each(cbegin, cend, [](const SharedNodePointer& node) {
if (node->getType() == NodeType::Agent) {
auto& avatar = static_cast<AvatarMixerClientData*>(node->getLinkedData())->getAvatar();
avatar.setNeedsHeroCheck();
}
});
});
}
}
// Allow nodes to process any pending/queued packets across our worker threads
{
auto start = usecTimestampNow();
@ -973,19 +989,31 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
{
const QString CONNECTION_RATE = "connection_rate";
auto nodeList = DependencyManager::get<NodeList>();
auto defaultConnectionRate = nodeList->getMaxConnectionRate();
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toInt((int)defaultConnectionRate);
nodeList->setMaxConnectionRate(connectionRate);
bool success;
int connectionRate = avatarMixerGroupObject[CONNECTION_RATE].toString().toInt(&success);
if (success) {
nodeList->setMaxConnectionRate(connectionRate);
}
}
{ // Fraction of downstream bandwidth reserved for 'hero' avatars:
static const QString PRIORITY_FRACTION_KEY = "priority_fraction";
if (avatarMixerGroupObject.contains(PRIORITY_FRACTION_KEY)) {
bool isDouble = avatarMixerGroupObject[PRIORITY_FRACTION_KEY].isDouble();
float priorityFraction = float(avatarMixerGroupObject[PRIORITY_FRACTION_KEY].toDouble());
_slavePool.setPriorityReservedFraction(std::min(std::max(0.0f, priorityFraction), 1.0f));
qCDebug(avatars) << "Avatar mixer reserving" << priorityFraction << "of bandwidth for priority avatars";
}
}
const QString AVATARS_SETTINGS_KEY = "avatars";
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
float settingMinHeight = avatarMixerGroupObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
_domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
float settingMaxHeight = avatarMixerGroupObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
_domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
// make sure that the domain owner didn't flip min and max
@ -997,11 +1025,11 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
<< "and a maximum avatar height of" << _domainMaximumHeight;
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
_slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION]
_slaveSharedData.skeletonURLWhitelist = avatarMixerGroupObject[AVATAR_WHITELIST_OPTION]
.toString().split(',', QString::KeepEmptyParts);
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
_slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION]
_slaveSharedData.skeletonReplacementURL = avatarMixerGroupObject[REPLACEMENT_AVATAR_OPTION]
.toString();
if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) {
@ -1018,9 +1046,12 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
void AvatarMixer::setupEntityQuery() {
_entityViewer.init();
EntityTreePointer entityTree = _entityViewer.getTree();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
_slaveSharedData.entityTree = _entityViewer.getTree();
DependencyManager::set<AssignmentParentFinder>(entityTree);
connect(entityTree.get(), &EntityTree::addingEntityPointer, this, &AvatarMixer::entityAdded);
connect(entityTree.get(), &EntityTree::deletingEntityPointer, this, &AvatarMixer::entityChange);
// ES query: {"avatarPriority": true, "type": "Zone"}
QJsonObject priorityZoneQuery;
@ -1028,6 +1059,7 @@ void AvatarMixer::setupEntityQuery() {
priorityZoneQuery["type"] = "Zone";
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
_slaveSharedData.entityTree = entityTree;
}
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
@ -1064,6 +1096,25 @@ void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, Sh
}
}
void AvatarMixer::entityAdded(EntityItem* entity) {
if (entity->getType() == EntityTypes::Zone) {
_dirtyHeroStatus = true;
entity->registerChangeHandler([this](const EntityItemID& entityItemID) {
this->entityChange();
});
}
}
void AvatarMixer::entityRemoved(EntityItem * entity) {
if (entity->getType() == EntityTypes::Zone) {
_dirtyHeroStatus = true;
}
}
void AvatarMixer::entityChange() {
_dirtyHeroStatus = true;
}
void AvatarMixer::aboutToFinish() {
DependencyManager::destroy<ResourceManager>();
DependencyManager::destroy<ResourceCacheSharedItems>();

View file

@ -34,8 +34,8 @@ public:
static bool shouldReplicateTo(const Node& from, const Node& to) {
return to.getType() == NodeType::DownstreamAvatarMixer &&
to.getPublicSocket() != from.getPublicSocket() &&
to.getLocalSocket() != from.getLocalSocket();
to.getPublicSocket() != from.getPublicSocket() &&
to.getLocalSocket() != from.getLocalSocket();
}
public slots:
@ -80,6 +80,7 @@ private:
// Attach to entity tree for avatar-priority zone info.
EntityTreeHeadlessViewer _entityViewer;
bool _dirtyHeroStatus { true }; // Dirty the needs-hero-update
// FIXME - new throttling - use these values somehow
float _trailingMixRatio { 0.0f };
@ -146,6 +147,12 @@ private:
AvatarMixerSlavePool _slavePool;
SlaveSharedData _slaveSharedData;
public slots:
// Avatar zone possibly changed
void entityAdded(EntityItem* entity);
void entityRemoved(EntityItem* entity);
void entityChange();
};
#endif // hifi_AvatarMixer_h

View file

@ -141,22 +141,15 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
_avatar->setHasPriorityWithoutTimestampReset(oldHasPriority);
auto newPosition = getPosition();
if (newPosition != oldPosition) {
//#define AVATAR_HERO_TEST_HACK
#ifdef AVATAR_HERO_TEST_HACK
{
const static QString heroKey { "HERO" };
_avatar->setPriorityAvatar(_avatar->getDisplayName().contains(heroKey));
}
#else
if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) {
EntityTree& entityTree = *slaveSharedData.entityTree;
FindPriorityZone findPriorityZone { newPosition, false } ;
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
_avatar->setHasPriority(findPriorityZone.isInPriorityZone);
//if (findPriorityZone.isInPriorityZone) {
// qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
//}
#endif
_avatar->setNeedsHeroCheck(false);
if (findPriorityZone.isInPriorityZone) {
qCWarning(avatars) << "Avatar" << _avatar->getSessionDisplayName() << "in hero zone";
}
}
return true;

View file

@ -43,12 +43,14 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio) {
float maxKbpsPerNode, float throttlingRatio,
float priorityReservedFraction) {
_begin = begin;
_end = end;
_lastFrameTimestamp = lastFrameTimestamp;
_maxKbpsPerNode = maxKbpsPerNode;
_throttlingRatio = throttlingRatio;
_avatarHeroFraction = priorityReservedFraction;
}
void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) {
@ -308,7 +310,6 @@ namespace {
} // Close anonymous namespace.
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
const float AVATAR_HERO_FRACTION { 0.4f };
const Node* destinationNode = node.data();
auto nodeList = DependencyManager::get<NodeList>();
@ -343,7 +344,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// max number of avatarBytes per frame (13 900, typical)
const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * _avatarHeroFraction); // 5555, typical
// keep track of the number of other avatars held back in this frame
int numAvatarsHeldBack = 0;

View file

@ -110,7 +110,8 @@ public:
void configure(ConstIter begin, ConstIter end);
void configureBroadcast(ConstIter begin, ConstIter end,
p_high_resolution_clock::time_point lastFrameTimestamp,
float maxKbpsPerNode, float throttlingRatio);
float maxKbpsPerNode, float throttlingRatio,
float priorityReservedFraction);
void processIncomingPackets(const SharedNodePointer& node);
void broadcastAvatarData(const SharedNodePointer& node);
@ -140,6 +141,7 @@ private:
p_high_resolution_clock::time_point _lastFrameTimestamp;
float _maxKbpsPerNode { 0.0f };
float _throttlingRatio { 0.0f };
float _avatarHeroFraction { 0.4f };
AvatarMixerSlaveStats _stats;
SlaveSharedData* _sharedData;

View file

@ -76,7 +76,8 @@ void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end,
float maxKbpsPerNode, float throttlingRatio) {
_function = &AvatarMixerSlave::broadcastAvatarData;
_configure = [=](AvatarMixerSlave& slave) {
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio);
slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio,
_priorityReservedFraction);
};
run(begin, end);
}

View file

@ -73,7 +73,10 @@ public:
void each(std::function<void(AvatarMixerSlave& slave)> functor);
void setNumThreads(int numThreads);
int numThreads() { return _numThreads; }
int numThreads() const { return _numThreads; }
void setPriorityReservedFraction(float fraction) { _priorityReservedFraction = fraction; }
float getPriorityReservedFraction() const { return _priorityReservedFraction; }
private:
void run(ConstIter begin, ConstIter end);
@ -91,7 +94,11 @@ private:
ConditionVariable _poolCondition;
void (AvatarMixerSlave::*_function)(const SharedNodePointer& node);
std::function<void(AvatarMixerSlave&)> _configure;
// Set from Domain Settings:
float _priorityReservedFraction { 0.4f };
int _numThreads { 0 };
int _numStarted { 0 }; // guarded by _mutex
int _numFinished { 0 }; // guarded by _mutex
int _numStopped { 0 }; // guarded by _mutex

View file

@ -19,8 +19,12 @@
class MixerAvatar : public AvatarData {
public:
bool getNeedsHeroCheck() const { return _needsHeroCheck; }
void setNeedsHeroCheck(bool needsHeroCheck = true)
{ _needsHeroCheck = needsHeroCheck; }
private:
bool _needsHeroCheck { false };
};
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;

View file

@ -1310,6 +1310,15 @@
"placeholder": "50",
"default": "50",
"advanced": true
},
{
"name": "priority_fraction",
"type": "double",
"label": "Hero Bandwidth",
"help": "Fraction of downstream bandwidth reserved for avatars in 'Hero' zones",
"placeholder": "0.40",
"default": "0.40",
"advanced": true
}
]
},