Merge pull request #15104 from roxanneskelly/79hero-master

merge PR 15026 15051 15067 15070 from v0.79.HERO-rc into master
This commit is contained in:
Roxanne Skelly 2019-03-06 13:06:45 -08:00 committed by GitHub
commit a54171d60c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 724 additions and 346 deletions

View file

@ -64,10 +64,6 @@ bool AudioMixerSlaveThread::try_pop(SharedNodePointer& node) {
return _pool._queue.try_pop(node); return _pool._queue.try_pop(node);
} }
#ifdef AUDIO_SINGLE_THREADED
static AudioMixerSlave slave;
#endif
void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) { void AudioMixerSlavePool::processPackets(ConstIter begin, ConstIter end) {
_function = &AudioMixerSlave::processPackets; _function = &AudioMixerSlave::processPackets;
_configure = [](AudioMixerSlave& slave) {}; _configure = [](AudioMixerSlave& slave) {};
@ -87,19 +83,9 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
_begin = begin; _begin = begin;
_end = end; _end = end;
#ifdef AUDIO_SINGLE_THREADED
_configure(slave);
std::for_each(begin, end, [&](const SharedNodePointer& node) {
_function(slave, node);
});
#else
// fill the queue // fill the queue
std::for_each(_begin, _end, [&](const SharedNodePointer& node) { std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
#if defined(__clang__) && defined(Q_OS_LINUX)
_queue.push(node); _queue.push(node);
#else
_queue.emplace(node);
#endif
}); });
{ {
@ -119,17 +105,12 @@ void AudioMixerSlavePool::run(ConstIter begin, ConstIter end) {
} }
assert(_queue.empty()); assert(_queue.empty());
#endif
} }
void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> functor) { void AudioMixerSlavePool::each(std::function<void(AudioMixerSlave& slave)> functor) {
#ifdef AUDIO_SINGLE_THREADED
functor(slave);
#else
for (auto& slave : _slaves) { for (auto& slave : _slaves) {
functor(*slave.get()); functor(*slave.get());
} }
#endif
} }
void AudioMixerSlavePool::setNumThreads(int numThreads) { void AudioMixerSlavePool::setNumThreads(int numThreads) {
@ -155,9 +136,6 @@ void AudioMixerSlavePool::setNumThreads(int numThreads) {
void AudioMixerSlavePool::resize(int numThreads) { void AudioMixerSlavePool::resize(int numThreads) {
assert(_numThreads == (int)_slaves.size()); assert(_numThreads == (int)_slaves.size());
#ifdef AUDIO_SINGLE_THREADED
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
#else
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads); qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
Lock lock(_mutex); Lock lock(_mutex);
@ -205,5 +183,4 @@ void AudioMixerSlavePool::resize(int numThreads) {
_numThreads = _numStarted = _numFinished = numThreads; _numThreads = _numStarted = _numFinished = numThreads;
assert(_numThreads == (int)_slaves.size()); assert(_numThreads == (int)_slaves.size());
#endif
} }

View file

@ -23,6 +23,7 @@
#include <QtCore/QRegularExpression> #include <QtCore/QRegularExpression>
#include <QtCore/QTimer> #include <QtCore/QTimer>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QJsonDocument>
#include <AABox.h> #include <AABox.h>
#include <AvatarLogging.h> #include <AvatarLogging.h>
@ -32,6 +33,8 @@
#include <SharedUtil.h> #include <SharedUtil.h>
#include <UUID.h> #include <UUID.h>
#include <TryLocker.h> #include <TryLocker.h>
#include "../AssignmentDynamicFactory.h"
#include "../entities/AssignmentParentFinder.h"
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
@ -55,6 +58,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
ThreadedAssignment(message), ThreadedAssignment(message),
_slavePool(&_slaveSharedData) _slavePool(&_slaveSharedData)
{ {
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
DependencyManager::set<AssignmentDynamicFactory>();
// make sure we hear about node kills so we can tell the other nodes // make sure we hear about node kills so we can tell the other nodes
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
@ -69,6 +75,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
this, "handleOctreePacket");
packetReceiver.registerListenerForTypes({ packetReceiver.registerListenerForTypes({
PacketType::ReplicatedAvatarIdentity, PacketType::ReplicatedAvatarIdentity,
@ -240,6 +248,10 @@ void AvatarMixer::start() {
int lockWait, nodeTransform, functor; int lockWait, nodeTransform, functor;
{
_entityViewer.queryOctree();
}
// Allow nodes to process any pending/queued packets across our worker threads // Allow nodes to process any pending/queued packets across our worker threads
{ {
auto start = usecTimestampNow(); auto start = usecTimestampNow();
@ -252,6 +264,10 @@ void AvatarMixer::start() {
}, &lockWait, &nodeTransform, &functor); }, &lockWait, &nodeTransform, &functor);
auto end = usecTimestampNow(); auto end = usecTimestampNow();
_processQueuedAvatarDataPacketsElapsedTime += (end - start); _processQueuedAvatarDataPacketsElapsedTime += (end - start);
_broadcastAvatarDataLockWait += lockWait;
_broadcastAvatarDataNodeTransform += nodeTransform;
_broadcastAvatarDataNodeFunctor += functor;
} }
// process pending display names... this doesn't currently run on multiple threads, because it // process pending display names... this doesn't currently run on multiple threads, because it
@ -269,6 +285,10 @@ void AvatarMixer::start() {
}, &lockWait, &nodeTransform, &functor); }, &lockWait, &nodeTransform, &functor);
auto end = usecTimestampNow(); auto end = usecTimestampNow();
_displayNameManagementElapsedTime += (end - start); _displayNameManagementElapsedTime += (end - start);
_broadcastAvatarDataLockWait += lockWait;
_broadcastAvatarDataNodeTransform += nodeTransform;
_broadcastAvatarDataNodeFunctor += functor;
} }
// this is where we need to put the real work... // this is where we need to put the real work...
@ -691,8 +711,11 @@ void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer<ReceivedMessage
} }
void AvatarMixer::sendStatsPacket() { void AvatarMixer::sendStatsPacket() {
auto start = usecTimestampNow(); if (!_numTightLoopFrames) {
return;
}
auto start = usecTimestampNow();
QJsonObject statsObject; QJsonObject statsObject;
@ -775,6 +798,7 @@ void AvatarMixer::sendStatsPacket() {
slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent); slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent);
slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent); slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent);
slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent); slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent);
slavesAggregatObject["sent_7_averageHeroAvatars"] = TIGHT_LOOP_STAT(aggregateStats.numHeroesIncluded);
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime); slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime); slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
@ -882,13 +906,15 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
void AvatarMixer::domainSettingsRequestComplete() { void AvatarMixer::domainSettingsRequestComplete() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
nodeList->addSetOfNodeTypesToNodeInterestSet({ nodeList->addSetOfNodeTypesToNodeInterestSet({
NodeType::Agent, NodeType::EntityScriptServer, NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer,
NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer
}); });
// parse the settings to pull out the values we need // parse the settings to pull out the values we need
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
setupEntityQuery();
// start our tight loop... // start our tight loop...
start(); start();
} }
@ -939,6 +965,14 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads."; qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads.";
} }
{
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);
}
const QString AVATARS_SETTINGS_KEY = "avatars"; const QString AVATARS_SETTINGS_KEY = "avatars";
static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
@ -976,3 +1010,58 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString()); qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
} }
} }
void AvatarMixer::setupEntityQuery() {
_entityViewer.init();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
_slaveSharedData.entityTree = _entityViewer.getTree();
// ES query: {"avatarPriority": true, "type": "Zone"}
QJsonObject priorityZoneQuery;
priorityZoneQuery["avatarPriority"] = true;
priorityZoneQuery["type"] = "Zone";
_entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery);
}
void AvatarMixer::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
PacketType packetType = message->getType();
switch (packetType) {
case PacketType::OctreeStats:
{ // Ignore stats, but may have a different Entity packet appended.
OctreeHeadlessViewer::parseOctreeStats(message, senderNode);
const auto piggyBackedSizeWithHeader = message->getBytesLeftToRead();
if (piggyBackedSizeWithHeader > 0) {
// pull out the piggybacked packet and create a new QSharedPointer<NLPacket> for it
auto buffer = std::unique_ptr<char[]>(new char[piggyBackedSizeWithHeader]);
memcpy(buffer.get(), message->getRawMessage() + message->getPosition(), piggyBackedSizeWithHeader);
auto newPacket = NLPacket::fromReceivedPacket(std::move(buffer), piggyBackedSizeWithHeader, message->getSenderSockAddr());
auto newMessage = QSharedPointer<ReceivedMessage>::create(*newPacket);
handleOctreePacket(newMessage, senderNode);
}
break;
}
case PacketType::EntityData:
_entityViewer.processDatagram(*message, senderNode);
break;
case PacketType::EntityErase:
_entityViewer.processEraseMessage(*message, senderNode);
break;
default:
qCDebug(avatars) << "Unexpected packet type:" << packetType;
break;
}
}
void AvatarMixer::aboutToFinish() {
DependencyManager::destroy<AssignmentDynamicFactory>();
DependencyManager::destroy<AssignmentParentFinder>();
ThreadedAssignment::aboutToFinish();
}

View file

@ -20,6 +20,7 @@
#include <PortableHighResolutionClock.h> #include <PortableHighResolutionClock.h>
#include <ThreadedAssignment.h> #include <ThreadedAssignment.h>
#include "../entities/EntityTreeHeadlessViewer.h"
#include "AvatarMixerClientData.h" #include "AvatarMixerClientData.h"
#include "AvatarMixerSlavePool.h" #include "AvatarMixerSlavePool.h"
@ -29,6 +30,7 @@ class AvatarMixer : public ThreadedAssignment {
Q_OBJECT Q_OBJECT
public: public:
AvatarMixer(ReceivedMessage& message); AvatarMixer(ReceivedMessage& message);
virtual void aboutToFinish() override;
static bool shouldReplicateTo(const Node& from, const Node& to) { static bool shouldReplicateTo(const Node& from, const Node& to) {
return to.getType() == NodeType::DownstreamAvatarMixer && return to.getType() == NodeType::DownstreamAvatarMixer &&
@ -57,6 +59,7 @@ private slots:
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message); void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
void domainSettingsRequestComplete(); void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void start(); void start();
private: private:
@ -71,8 +74,13 @@ private:
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node); void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
void setupEntityQuery();
p_high_resolution_clock::time_point _lastFrameTimestamp; p_high_resolution_clock::time_point _lastFrameTimestamp;
// Attach to entity tree for avatar-priority zone info.
EntityTreeHeadlessViewer _entityViewer;
// FIXME - new throttling - use these values somehow // FIXME - new throttling - use these values somehow
float _trailingMixRatio { 0.0f }; float _trailingMixRatio { 0.0f };
float _throttlingRatio { 0.0f }; float _throttlingRatio { 0.0f };

View file

@ -16,6 +16,10 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include <NodeList.h> #include <NodeList.h>
#include <EntityTree.h>
#include <ZoneEntityItem.h>
#include "AvatarLogging.h"
#include "AvatarMixerSlave.h" #include "AvatarMixerSlave.h"
@ -62,7 +66,7 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
switch (packet->getType()) { switch (packet->getType()) {
case PacketType::AvatarData: case PacketType::AvatarData:
parseData(*packet); parseData(*packet, slaveSharedData);
break; break;
case PacketType::SetAvatarTraits: case PacketType::SetAvatarTraits:
processSetTraitsMessage(*packet, slaveSharedData, *node); processSetTraitsMessage(*packet, slaveSharedData, *node);
@ -80,7 +84,42 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
return packetsProcessed; return packetsProcessed;
} }
int AvatarMixerClientData::parseData(ReceivedMessage& message) { namespace {
using std::static_pointer_cast;
// Operator to find if a point is within an avatar-priority (hero) Zone Entity.
struct FindPriorityZone {
glm::vec3 position;
bool isInPriorityZone { false };
float zoneVolume { std::numeric_limits<float>::max() };
static bool operation(const OctreeElementPointer& element, void* extraData) {
auto findPriorityZone = static_cast<FindPriorityZone*>(extraData);
if (element->getAACube().contains(findPriorityZone->position)) {
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) {
if (item->getType() == EntityTypes::Zone
&& item->contains(findPriorityZone->position)) {
auto zoneItem = static_pointer_cast<ZoneEntityItem>(item);
if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) {
float volume = zoneItem->getVolumeEstimate();
if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins
findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED;
findPriorityZone->zoneVolume = volume;
}
}
}
});
return true; // Keep recursing
} else { // Position isn't within this subspace, so end recursion.
return false;
}
}
};
} // Close anonymous namespace.
int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) {
// pull the sequence number from the data first // pull the sequence number from the data first
uint16_t sequenceNumber; uint16_t sequenceNumber;
@ -90,9 +129,33 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
incrementNumOutOfOrderSends(); incrementNumOutOfOrderSends();
} }
_lastReceivedSequenceNumber = sequenceNumber; _lastReceivedSequenceNumber = sequenceNumber;
glm::vec3 oldPosition = getPosition();
// compute the offset to the data payload // compute the offset to the data payload
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) {
return false;
}
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
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
}
return true;
} }
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,

View file

@ -21,7 +21,7 @@
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <AvatarData.h> #include "MixerAvatar.h"
#include <AssociatedTraitValues.h> #include <AssociatedTraitValues.h>
#include <NodeData.h> #include <NodeData.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
@ -45,11 +45,12 @@ public:
using HRCTime = p_high_resolution_clock::time_point; using HRCTime = p_high_resolution_clock::time_point;
using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>; using PerNodeTraitVersions = std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions>;
int parseData(ReceivedMessage& message) override; using NodeData::parseData; // Avoid clang warning about hiding.
AvatarData& getAvatar() { return *_avatar; } int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData);
const AvatarData& getAvatar() const { return *_avatar; } MixerAvatar& getAvatar() { return *_avatar; }
const AvatarData* getConstAvatarData() const { return _avatar.get(); } const MixerAvatar& getAvatar() const { return *_avatar; }
AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; } const MixerAvatar* getConstAvatarData() const { return _avatar.get(); }
MixerAvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const; uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const;
void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber) void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber)
@ -163,7 +164,7 @@ private:
}; };
PacketQueue _packetQueue; PacketQueue _packetQueue;
AvatarSharedPointer _avatar { new AvatarData() }; MixerAvatarSharedPointer _avatar { new MixerAvatar() };
uint16_t _lastReceivedSequenceNumber { 0 }; uint16_t _lastReceivedSequenceNumber { 0 };
std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers; std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers;

View file

@ -281,7 +281,34 @@ AABox computeBubbleBox(const AvatarData& avatar, float bubbleExpansionFactor) {
return box; return box;
} }
namespace {
class SortableAvatar : public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const MixerAvatar* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {
}
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
float getRadius() const override {
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
}
uint64_t getTimestamp() const override {
return _lastEncodeTime;
}
const Node* getNode() const { return _node; }
const MixerAvatar* getAvatar() const { return _avatar; }
private:
const MixerAvatar* _avatar;
const Node* _node;
uint64_t _lastEncodeTime;
};
} // Close anonymous namespace.
void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) { void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) {
const float AVATAR_HERO_FRACTION { 0.4f };
const Node* destinationNode = node.data(); const Node* destinationNode = node.data();
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -293,29 +320,30 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
_stats.nodesBroadcastedTo++; _stats.nodesBroadcastedTo++;
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData()); AvatarMixerClientData* destinationNodeData = reinterpret_cast<AvatarMixerClientData*>(destinationNode->getLinkedData());
nodeData->resetInViewStats(); destinationNodeData->resetInViewStats();
const AvatarData& avatar = nodeData->getAvatar(); const AvatarData& avatar = destinationNodeData->getAvatar();
glm::vec3 myPosition = avatar.getClientGlobalPosition(); glm::vec3 destinationPosition = avatar.getClientGlobalPosition();
// reset the internal state for correct random number distribution // reset the internal state for correct random number distribution
distribution.reset(); distribution.reset();
// Estimate number to sort on number sent last frame (with min. of 20). // Estimate number to sort on number sent last frame (with min. of 20).
const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20); const int numToSendEst = std::max(int(destinationNodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
// reset the number of sent avatars // reset the number of sent avatars
nodeData->resetNumAvatarsSentLastFrame(); destinationNodeData->resetNumAvatarsSentLastFrame();
// keep track of outbound data rate specifically for avatar data // keep track of outbound data rate specifically for avatar data
int numAvatarDataBytes = 0; int numAvatarDataBytes = 0;
int identityBytesSent = 0; int identityBytesSent = 0;
int traitBytesSent = 0; int traitBytesSent = 0;
// max number of avatarBytes per frame // max number of avatarBytes per frame (13 900, typical)
int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND); const int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
const int maxHeroBytesPerFrame = int(maxAvatarBytesPerFrame * AVATAR_HERO_FRACTION); // 5555, typical
// keep track of the number of other avatars held back in this frame // keep track of the number of other avatars held back in this frame
int numAvatarsHeldBack = 0; int numAvatarsHeldBack = 0;
@ -325,8 +353,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// When this is true, the AvatarMixer will send Avatar data to a client // When this is true, the AvatarMixer will send Avatar data to a client
// about avatars they've ignored or that are out of view // about avatars they've ignored or that are out of view
bool PALIsOpen = nodeData->getRequestsDomainListData(); bool PALIsOpen = destinationNodeData->getRequestsDomainListData();
bool PALWasOpen = nodeData->getPrevRequestsDomainListData(); bool PALWasOpen = destinationNodeData->getPrevRequestsDomainListData();
// When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them
bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick(); bool getsAnyIgnored = PALIsOpen && destinationNode->getCanKick();
@ -337,36 +365,23 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// compute node bounding box // compute node bounding box
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR); AABox destinationNodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
class SortableAvatar: public PrioritySortUtil::Sortable {
public:
SortableAvatar() = delete;
SortableAvatar(const AvatarData* avatar, const Node* avatarNode, uint64_t lastEncodeTime)
: _avatar(avatar), _node(avatarNode), _lastEncodeTime(lastEncodeTime) {}
glm::vec3 getPosition() const override { return _avatar->getClientGlobalPosition(); }
float getRadius() const override {
glm::vec3 nodeBoxScale = _avatar->getGlobalBoundingBox().getScale();
return 0.5f * glm::max(nodeBoxScale.x, glm::max(nodeBoxScale.y, nodeBoxScale.z));
}
uint64_t getTimestamp() const override {
return _lastEncodeTime;
}
const Node* getNode() const { return _node; }
private:
const AvatarData* _avatar;
const Node* _node;
uint64_t _lastEncodeTime;
};
// prepare to sort // prepare to sort
const auto& cameraViews = nodeData->getViewFrustums(); const auto& cameraViews = destinationNodeData->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}
};
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();
@ -376,47 +391,47 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
continue; continue;
} }
auto avatarNode = otherNodeRaw; auto sourceAvatarNode = otherNodeRaw;
bool shouldIgnore = false; bool sendAvatar = true; // We will consider this source avatar for sending.
// We ignore other nodes for a couple of reasons: // We ignore other nodes for a couple of reasons:
// 1) ignore bubbles and ignore specific node // 1) ignore bubbles and ignore specific node
// 2) the node hasn't really updated it's frame data recently, this can // 2) the node hasn't really updated it's frame data recently, this can
// happen if for example the avatar is connected on a desktop and sending // happen if for example the avatar is connected on a desktop and sending
// updates at ~30hz. So every 3 frames we skip a frame. // updates at ~30hz. So every 3 frames we skip a frame.
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map assert(sourceAvatarNode); // we can't have gotten here without the avatarData being a valid key in the map
const AvatarMixerClientData* avatarClientNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData()); const AvatarMixerClientData* sourceAvatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceAvatarNode->getLinkedData());
assert(avatarClientNodeData); // we can't have gotten here without avatarNode having valid data assert(sourceAvatarNodeData); // we can't have gotten here without sourceAvatarNode having valid data
quint64 startIgnoreCalculation = usecTimestampNow(); quint64 startIgnoreCalculation = usecTimestampNow();
// make sure we have data for this avatar, that it isn't the same node, // make sure we have data for this avatar, that it isn't the same node,
// and isn't an avatar that the viewing node has ignored // and isn't an avatar that the viewing node has ignored
// or that has ignored the viewing node // or that has ignored the viewing node
if ((destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) if ((destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) && !PALIsOpen)
|| (avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) { || (sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()) && !getsAnyIgnored)) {
shouldIgnore = true; sendAvatar = false;
} else { } else {
// Check to see if the space bubble is enabled // Check to see if the space bubble is enabled
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { if (destinationNodeData->isIgnoreRadiusEnabled() || (sourceAvatarNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
// Perform the collision check between the two bounding boxes // Perform the collision check between the two bounding boxes
AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox(); AABox sourceNodeBox = sourceAvatarNodeData->getAvatar().getDefaultBubbleBox();
if (nodeBox.touches(otherNodeBox)) { if (destinationNodeBox.touches(sourceNodeBox)) {
nodeData->ignoreOther(destinationNode, avatarNode); destinationNodeData->ignoreOther(destinationNode, sourceAvatarNode);
shouldIgnore = !getsAnyIgnored; sendAvatar = getsAnyIgnored;
} }
} }
// Not close enough to ignore // Not close enough to ignore
if (!shouldIgnore) { if (sendAvatar) {
nodeData->removeFromRadiusIgnoringSet(avatarNode->getUUID()); destinationNodeData->removeFromRadiusIgnoringSet(sourceAvatarNode->getUUID());
} }
} }
if (!shouldIgnore) { if (sendAvatar) {
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID()); AvatarDataSequenceNumber lastSeqToReceiver = destinationNodeData->getLastBroadcastSequenceNumber(sourceAvatarNode->getLocalID());
AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber(); AvatarDataSequenceNumber lastSeqFromSender = sourceAvatarNodeData->getLastReceivedSequenceNumber();
// FIXME - This code does appear to be working. But it seems brittle. // FIXME - This code does appear to be working. But it seems brittle.
// It supports determining if the frame of data for this "other" // It supports determining if the frame of data for this "other"
@ -430,26 +445,28 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// or that somehow we haven't sent // or that somehow we haven't sent
if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) {
++numAvatarsHeldBack; ++numAvatarsHeldBack;
shouldIgnore = true; sendAvatar = false;
} else if (lastSeqFromSender == 0) { } else if (lastSeqFromSender == 0) {
// We have have not yet recieved any data about this avatar. Ignore it for now // We have have not yet received any data about this avatar. Ignore it for now
// This is important for Agent scripts that are not avatar // This is important for Agent scripts that are not avatar
// so that they don't appear to be an avatar at the origin // so that they don't appear to be an avatar at the origin
shouldIgnore = true; sendAvatar = false;
} else if (lastSeqFromSender - lastSeqToReceiver > 1) { } else if (lastSeqFromSender - lastSeqToReceiver > 1) {
// this is a skip - we still send the packet but capture the presence of the skip so we see it happening // this is a skip - we still send the packet but capture the presence of the skip so we see it happening
++numAvatarsWithSkippedFrames; ++numAvatarsWithSkippedFrames;
} }
} }
quint64 endIgnoreCalculation = usecTimestampNow(); quint64 endIgnoreCalculation = usecTimestampNow();
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
if (!shouldIgnore) { if (sendAvatar) {
// sort this one for later // sort this one for later
const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData(); const MixerAvatar* avatarNodeData = sourceAvatarNodeData->getConstAvatarData();
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID()); auto lastEncodeTime = destinationNodeData->getLastOtherAvatarEncodeTime(sourceAvatarNode->getLocalID());
sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime)); avatarPriorityQueues[avatarNodeData->getHasPriority() ? kHero : kNonhero].push(
SortableAvatar(avatarNodeData, sourceAvatarNode, 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
@ -459,37 +476,40 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
// will be sent when it doesn't need to be (but where it _should_ be OK to send). // will be sent when it doesn't need to be (but where it _should_ be OK to send).
// However, it's less heavy-handed than using `shouldIgnore`. // However, it's less heavy-handed than using `shouldIgnore`.
if (PALWasOpen && !PALIsOpen && if (PALWasOpen && !PALIsOpen &&
(destinationNode->isIgnoringNodeWithID(avatarNode->getUUID()) || (destinationNode->isIgnoringNodeWithID(sourceAvatarNode->getUUID()) ||
avatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) { sourceAvatarNode->isIgnoringNodeWithID(destinationNode->getUUID()))) {
// ...send a Kill Packet to Node A, instructing Node A to kill Avatar B, // ...send a Kill Packet to Node A, instructing Node A to kill Avatar B,
// then have Node A cleanup the killed Node B. // then have Node A cleanup the killed Node B.
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true); auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason), true);
packet->write(avatarNode->getUUID().toRfc4122()); packet->write(sourceAvatarNode->getUUID().toRfc4122());
packet->writePrimitive(KillAvatarReason::AvatarIgnored); packet->writePrimitive(KillAvatarReason::AvatarIgnored);
nodeList->sendPacket(std::move(packet), *destinationNode); nodeList->sendPacket(std::move(packet), *destinationNode);
nodeData->cleanupKilledNode(avatarNode->getUUID(), avatarNode->getLocalID()); destinationNodeData->cleanupKilledNode(sourceAvatarNode->getUUID(), sourceAvatarNode->getLocalID());
} }
nodeData->setPrevRequestsDomainListData(PALIsOpen); destinationNodeData->setPrevRequestsDomainListData(PALIsOpen);
} }
// 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); // Loop over two priorities - hero avatars then everyone else:
for (PriorityVariants currentVariant = kHero; currentVariant <= kNonhero; ++((int&)currentVariant)) {
const auto& sortedAvatarVector = avatarPriorityQueues[currentVariant].getSortedVector(numToSendEst);
for (const auto& sortedAvatar : sortedAvatarVector) { for (const auto& sortedAvatar : sortedAvatarVector) {
const Node* otherNode = sortedAvatar.getNode(); const Node* sourceNode = sortedAvatar.getNode();
auto lastEncodeForOther = sortedAvatar.getTimestamp(); auto lastEncodeForOther = sortedAvatar.getTimestamp();
assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map assert(sourceNode); // 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;
@ -508,33 +528,38 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
} }
} }
bool overHeroBudget = currentVariant == kHero && numAvatarDataBytes > maxHeroBytesPerFrame;
if (overHeroBudget) {
break; // No more heroes (this frame).
}
auto startAvatarDataPacking = chrono::high_resolution_clock::now(); auto startAvatarDataPacking = chrono::high_resolution_clock::now();
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData()); const AvatarMixerClientData* sourceNodeData = reinterpret_cast<const AvatarMixerClientData*>(sourceNode->getLinkedData());
const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); const MixerAvatar* sourceAvatar = sourceNodeData->getConstAvatarData();
// Typically all out-of-view avatars but such avatars' priorities will rise with time: // Typically all out-of-view avatars but such avatars' priorities will rise with time:
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; bool isLowerPriority = currentVariant != kHero && sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD; // XXX: hero handling?
if (isLowerPriority) { if (isLowerPriority) {
detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData;
nodeData->incrementAvatarOutOfView(); destinationNodeData->incrementAvatarOutOfView();
} else if (!overBudget) { } else if (!overBudget) {
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData; detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
nodeData->incrementAvatarInView(); destinationNodeData->incrementAvatarInView();
// If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO // 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. // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A.
if (otherAvatar->hasProcessedFirstIdentity() if (sourceAvatar->hasProcessedFirstIdentity()
&& nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) { && destinationNodeData->getLastBroadcastTime(sourceNode->getLocalID()) <= sourceNodeData->getIdentityChangeTimestamp()) {
identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode); identityBytesSent += sendIdentityPacket(*identityPacketList, sourceNodeData, *destinationNode);
// remember the last time we sent identity details about this other node to the receiver // remember the last time we sent identity details about this other node to the receiver
nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow()); destinationNodeData->setLastBroadcastTime(sourceNode->getLocalID(), usecTimestampNow());
} }
} }
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID()); QVector<JointData>& lastSentJointsForOther = destinationNodeData->getLastOtherAvatarSentJoints(sourceNode->getLocalID());
const bool distanceAdjust = true; const bool distanceAdjust = true;
const bool dropFaceTracking = false; const bool dropFaceTracking = false;
@ -543,8 +568,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
do { do {
auto startSerialize = chrono::high_resolution_clock::now(); auto startSerialize = chrono::high_resolution_clock::now();
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, QByteArray bytes = sourceAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
sendStatus, dropFaceTracking, distanceAdjust, myPosition, sendStatus, dropFaceTracking, distanceAdjust, destinationPosition,
&lastSentJointsForOther, avatarSpaceAvailable); &lastSentJointsForOther, avatarSpaceAvailable);
auto endSerialize = chrono::high_resolution_clock::now(); auto endSerialize = chrono::high_resolution_clock::now();
_stats.toByteArrayElapsedTime += _stats.toByteArrayElapsedTime +=
@ -564,30 +589,40 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
if (detail != AvatarData::NoData) { if (detail != AvatarData::NoData) {
_stats.numOthersIncluded++; _stats.numOthersIncluded++;
if (sourceAvatar->getHasPriority()) {
_stats.numHeroesIncluded++;
}
// increment the number of avatars sent to this receiver // increment the number of avatars sent to this receiver
nodeData->incrementNumAvatarsSentLastFrame(); destinationNodeData->incrementNumAvatarsSentLastFrame();
// set the last sent sequence number for this sender on the receiver // set the last sent sequence number for this sender on the receiver
nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(), destinationNodeData->setLastBroadcastSequenceNumber(sourceNode->getLocalID(),
otherNodeData->getLastReceivedSequenceNumber()); sourceNodeData->getLastReceivedSequenceNumber());
nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow()); destinationNodeData->setLastOtherAvatarEncodeTime(sourceNode->getLocalID(), usecTimestampNow());
} }
auto endAvatarDataPacking = chrono::high_resolution_clock::now(); auto endAvatarDataPacking = chrono::high_resolution_clock::now();
_stats.avatarDataPackingElapsedTime += _stats.avatarDataPackingElapsedTime +=
(quint64) chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count(); (quint64)chrono::duration_cast<chrono::microseconds>(endAvatarDataPacking - startAvatarDataPacking).count();
if (!overBudget) { if (!overBudget) {
// use helper to add any changed traits to our packet list // use helper to add any changed traits to our packet list
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList); traitBytesSent += addChangedTraitsToBulkPacket(destinationNodeData, sourceNodeData, *traitsPacketList);
} }
numAvatarsSent++;
remainingAvatars--; remainingAvatars--;
} }
if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) { if (currentVariant == kHero) { // Dump any remaining heroes into the commoners.
qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame() for (auto avIter = sortedAvatarVector.begin() + numAvatarsSent; avIter < sortedAvatarVector.end(); ++avIter) {
avatarPriorityQueues[kNonhero].push(*avIter);
}
}
}
if (destinationNodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
qCWarning(avatars) << "More avatars sent than upper estimate" << destinationNodeData->getNumAvatarsSentLastFrame()
<< " / " << numToSendEst; << " / " << numToSendEst;
} }
@ -618,12 +653,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
} }
// record the bytes sent for other avatar data in the AvatarMixerClientData // record the bytes sent for other avatar data in the AvatarMixerClientData
nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent); destinationNodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent);
// record the number of avatars held back this frame // record the number of avatars held back this frame
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); destinationNodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); destinationNodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
quint64 endPacketSending = usecTimestampNow(); quint64 endPacketSending = usecTimestampNow();
_stats.packetSendingElapsedTime += (endPacketSending - startPacketSending); _stats.packetSendingElapsedTime += (endPacketSending - startPacketSending);

View file

@ -32,6 +32,7 @@ public:
int numIdentityPacketsSent { 0 }; int numIdentityPacketsSent { 0 };
int numOthersIncluded { 0 }; int numOthersIncluded { 0 };
int overBudgetAvatars { 0 }; int overBudgetAvatars { 0 };
int numHeroesIncluded { 0 };
quint64 ignoreCalculationElapsedTime { 0 }; quint64 ignoreCalculationElapsedTime { 0 };
quint64 avatarDataPackingElapsedTime { 0 }; quint64 avatarDataPackingElapsedTime { 0 };
@ -57,6 +58,7 @@ public:
numIdentityPacketsSent = 0; numIdentityPacketsSent = 0;
numOthersIncluded = 0; numOthersIncluded = 0;
overBudgetAvatars = 0; overBudgetAvatars = 0;
numHeroesIncluded = 0;
ignoreCalculationElapsedTime = 0; ignoreCalculationElapsedTime = 0;
avatarDataPackingElapsedTime = 0; avatarDataPackingElapsedTime = 0;
@ -80,6 +82,7 @@ public:
numIdentityPacketsSent += rhs.numIdentityPacketsSent; numIdentityPacketsSent += rhs.numIdentityPacketsSent;
numOthersIncluded += rhs.numOthersIncluded; numOthersIncluded += rhs.numOthersIncluded;
overBudgetAvatars += rhs.overBudgetAvatars; overBudgetAvatars += rhs.overBudgetAvatars;
numHeroesIncluded += rhs.numHeroesIncluded;
ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime;
avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime;
@ -90,9 +93,13 @@ public:
} }
}; };
class EntityTree;
using EntityTreePointer = std::shared_ptr<EntityTree>;
struct SlaveSharedData { struct SlaveSharedData {
QStringList skeletonURLWhitelist; QStringList skeletonURLWhitelist;
QUrl skeletonReplacementURL; QUrl skeletonReplacementURL;
EntityTreePointer entityTree;
}; };
class AvatarMixerSlave { class AvatarMixerSlave {

View file

@ -63,10 +63,6 @@ bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) {
return _pool._queue.try_pop(node); return _pool._queue.try_pop(node);
} }
#ifdef AVATAR_SINGLE_THREADED
static AvatarMixerSlave slave;
#endif
void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) {
_function = &AvatarMixerSlave::processIncomingPackets; _function = &AvatarMixerSlave::processIncomingPackets;
_configure = [=](AvatarMixerSlave& slave) { _configure = [=](AvatarMixerSlave& slave) {
@ -89,19 +85,9 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
_begin = begin; _begin = begin;
_end = end; _end = end;
#ifdef AUDIO_SINGLE_THREADED
_configure(slave);
std::for_each(begin, end, [&](const SharedNodePointer& node) {
_function(slave, node);
});
#else
// fill the queue // fill the queue
std::for_each(_begin, _end, [&](const SharedNodePointer& node) { std::for_each(_begin, _end, [&](const SharedNodePointer& node) {
#if defined(__clang__) && defined(Q_OS_LINUX)
_queue.push(node); _queue.push(node);
#else
_queue.emplace(node);
#endif
}); });
{ {
@ -121,18 +107,13 @@ void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) {
} }
assert(_queue.empty()); assert(_queue.empty());
#endif
} }
void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) { void AvatarMixerSlavePool::each(std::function<void(AvatarMixerSlave& slave)> functor) {
#ifdef AVATAR_SINGLE_THREADED
functor(slave);
#else
for (auto& slave : _slaves) { for (auto& slave : _slaves) {
functor(*slave.get()); functor(*slave.get());
} }
#endif
} }
void AvatarMixerSlavePool::setNumThreads(int numThreads) { void AvatarMixerSlavePool::setNumThreads(int numThreads) {
@ -158,9 +139,6 @@ void AvatarMixerSlavePool::setNumThreads(int numThreads) {
void AvatarMixerSlavePool::resize(int numThreads) { void AvatarMixerSlavePool::resize(int numThreads) {
assert(_numThreads == (int)_slaves.size()); assert(_numThreads == (int)_slaves.size());
#ifdef AVATAR_SINGLE_THREADED
qDebug("%s: running single threaded", __FUNCTION__, numThreads);
#else
qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads); qDebug("%s: set %d threads (was %d)", __FUNCTION__, numThreads, _numThreads);
Lock lock(_mutex); Lock lock(_mutex);
@ -208,5 +186,4 @@ void AvatarMixerSlavePool::resize(int numThreads) {
_numThreads = _numStarted = _numFinished = numThreads; _numThreads = _numStarted = _numFinished = numThreads;
assert(_numThreads == (int)_slaves.size()); assert(_numThreads == (int)_slaves.size());
#endif
} }

View file

@ -0,0 +1,31 @@
//
// MixerAvatar.h
// assignment-client/src/avatars
//
// Created by Simon Walton Feb 2019.
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Avatar class for use within the avatar mixer - encapsulates data required only for
// sorting priorities within the mixer.
#ifndef hifi_MixerAvatar_h
#define hifi_MixerAvatar_h
#include <AvatarData.h>
class MixerAvatar : public AvatarData {
public:
bool getHasPriority() const { return _hasPriority; }
void setHasPriority(bool hasPriority) { _hasPriority = hasPriority; }
private:
bool _hasPriority { false };
};
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
#endif // hifi_MixerAvatar_h

View file

@ -1203,7 +1203,8 @@ void OctreeServer::beginRunning() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
// we need to ask the DS about agents so we can ping/reply with them // we need to ask the DS about agents so we can ping/reply with them
nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer,
NodeType::AvatarMixer });
beforeRun(); // after payload has been processed beforeRun(); // after payload has been processed

View file

@ -1302,6 +1302,14 @@
"placeholder": "1", "placeholder": "1",
"default": "1", "default": "1",
"advanced": true "advanced": true
},
{
"name": "connection_rate",
"label": "Connection Rate",
"help": "Number of new agents that can connect to the mixer every second",
"placeholder": "50",
"default": "50",
"advanced": true
} }
] ]
}, },

View file

@ -1243,12 +1243,11 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) {
limitedNodeList->eachMatchingNode( limitedNodeList->eachMatchingNode(
[this, addedNode](const SharedNodePointer& node)->bool { [this, addedNode](const SharedNodePointer& node)->bool {
if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) {
// is the added Node in this node's interest list? // is the added Node in this node's interest list?
return isInInterestSet(node, addedNode); return node->getLinkedData()
} else { && node->getActiveSocket()
return false; && node != addedNode
} && isInInterestSet(node, addedNode);
}, },
[this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) { [this, &addNodePacket, connectionSecretIndex, addedNode, limitedNodeListWeak](const SharedNodePointer& node) {
// send off this packet to the node // send off this packet to the node

View file

@ -503,6 +503,7 @@ void OtherAvatar::handleChangedAvatarEntityData() {
// then set the the original ID for the changes to take effect // then set the the original ID for the changes to take effect
// TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes // TODO: This is a horrible hack and once properties.constructFromBuffer no longer causes
// side effects...remove the following three lines // side effects...remove the following three lines
const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}"); const QUuid NULL_ID = QUuid("{00000000-0000-0000-0000-000000000005}");
entity->setParentID(NULL_ID); entity->setParentID(NULL_ID);
entity->setParentID(oldParentID); entity->setParentID(oldParentID);

View file

@ -41,6 +41,7 @@
#include "EntitySimulation.h" #include "EntitySimulation.h"
#include "EntityDynamicFactoryInterface.h" #include "EntityDynamicFactoryInterface.h"
//#define WANT_DEBUG
Q_DECLARE_METATYPE(EntityItemPointer); Q_DECLARE_METATYPE(EntityItemPointer);
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>(); int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
@ -95,6 +96,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_LAST_EDITED_BY; requestedProperties += PROP_LAST_EDITED_BY;
requestedProperties += PROP_ENTITY_HOST_TYPE; requestedProperties += PROP_ENTITY_HOST_TYPE;
requestedProperties += PROP_OWNING_AVATAR_ID; requestedProperties += PROP_OWNING_AVATAR_ID;
requestedProperties += PROP_PARENT_ID;
requestedProperties += PROP_PARENT_JOINT_INDEX;
requestedProperties += PROP_QUERY_AA_CUBE; requestedProperties += PROP_QUERY_AA_CUBE;
requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_CAN_CAST_SHADOW;
requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA;
@ -502,6 +505,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
} }
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
{
quint64 lastEdited = getLastEdited(); quint64 lastEdited = getLastEdited();
float editedAgo = getEditedAgo(); float editedAgo = getEditedAgo();
QString agoAsString = formatSecondsElapsed(editedAgo); QString agoAsString = formatSecondsElapsed(editedAgo);
@ -515,6 +519,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString;
qCDebug(entities) << " lastEdited =" << lastEdited; qCDebug(entities) << " lastEdited =" << lastEdited;
qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString;
}
#endif #endif
quint64 lastEditedFromBuffer = 0; quint64 lastEditedFromBuffer = 0;
@ -1099,7 +1104,7 @@ void EntityItem::simulate(const quint64& now) {
qCDebug(entities) << " hasGravity=" << hasGravity(); qCDebug(entities) << " hasGravity=" << hasGravity();
qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity();
qCDebug(entities) << " isMortal=" << isMortal(); qCDebug(entities) << " isMortal=" << isMortal();
qCDebug(entities) << " getAge()=" << getAge(); qCDebug(entities) << " getAge()=" << getAge();
qCDebug(entities) << " getLifetime()=" << getLifetime(); qCDebug(entities) << " getLifetime()=" << getLifetime();
@ -1111,12 +1116,12 @@ void EntityItem::simulate(const quint64& now) {
qCDebug(entities) << " hasGravity=" << hasGravity(); qCDebug(entities) << " hasGravity=" << hasGravity();
qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAcceleration=" << hasAcceleration();
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity();
} }
if (hasAngularVelocity()) { if (hasAngularVelocity()) {
qCDebug(entities) << " CHANGING...="; qCDebug(entities) << " CHANGING...=";
qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity();
qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity();
} }
if (isMortal()) { if (isMortal()) {
qCDebug(entities) << " MORTAL...="; qCDebug(entities) << " MORTAL...=";
@ -1738,7 +1743,7 @@ bool EntityItem::contains(const glm::vec3& point) const {
// the above cases not yet supported --> fall through to BOX case // the above cases not yet supported --> fall through to BOX case
case SHAPE_TYPE_BOX: { case SHAPE_TYPE_BOX: {
localPoint = glm::abs(localPoint); localPoint = glm::abs(localPoint);
return glm::any(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE))); return glm::all(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE)));
} }
case SHAPE_TYPE_ELLIPSOID: { case SHAPE_TYPE_ELLIPSOID: {
// since we've transformed into the normalized space this is just a sphere-point intersection test // since we've transformed into the normalized space this is just a sphere-point intersection test
@ -2652,13 +2657,23 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
// ALL entity properties. Some work will need to be done to the property system so that it can be more flexible // ALL entity properties. Some work will need to be done to the property system so that it can be more flexible
// (to grab the value and default value of a property given the string representation of that property, for example) // (to grab the value and default value of a property given the string representation of that property, for example)
// currently the only property filter we handle is '+' for serverScripts // currently the only property filter we handle in EntityItem is '+' for serverScripts
// which means that we only handle a filtered query asking for entities where the serverScripts property is non-default // which means that we only handle a filtered query asking for entities where the serverScripts property is non-default
static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts"; static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts";
static const QString ENTITY_TYPE_PROPERTY = "type";
if (jsonFilters[SERVER_SCRIPTS_PROPERTY] == EntityQueryFilterSymbol::NonDefault) { foreach(const auto& property, jsonFilters.keys()) {
return _serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS; if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) {
// check if this entity has a non-default value for serverScripts
if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) {
return true;
} else {
return false;
}
} else if (property == ENTITY_TYPE_PROPERTY) {
return (jsonFilters[property] == EntityTypes::getEntityTypeName(getType()) );
}
} }
// the json filter syntax did not match what we expected, return a match // the json filter syntax did not match what we expected, return a match

View file

@ -514,7 +514,7 @@ public:
QUuid getLastEditedBy() const { return _lastEditedBy; } QUuid getLastEditedBy() const { return _lastEditedBy; }
void setLastEditedBy(QUuid value) { _lastEditedBy = value; } void setLastEditedBy(QUuid value) { _lastEditedBy = value; }
bool matchesJSONFilters(const QJsonObject& jsonFilters) const; virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const;
virtual bool getMeshes(MeshProxyList& result) { return true; } virtual bool getMeshes(MeshProxyList& result) { return true; }

View file

@ -225,6 +225,15 @@ QString EntityItemProperties::getBloomModeAsString() const {
return getComponentModeAsString(_bloomMode); return getComponentModeAsString(_bloomMode);
} }
namespace {
const QStringList AVATAR_PRIORITIES_AS_STRING
{ "inherit", "crowd", "hero" };
}
QString EntityItemProperties::getAvatarPriorityAsString() const {
return AVATAR_PRIORITIES_AS_STRING.value(_avatarPriority);
}
std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator EntityItemProperties::findComponent(const QString& mode) { std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator EntityItemProperties::findComponent(const QString& mode) {
return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) {
return (pair.second == mode); return (pair.second == mode);
@ -249,6 +258,15 @@ void EntityItemProperties::setBloomModeFromString(const QString& bloomMode) {
} }
} }
void EntityItemProperties::setAvatarPriorityFromString(QString const& avatarPriority) {
auto result = AVATAR_PRIORITIES_AS_STRING.indexOf(avatarPriority);
if (result != -1) {
_avatarPriority = result;
_avatarPriorityChanged = true;
}
}
QString EntityItemProperties::getKeyLightModeAsString() const { QString EntityItemProperties::getKeyLightModeAsString() const {
return getComponentModeAsString(_keyLightMode); return getComponentModeAsString(_keyLightMode);
} }
@ -622,6 +640,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode);
CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode);
CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode); CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode);
CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority);
// Polyvox // Polyvox
CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize);
@ -1426,7 +1445,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the
* zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to
* certain properties.<br /> * certain properties.<br />
*
* @property {string} avatarPriority="inherit" - Configures the update priority of contained avatars to other clients.<br />
* <code>"inherit"</code>: Priority from enclosing zones is unchanged.<br />
* <code>"crowd"</code>: Priority in this zone is the normal priority.<br />
* <code>"hero"</code>: Avatars in this zone will have an increased update priority
* <pre> * <pre>
*
* function filter(properties) { * function filter(properties) {
* // Test and edit properties object values, * // Test and edit properties object values,
* // e.g., properties.modelURL, as required. * // e.g., properties.modelURL, as required.
@ -1761,6 +1786,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AVATAR_PRIORITY, avatarPriority, getAvatarPriorityAsString());
} }
// Web only // Web only
@ -2123,6 +2149,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(avatarPriority, AvatarPriority);
// Polyvox // Polyvox
COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize);
@ -2403,6 +2430,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(skyboxMode); COPY_PROPERTY_IF_CHANGED(skyboxMode);
COPY_PROPERTY_IF_CHANGED(hazeMode); COPY_PROPERTY_IF_CHANGED(hazeMode);
COPY_PROPERTY_IF_CHANGED(bloomMode); COPY_PROPERTY_IF_CHANGED(bloomMode);
COPY_PROPERTY_IF_CHANGED(avatarPriority);
// Polyvox // Polyvox
COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); COPY_PROPERTY_IF_CHANGED(voxelVolumeSize);
@ -2789,6 +2817,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t);
// Polyvox // Polyvox
ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3);
@ -3191,6 +3220,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode());
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode());
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode());
APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, (uint32_t)properties.getAvatarPriority());
} }
if (properties.getType() == EntityTypes::PolyVox) { if (properties.getType() == EntityTypes::PolyVox) {
@ -3656,6 +3686,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
} }
if (properties.getType() == EntityTypes::PolyVox) { if (properties.getType() == EntityTypes::PolyVox) {
@ -4039,6 +4070,7 @@ void EntityItemProperties::markAllChanged() {
_skyboxModeChanged = true; _skyboxModeChanged = true;
_hazeModeChanged = true; _hazeModeChanged = true;
_bloomModeChanged = true; _bloomModeChanged = true;
_avatarPriorityChanged = true;
// Polyvox // Polyvox
_voxelVolumeSizeChanged = true; _voxelVolumeSizeChanged = true;
@ -4637,6 +4669,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (bloomModeChanged()) { if (bloomModeChanged()) {
out += "bloomMode"; out += "bloomMode";
} }
if (avatarPriorityChanged()) {
out += "avatarPriority";
}
// Polyvox // Polyvox
if (voxelVolumeSizeChanged()) { if (voxelVolumeSizeChanged()) {

View file

@ -321,6 +321,7 @@ public:
DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
DEFINE_PROPERTY_REF_ENUM(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
// Polyvox // Polyvox
DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
@ -681,6 +682,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AvatarPriority, avatarPriority, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");

View file

@ -156,6 +156,7 @@ enum EntityPropertyList {
PROP_DERIVED_28, PROP_DERIVED_28,
PROP_DERIVED_29, PROP_DERIVED_29,
PROP_DERIVED_30, PROP_DERIVED_30,
PROP_DERIVED_31,
PROP_AFTER_LAST_ITEM, PROP_AFTER_LAST_ITEM,
@ -276,6 +277,8 @@ enum EntityPropertyList {
PROP_SKYBOX_MODE = PROP_DERIVED_28, PROP_SKYBOX_MODE = PROP_DERIVED_28,
PROP_HAZE_MODE = PROP_DERIVED_29, PROP_HAZE_MODE = PROP_DERIVED_29,
PROP_BLOOM_MODE = PROP_DERIVED_30, PROP_BLOOM_MODE = PROP_DERIVED_30,
// Avatar priority
PROP_AVATAR_PRIORITY = PROP_DERIVED_31,
// Polyvox // Polyvox
PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0, PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0,

View file

@ -71,6 +71,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de
COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority);
return properties; return properties;
} }
@ -117,6 +118,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie
SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority);
somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged ||
_skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged;
@ -192,6 +194,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode);
READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode);
READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode);
READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
return bytesRead; return bytesRead;
} }
@ -211,6 +214,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p
requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_FLYING_ALLOWED;
requestedProperties += PROP_GHOSTING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED;
requestedProperties += PROP_FILTER_URL; requestedProperties += PROP_FILTER_URL;
requestedProperties += PROP_AVATAR_PRIORITY;
requestedProperties += PROP_KEY_LIGHT_MODE; requestedProperties += PROP_KEY_LIGHT_MODE;
requestedProperties += PROP_AMBIENT_LIGHT_MODE; requestedProperties += PROP_AMBIENT_LIGHT_MODE;
@ -256,6 +260,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode());
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode());
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode());
APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority());
} }
void ZoneEntityItem::debugDump() const { void ZoneEntityItem::debugDump() const {
@ -269,6 +274,7 @@ void ZoneEntityItem::debugDump() const {
qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode); qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode);
qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode); qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode);
qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode); qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode);
qCDebug(entities) << " _avatarPriority:" << getAvatarPriority();
_keyLightProperties.debugDump(); _keyLightProperties.debugDump();
_ambientLightProperties.debugDump(); _ambientLightProperties.debugDump();
@ -463,3 +469,18 @@ void ZoneEntityItem::fetchCollisionGeometryResource() {
_shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL); _shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL);
} }
} }
bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
// currently the only property filter we handle in ZoneEntityItem is value of avatarPriority
static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority";
// If set ignore only priority-inherit zones:
if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY) && jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool()
&& _avatarPriority != COMPONENT_MODE_INHERIT) {
return true;
}
// Chain to base:
return EntityItem::matchesJSONFilters(jsonFilters);
}

View file

@ -66,6 +66,8 @@ public:
QString getCompoundShapeURL() const; QString getCompoundShapeURL() const;
virtual void setCompoundShapeURL(const QString& url); virtual void setCompoundShapeURL(const QString& url);
virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const override;
KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock<KeyLightPropertyGroup>([&] { return _keyLightProperties; }); } KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock<KeyLightPropertyGroup>([&] { return _keyLightProperties; }); }
AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock<AmbientLightPropertyGroup>([&] { return _ambientLightProperties; }); } AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock<AmbientLightPropertyGroup>([&] { return _ambientLightProperties; }); }
@ -96,6 +98,9 @@ public:
QString getFilterURL() const; QString getFilterURL() const;
void setFilterURL(const QString url); void setFilterURL(const QString url);
uint32_t getAvatarPriority() const { return _avatarPriority; }
void setAvatarPriority(uint32_t value) { _avatarPriority = value; }
bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; }
@ -147,6 +152,9 @@ protected:
bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED };
QString _filterURL { DEFAULT_FILTER_URL }; QString _filterURL { DEFAULT_FILTER_URL };
// Avatar-updates priority
uint32_t _avatarPriority { COMPONENT_MODE_INHERIT };
// Dirty flags turn true when either keylight properties is changing values. // Dirty flags turn true when either keylight properties is changing values.
bool _keyLightPropertiesChanged { false }; bool _keyLightPropertiesChanged { false };
bool _ambientLightPropertiesChanged { false }; bool _ambientLightPropertiesChanged { false };

View file

@ -40,6 +40,9 @@
static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); static Setting::Handle<quint16> LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0);
using namespace std::chrono_literals;
static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL_MS = 1s;
const std::set<NodeType_t> SOLO_NODE_TYPES = { const std::set<NodeType_t> SOLO_NODE_TYPES = {
NodeType::AvatarMixer, NodeType::AvatarMixer,
NodeType::AudioMixer, NodeType::AudioMixer,
@ -88,6 +91,11 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) :
connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats); connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats);
statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS); statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS);
// Flush delayed adds every second
QTimer* delayedAddsFlushTimer = new QTimer(this);
connect(delayedAddsFlushTimer, &QTimer::timeout, this, &NodeList::processDelayedAdds);
delayedAddsFlushTimer->start(CONNECTION_RATE_INTERVAL_MS.count());
// check the local socket right now // check the local socket right now
updateLocalSocket(); updateLocalSocket();
@ -367,7 +375,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe
return true; return true;
} else { } else if (!isDelayedNode(sourceID)){
HIFI_FCDEBUG(networking(), HIFI_FCDEBUG(networking(),
"Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID); "Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID);
} }
@ -558,25 +566,23 @@ SharedNodePointer LimitedNodeList::nodeWithLocalID(Node::LocalID localID) const
} }
void LimitedNodeList::eraseAllNodes() { void LimitedNodeList::eraseAllNodes() {
QSet<SharedNodePointer> killedNodes; std::vector<SharedNodePointer> killedNodes;
{ {
// iterate the current nodes - grab them so we can emit that they are dying // iterate the current nodes - grab them so we can emit that they are dying
// and then remove them from the hash // and then remove them from the hash
QWriteLocker writeLocker(&_nodeMutex); QWriteLocker writeLocker(&_nodeMutex);
_localIDMap.clear();
if (_nodeHash.size() > 0) { if (_nodeHash.size() > 0) {
qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList."; qCDebug(networking) << "LimitedNodeList::eraseAllNodes() removing all nodes from NodeList.";
auto it = _nodeHash.begin(); killedNodes.reserve(_nodeHash.size());
for (auto& pair : _nodeHash) {
while (it != _nodeHash.end()) { killedNodes.push_back(pair.second);
killedNodes.insert(it->second);
it = _nodeHash.unsafe_erase(it);
} }
} }
_localIDMap.clear();
_nodeHash.clear();
} }
foreach(const SharedNodePointer& killedNode, killedNodes) { foreach(const SharedNodePointer& killedNode, killedNodes) {
@ -593,18 +599,13 @@ void LimitedNodeList::reset() {
} }
bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) { bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) {
QReadLocker readLocker(&_nodeMutex); auto matchingNode = nodeWithUUID(nodeUUID);
NodeHash::iterator it = _nodeHash.find(nodeUUID);
if (it != _nodeHash.end()) {
SharedNodePointer matchingNode = it->second;
readLocker.unlock();
if (matchingNode) {
{ {
QWriteLocker writeLocker(&_nodeMutex); QWriteLocker writeLocker(&_nodeMutex);
_localIDMap.unsafe_erase(matchingNode->getLocalID()); _localIDMap.unsafe_erase(matchingNode->getLocalID());
_nodeHash.unsafe_erase(it); _nodeHash.unsafe_erase(matchingNode->getUUID());
} }
handleNodeKill(matchingNode, newConnectionID); handleNodeKill(matchingNode, newConnectionID);
@ -645,13 +646,8 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket,
Node::LocalID localID, bool isReplicated, bool isUpstream, Node::LocalID localID, bool isReplicated, bool isUpstream,
const QUuid& connectionSecret, const NodePermissions& permissions) { const QUuid& connectionSecret, const NodePermissions& permissions) {
{ auto matchingNode = nodeWithUUID(uuid);
QReadLocker readLocker(&_nodeMutex); if (matchingNode) {
NodeHash::const_iterator it = _nodeHash.find(uuid);
if (it != _nodeHash.end()) {
SharedNodePointer& matchingNode = it->second;
matchingNode->setPublicSocket(publicSocket); matchingNode->setPublicSocket(publicSocket);
matchingNode->setLocalSocket(localSocket); matchingNode->setLocalSocket(localSocket);
matchingNode->setPermissions(permissions); matchingNode->setPermissions(permissions);
@ -662,13 +658,14 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
return matchingNode; return matchingNode;
} }
}
auto removeOldNode = [&](auto node) { auto removeOldNode = [&](auto node) {
if (node) { if (node) {
{
QWriteLocker writeLocker(&_nodeMutex); QWriteLocker writeLocker(&_nodeMutex);
_localIDMap.unsafe_erase(node->getLocalID()); _localIDMap.unsafe_erase(node->getLocalID());
_nodeHash.unsafe_erase(node->getUUID()); _nodeHash.unsafe_erase(node->getUUID());
}
handleNodeKill(node); handleNodeKill(node);
} }
}; };
@ -736,6 +733,53 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t
return newNodePointer; return newNodePointer;
} }
void LimitedNodeList::addNewNode(NewNodeInfo info) {
// Throttle connection of new agents.
if (info.type == NodeType::Agent && _nodesAddedInCurrentTimeSlice >= _maxConnectionRate) {
delayNodeAdd(info);
return;
}
SharedNodePointer node = addOrUpdateNode(info.uuid, info.type, info.publicSocket, info.localSocket,
info.sessionLocalID, info.isReplicated, false,
info.connectionSecretUUID, info.permissions);
++_nodesAddedInCurrentTimeSlice;
}
void LimitedNodeList::delayNodeAdd(NewNodeInfo info) {
_delayedNodeAdds.push_back(info);
}
void LimitedNodeList::removeDelayedAdd(QUuid nodeUUID) {
auto it = std::find_if(_delayedNodeAdds.begin(), _delayedNodeAdds.end(), [&](auto info) {
return info.uuid == nodeUUID;
});
if (it != _delayedNodeAdds.end()) {
_delayedNodeAdds.erase(it);
}
}
bool LimitedNodeList::isDelayedNode(QUuid nodeUUID) {
auto it = std::find_if(_delayedNodeAdds.begin(), _delayedNodeAdds.end(), [&](auto info) {
return info.uuid == nodeUUID;
});
return it != _delayedNodeAdds.end();
}
void LimitedNodeList::processDelayedAdds() {
_nodesAddedInCurrentTimeSlice = 0;
auto nodesToAdd = glm::min(_delayedNodeAdds.size(), _maxConnectionRate);
auto firstNodeToAdd = _delayedNodeAdds.begin();
auto lastNodeToAdd = firstNodeToAdd + nodesToAdd;
for (auto it = firstNodeToAdd; it != lastNodeToAdd; ++it) {
addNewNode(*it);
}
_delayedNodeAdds.erase(firstNodeToAdd, lastNodeToAdd);
}
std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) { std::unique_ptr<NLPacket> LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) {
int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t); int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t);

View file

@ -51,6 +51,8 @@ const int INVALID_PORT = -1;
const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000; const quint64 NODE_SILENCE_THRESHOLD_MSECS = 5 * 1000;
static const size_t DEFAULT_MAX_CONNECTION_RATE { std::numeric_limits<size_t>::max() };
extern const std::set<NodeType_t> SOLO_NODE_TYPES; extern const std::set<NodeType_t> SOLO_NODE_TYPES;
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
@ -205,7 +207,10 @@ public:
int* lockWaitOut = nullptr, int* lockWaitOut = nullptr,
int* nodeTransformOut = nullptr, int* nodeTransformOut = nullptr,
int* functorOut = nullptr) { int* functorOut = nullptr) {
auto start = usecTimestampNow(); quint64 start, endTransform, endFunctor;
start = usecTimestampNow();
std::vector<SharedNodePointer> nodes;
{ {
QReadLocker readLock(&_nodeMutex); QReadLocker readLock(&_nodeMutex);
auto endLock = usecTimestampNow(); auto endLock = usecTimestampNow();
@ -216,23 +221,23 @@ public:
// Size of _nodeHash could change at any time, // Size of _nodeHash could change at any time,
// so reserve enough memory for the current size // so reserve enough memory for the current size
// and then back insert all the nodes found // and then back insert all the nodes found
std::vector<SharedNodePointer> nodes;
nodes.reserve(_nodeHash.size()); nodes.reserve(_nodeHash.size());
std::transform(_nodeHash.cbegin(), _nodeHash.cend(), std::back_inserter(nodes), [&](const NodeHash::value_type& it) { std::transform(_nodeHash.cbegin(), _nodeHash.cend(), std::back_inserter(nodes), [&](const NodeHash::value_type& it) {
return it.second; return it.second;
}); });
auto endTransform = usecTimestampNow();
endTransform = usecTimestampNow();
if (nodeTransformOut) { if (nodeTransformOut) {
*nodeTransformOut = (endTransform - endLock); *nodeTransformOut = (endTransform - endLock);
} }
}
functor(nodes.cbegin(), nodes.cend()); functor(nodes.cbegin(), nodes.cend());
auto endFunctor = usecTimestampNow(); endFunctor = usecTimestampNow();
if (functorOut) { if (functorOut) {
*functorOut = (endFunctor - endTransform); *functorOut = (endFunctor - endTransform);
} }
} }
}
template<typename NodeLambda> template<typename NodeLambda>
void eachNode(NodeLambda functor) { void eachNode(NodeLambda functor) {
@ -316,6 +321,9 @@ public:
void sendFakedHandshakeRequestToNode(SharedNodePointer node); void sendFakedHandshakeRequestToNode(SharedNodePointer node);
#endif #endif
size_t getMaxConnectionRate() const { return _maxConnectionRate; }
void setMaxConnectionRate(size_t rate) { _maxConnectionRate = rate; }
int getInboundPPS() const { return _inboundPPS; } int getInboundPPS() const { return _inboundPPS; }
int getOutboundPPS() const { return _outboundPPS; } int getOutboundPPS() const { return _outboundPPS; }
float getInboundKbps() const { return _inboundKbps; } float getInboundKbps() const { return _inboundKbps; }
@ -367,7 +375,20 @@ protected slots:
void clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr); void clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr);
void processDelayedAdds();
protected: protected:
struct NewNodeInfo {
qint8 type;
QUuid uuid;
HifiSockAddr publicSocket;
HifiSockAddr localSocket;
NodePermissions permissions;
bool isReplicated;
Node::LocalID sessionLocalID;
QUuid connectionSecretUUID;
};
LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton LimitedNodeList(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton void operator=(LimitedNodeList const&) = delete; // Don't implement, needed to avoid copies of singleton
@ -390,6 +411,11 @@ protected:
bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr); bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr);
void addNewNode(NewNodeInfo info);
void delayNodeAdd(NewNodeInfo info);
void removeDelayedAdd(QUuid nodeUUID);
bool isDelayedNode(QUuid nodeUUID);
NodeHash _nodeHash; NodeHash _nodeHash;
mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive }; mutable QReadWriteLock _nodeMutex { QReadWriteLock::Recursive };
udt::Socket _nodeSocket; udt::Socket _nodeSocket;
@ -440,6 +466,10 @@ private:
Node::LocalID _sessionLocalID { 0 }; Node::LocalID _sessionLocalID { 0 };
bool _flagTimeForConnectionStep { false }; // only keep track in interface bool _flagTimeForConnectionStep { false }; // only keep track in interface
size_t _maxConnectionRate { DEFAULT_MAX_CONNECTION_RATE };
size_t _nodesAddedInCurrentTimeSlice { 0 };
std::vector<NewNodeInfo> _delayedNodeAdds;
int _inboundPPS { 0 }; int _inboundPPS { 0 };
int _outboundPPS { 0 }; int _outboundPPS { 0 };
float _inboundKbps { 0.0f }; float _inboundKbps { 0.0f };

View file

@ -200,7 +200,6 @@ void NodeList::timePingReply(ReceivedMessage& message, const SharedNodePointer&
} }
void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) { void NodeList::processPingPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
// send back a reply // send back a reply
auto replyPacket = constructPingReplyPacket(*message); auto replyPacket = constructPingReplyPacket(*message);
const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); const HifiSockAddr& senderSockAddr = message->getSenderSockAddr();
@ -291,41 +290,47 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
void NodeList::sendDomainServerCheckIn() { void NodeList::sendDomainServerCheckIn() {
// This function is called by the server check-in timer thread
// not the NodeList thread. Calling it on the NodeList thread
// resulted in starvation of the server check-in function.
// be VERY CAREFUL modifying this code as members of NodeList
// may be called by multiple threads.
if (!_sendDomainServerCheckInEnabled) { if (!_sendDomainServerCheckInEnabled) {
qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled."; qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled.";
return; return;
} }
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection);
return;
}
if (_isShuttingDown) { if (_isShuttingDown) {
qCDebug(networking) << "Refusing to send a domain-server check in while shutting down."; qCDebug(networking) << "Refusing to send a domain-server check in while shutting down.";
return; return;
} }
if (_publicSockAddr.isNull()) { auto publicSockAddr = _publicSockAddr;
auto domainHandlerIp = _domainHandler.getIP();
if (publicSockAddr.isNull()) {
// we don't know our public socket and we need to send it to the domain server // we don't know our public socket and we need to send it to the domain server
qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
} else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { } else if (domainHandlerIp.isNull() && _domainHandler.requiresICE()) {
qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
handleICEConnectionToDomainServer(); handleICEConnectionToDomainServer();
// let the domain handler know we are due to send a checkin packet // let the domain handler know we are due to send a checkin packet
} else if (!_domainHandler.getIP().isNull() && !_domainHandler.checkInPacketTimeout()) { } else if (!domainHandlerIp.isNull() && !_domainHandler.checkInPacketTimeout()) {
bool domainIsConnected = _domainHandler.isConnected();
PacketType domainPacketType = !_domainHandler.isConnected() HifiSockAddr domainSockAddr = _domainHandler.getSockAddr();
PacketType domainPacketType = !domainIsConnected
? PacketType::DomainConnectRequest : PacketType::DomainListRequest; ? PacketType::DomainConnectRequest : PacketType::DomainListRequest;
if (!_domainHandler.isConnected()) { if (!domainIsConnected) {
qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname(); auto hostname = _domainHandler.getHostname();
qCDebug(networking) << "Sending connect request to domain-server at" << hostname;
// is this our localhost domain-server? // is this our localhost domain-server?
// if so we need to make sure we have an up-to-date local port in case it restarted // if so we need to make sure we have an up-to-date local port in case it restarted
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost if (domainSockAddr.getAddress() == QHostAddress::LocalHost
|| _domainHandler.getHostname() == "localhost") { || hostname == "localhost") {
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort); getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);
@ -338,7 +343,7 @@ void NodeList::sendDomainServerCheckIn() {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
const QUuid& connectionToken = _domainHandler.getConnectionToken(); const QUuid& connectionToken = _domainHandler.getConnectionToken();
bool requiresUsernameSignature = !_domainHandler.isConnected() && !connectionToken.isNull(); bool requiresUsernameSignature = !domainIsConnected && !connectionToken.isNull();
if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) { if (requiresUsernameSignature && !accountManager->getAccountInfo().hasPrivateKey()) {
qWarning() << "A keypair is required to present a username signature to the domain-server" qWarning() << "A keypair is required to present a username signature to the domain-server"
@ -353,6 +358,7 @@ void NodeList::sendDomainServerCheckIn() {
QDataStream packetStream(domainPacket.get()); QDataStream packetStream(domainPacket.get());
HifiSockAddr localSockAddr = _localSockAddr;
if (domainPacketType == PacketType::DomainConnectRequest) { if (domainPacketType == PacketType::DomainConnectRequest) {
#if (PR_BUILD || DEV_BUILD) #if (PR_BUILD || DEV_BUILD)
@ -361,13 +367,9 @@ void NodeList::sendDomainServerCheckIn() {
} }
#endif #endif
QUuid connectUUID; QUuid connectUUID = _domainHandler.getAssignmentUUID();
if (!_domainHandler.getAssignmentUUID().isNull()) { if (connectUUID.isNull() && _domainHandler.requiresICE()) {
// this is a connect request and we're an assigned node
// so set our packetUUID as the assignment UUID
connectUUID = _domainHandler.getAssignmentUUID();
} else if (_domainHandler.requiresICE()) {
// this is a connect request and we're an interface client // this is a connect request and we're an interface client
// that used ice to discover the DS // that used ice to discover the DS
// so send our ICE client UUID with the connect request // so send our ICE client UUID with the connect request
@ -383,10 +385,9 @@ void NodeList::sendDomainServerCheckIn() {
// if possible, include the MAC address for the current interface in our connect request // if possible, include the MAC address for the current interface in our connect request
QString hardwareAddress; QString hardwareAddress;
for (auto networkInterface : QNetworkInterface::allInterfaces()) { for (auto networkInterface : QNetworkInterface::allInterfaces()) {
for (auto interfaceAddress : networkInterface.addressEntries()) { for (auto interfaceAddress : networkInterface.addressEntries()) {
if (interfaceAddress.ip() == _localSockAddr.getAddress()) { if (interfaceAddress.ip() == localSockAddr.getAddress()) {
// this is the interface whose local IP matches what we've detected the current IP to be // this is the interface whose local IP matches what we've detected the current IP to be
hardwareAddress = networkInterface.hardwareAddress(); hardwareAddress = networkInterface.hardwareAddress();
@ -410,10 +411,10 @@ void NodeList::sendDomainServerCheckIn() {
// pack our data to send to the domain-server including // pack our data to send to the domain-server including
// the hostname information (so the domain-server can see which place name we came in on) // the hostname information (so the domain-server can see which place name we came in on)
packetStream << _ownerType.load() << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList();
packetStream << DependencyManager::get<AddressManager>()->getPlaceName(); packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
if (!_domainHandler.isConnected()) { if (!domainIsConnected) {
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
packetStream << accountInfo.getUsername(); packetStream << accountInfo.getUsername();
@ -433,9 +434,9 @@ void NodeList::sendDomainServerCheckIn() {
checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER); checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER);
for (int i = 1; i < checkinCount; ++i) { for (int i = 1; i < checkinCount; ++i) {
auto packetCopy = domainPacket->createCopy(*domainPacket); auto packetCopy = domainPacket->createCopy(*domainPacket);
sendPacket(std::move(packetCopy), _domainHandler.getSockAddr()); sendPacket(std::move(packetCopy), domainSockAddr);
} }
sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); sendPacket(std::move(domainPacket), domainSockAddr);
} }
} }
@ -708,37 +709,28 @@ void NodeList::processDomainServerRemovedNode(QSharedPointer<ReceivedMessage> me
QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
qCDebug(networking) << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID); qCDebug(networking) << "Received packet from domain-server to remove node with UUID" << uuidStringWithoutCurlyBraces(nodeUUID);
killNodeWithUUID(nodeUUID); killNodeWithUUID(nodeUUID);
removeDelayedAdd(nodeUUID);
} }
void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
// setup variables to read into from QDataStream NewNodeInfo info;
qint8 nodeType;
QUuid nodeUUID, connectionSecretUUID;
HifiSockAddr nodePublicSocket, nodeLocalSocket;
NodePermissions permissions;
bool isReplicated;
Node::LocalID sessionLocalID;
packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> permissions packetStream >> info.type
>> isReplicated >> sessionLocalID; >> info.uuid
>> info.publicSocket
>> info.localSocket
>> info.permissions
>> info.isReplicated
>> info.sessionLocalID
>> info.connectionSecretUUID;
// if the public socket address is 0 then it's reachable at the same IP // if the public socket address is 0 then it's reachable at the same IP
// as the domain server // as the domain server
if (nodePublicSocket.getAddress().isNull()) { if (info.publicSocket.getAddress().isNull()) {
nodePublicSocket.setAddress(_domainHandler.getIP()); info.publicSocket.setAddress(_domainHandler.getIP());
} }
packetStream >> connectionSecretUUID; addNewNode(info);
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket,
sessionLocalID, isReplicated, false, connectionSecretUUID, permissions);
// nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server
// and always have their public socket as their active socket
if (node->getType() == NodeType::downstreamType(_ownerType) || node->getType() == NodeType::upstreamType(_ownerType)) {
node->setLastHeardMicrostamp(usecTimestampNow());
node->activatePublicSocket();
}
} }
void NodeList::sendAssignment(Assignment& assignment) { void NodeList::sendAssignment(Assignment& assignment) {
@ -785,7 +777,6 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
} }
void NodeList::startNodeHolePunch(const SharedNodePointer& node) { void NodeList::startNodeHolePunch(const SharedNodePointer& node) {
// we don't hole punch to downstream servers, since it is assumed that we have a direct line to them // we don't hole punch to downstream servers, since it is assumed that we have a direct line to them
// we also don't hole punch to relayed upstream nodes, since we do not communicate directly with them // we also don't hole punch to relayed upstream nodes, since we do not communicate directly with them
@ -799,6 +790,14 @@ void NodeList::startNodeHolePunch(const SharedNodePointer& node) {
// ping this node immediately // ping this node immediately
pingPunchForInactiveNode(node); pingPunchForInactiveNode(node);
} }
// nodes that are downstream or upstream of our own type are kept alive when we hear about them from the domain server
// and always have their public socket as their active socket
if (node->getType() == NodeType::downstreamType(_ownerType) || node->getType() == NodeType::upstreamType(_ownerType)) {
node->setLastHeardMicrostamp(usecTimestampNow());
node->activatePublicSocket();
}
} }
void NodeList::handleNodePingTimeout() { void NodeList::handleNodePingTimeout() {

View file

@ -102,6 +102,11 @@ void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObjec
statsObject["io_stats"] = ioStats; statsObject["io_stats"] = ioStats;
QJsonObject assignmentStats;
assignmentStats["numQueuedCheckIns"] = _numQueuedCheckIns;
statsObject["assignmentStats"] = assignmentStats;
nodeList->sendStatsToDomainServer(statsObject); nodeList->sendStatsToDomainServer(statsObject);
} }
@ -119,10 +124,16 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() {
stop(); stop();
} else { } else {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); // Call sendDomainServerCheckIn directly instead of putting it on
// the event queue. Under high load, the event queue can back up
// longer than the total timeout period and cause a restart
nodeList->sendDomainServerCheckIn();
// increase the number of queued check ins // increase the number of queued check ins
_numQueuedCheckIns++; _numQueuedCheckIns++;
if (_numQueuedCheckIns > 1) {
qCDebug(networking) << "Number of queued checkins = " << _numQueuedCheckIns;
}
} }
} }

View file

@ -260,6 +260,7 @@ enum class EntityVersion : PacketVersion {
MissingWebEntityProperties, MissingWebEntityProperties,
PulseProperties, PulseProperties,
RingGizmoEntities, RingGizmoEntities,
AvatarPriorityZone,
ShowKeyboardFocusHighlight, ShowKeyboardFocusHighlight,
WebBillboardMode, WebBillboardMode,
ModelScale, ModelScale,

View file

@ -134,6 +134,9 @@
"bloom.bloomSize": { "bloom.bloomSize": {
"tooltip": "The radius of bloom. The higher the value, the larger the bloom." "tooltip": "The radius of bloom. The higher the value, the larger the bloom."
}, },
"avatarPriority": {
"tooltip": "Alter Avatars' update priorities."
},
"modelURL": { "modelURL": {
"tooltip": "A mesh model from an FBX or OBJ file." "tooltip": "A mesh model from an FBX or OBJ file."
}, },

View file

@ -382,7 +382,8 @@ const DEFAULT_ENTITY_PROPERTIES = {
}, },
}, },
shapeType: "box", shapeType: "box",
bloomMode: "inherit" bloomMode: "inherit",
avatarPriority: "inherit"
}, },
Model: { Model: {
collisionShape: "none", collisionShape: "none",

View file

@ -428,6 +428,13 @@ const GROUPS = [
propertyID: "bloom.bloomSize", propertyID: "bloom.bloomSize",
showPropertyRule: { "bloomMode": "enabled" }, showPropertyRule: { "bloomMode": "enabled" },
}, },
{
label: "Avatar Priority",
type: "dropdown",
options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" },
propertyID: "avatarPriority",
},
] ]
}, },
{ {