From 8ed69f62fa5bea7a053b082f66de96573de945bc Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 10 Feb 2017 18:22:02 -0800 Subject: [PATCH 01/35] adjust stats so avatar list is at bottom --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 61164ee8d7..9bfc2b28e6 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -660,7 +660,7 @@ void AvatarMixer::sendStatsPacket() { avatarsObject[uuidStringWithoutCurlyBraces(node->getUUID())] = avatarStats; }); - statsObject["avatars"] = avatarsObject; + statsObject["z_avatars"] = avatarsObject; ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; From b48ff6f24f2b1afa811fea6d3f204df1671131c0 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 13 Feb 2017 18:17:01 -0800 Subject: [PATCH 02/35] add some various debug and stats code to avatarMixer --- assignment-client/src/avatars/AvatarMixer.cpp | 204 +++++++++++++++++- assignment-client/src/avatars/AvatarMixer.h | 20 ++ 2 files changed, 219 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 9bfc2b28e6..261fb1b76e 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -83,10 +83,99 @@ void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const Shar ++_sumIdentityPackets; } +#include +#include + +void AvatarMixer::start() { + auto nodeList = DependencyManager::get(); + + +/**** + // prepare the NodeList + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); + nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); }; + + // parse out any AudioMixer settings + { + DomainHandler& domainHandler = nodeList->getDomainHandler(); + const QJsonObject& settingsObject = domainHandler.getSettingsObject(); + parseSettingsObject(settingsObject); + } + + // mix state + unsigned int frame = 1; + auto frameTimestamp = p_high_resolution_clock::now(); +****/ + + while (!_isFinished) { + _loopRate.increment(); + + auto sleepAmount = std::chrono::milliseconds(AVATAR_DATA_SEND_INTERVAL_MSECS); + std::this_thread::sleep_for(sleepAmount); + + /* + auto ticTimer = _ticTiming.timer(); + + { + auto timer = _sleepTiming.timer(); + auto frameDuration = timeFrame(frameTimestamp); + throttle(frameDuration, frame); + } + + auto frameTimer = _frameTiming.timer(); + + nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + // prepare frames; pop off any new audio from their streams + { + auto prepareTimer = _prepareTiming.timer(); + std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { + _stats.sumStreams += prepareFrame(node, frame); + }); + } + + // mix across slave threads + { + auto mixTimer = _mixTiming.timer(); + _slavePool.mix(cbegin, cend, frame, _throttlingRatio); + } + }); + */ + + // gather stats + /* + _slavePool.each([&](AudioMixerSlave& slave) { + _stats.accumulate(slave.stats); + slave.stats.reset(); + }); + */ + + /* + ++frame; + ++_numStatFrames; + */ + + // play nice with qt event-looping + { + //auto eventsTimer = _eventsTiming.timer(); + + // since we're a while loop we need to yield to qt's event processing + QCoreApplication::processEvents(); + + if (_isFinished) { + // alert qt eventing that this is finished + QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete); + break; + } + } + } +} + + // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. void AvatarMixer::broadcastAvatarData() { + quint64 startBroadcastAvatarData = usecTimestampNow(); _broadcastRate.increment(); int idleTime = AVATAR_DATA_SEND_INTERVAL_MSECS; @@ -262,6 +351,7 @@ void AvatarMixer::broadcastAvatarData() { // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); + quint64 startDisplayNameManagement = usecTimestampNow(); if (nodeData->getAvatarSessionDisplayNameMustChange()) { const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { @@ -289,11 +379,18 @@ void AvatarMixer::broadcastAvatarData() { sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } + quint64 endDisplayNameManagement = usecTimestampNow(); + _displayNameManagementElapsedTime += (endDisplayNameManagement - startDisplayNameManagement); + // this is an AGENT we have received head data from // send back a packet with other active node data to this node nodeList->eachMatchingNode( [&](const SharedNodePointer& otherNode)->bool { + + bool shouldConsider = false; + quint64 startIgnoreCalculation = usecTimestampNow(); + // 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 // or that has ignored the viewing node @@ -301,7 +398,9 @@ void AvatarMixer::broadcastAvatarData() { || otherNode->getUUID() == node->getUUID() || (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe) || (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { - return false; + + shouldConsider = false; + } else { AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); @@ -333,20 +432,35 @@ void AvatarMixer::broadcastAvatarData() { // Perform the collision check between the two bounding boxes if (nodeBox.touches(otherNodeBox)) { nodeData->ignoreOther(node, otherNode); - return getsAnyIgnored; + shouldConsider = getsAnyIgnored; } } // Not close enough to ignore - nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); - return true; + if (shouldConsider) { + nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); + } + + shouldConsider = true; } + + quint64 endIgnoreCalculation = usecTimestampNow(); + _ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); + + return shouldConsider; }, [&](const SharedNodePointer& otherNode) { + + quint64 startAvatarDataPacking = usecTimestampNow(); + ++numOtherAvatars; AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); MutexTryLocker lock(otherNodeData->getMutex()); if (!lock.isLocked()) { + + // FIXME -- might want to track this lock failed... + quint64 endAvatarDataPacking = usecTimestampNow(); + _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } @@ -374,6 +488,9 @@ void AvatarMixer::broadcastAvatarData() { if (distanceToAvatar != 0.0f && !getsOutOfView && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { + + quint64 endAvatarDataPacking = usecTimestampNow(); + _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } @@ -389,6 +506,9 @@ void AvatarMixer::broadcastAvatarData() { // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { ++numAvatarsHeldBack; + + quint64 endAvatarDataPacking = usecTimestampNow(); + _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } 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 @@ -411,6 +531,8 @@ void AvatarMixer::broadcastAvatarData() { // this throttles the extra data to only be sent every Nth message if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { + quint64 endAvatarDataPacking = usecTimestampNow(); + _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } @@ -436,8 +558,13 @@ void AvatarMixer::broadcastAvatarData() { numAvatarDataBytes += avatarPacketList->write(bytes); avatarPacketList->endSegment(); + + quint64 endAvatarDataPacking = usecTimestampNow(); + _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); }); + quint64 startPacketSending = usecTimestampNow(); + // close the current packet so that we're always sending something avatarPacketList->closeCurrentPacket(true); @@ -457,6 +584,10 @@ void AvatarMixer::broadcastAvatarData() { } else { nodeData->setMaxAvatarDistance(maxAvatarDistanceThisFrame); } + + quint64 endPacketSending = usecTimestampNow(); + _packetSendingElapsedTime += (endPacketSending - startPacketSending); + } ); @@ -501,6 +632,9 @@ void AvatarMixer::broadcastAvatarData() { } #endif + quint64 endBroadcastAvatarData = usecTimestampNow(); + _broadcastAvatarDataElapsedTime += (endBroadcastAvatarData - startBroadcastAvatarData); + } void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { @@ -551,6 +685,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { } void AvatarMixer::handleViewFrustumPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto start = usecTimestampNow(); auto nodeList = DependencyManager::get(); nodeList->getOrCreateLinkedData(senderNode); @@ -560,9 +695,14 @@ void AvatarMixer::handleViewFrustumPacket(QSharedPointer messag nodeData->readViewFrustumPacket(message->getMessage()); } } + + auto end = usecTimestampNow(); + _handleViewFrustumPacketElapsedTime += (end - start); } void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto start = usecTimestampNow(); + auto nodeList = DependencyManager::get(); nodeList->getOrCreateLinkedData(senderNode); @@ -574,14 +714,20 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointersetRequestsDomainListData(isRequesting); } } + auto end = usecTimestampNow(); + _handleRequestsDomainListDataPacketElapsedTime += (end - start); } void AvatarMixer::handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto start = usecTimestampNow(); auto nodeList = DependencyManager::get(); nodeList->updateNodeWithDataFromPacket(message, senderNode); + auto end = usecTimestampNow(); + _handleAvatarDataPacketElapsedTime += (end - start); } void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto start = usecTimestampNow(); auto nodeList = DependencyManager::get(); nodeList->getOrCreateLinkedData(senderNode); @@ -605,18 +751,29 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes } } } + auto end = usecTimestampNow(); + _handleAvatarIdentityPacketElapsedTime += (end - start); } void AvatarMixer::handleKillAvatarPacket(QSharedPointer message) { + auto start = usecTimestampNow(); DependencyManager::get()->processKillNode(*message); + auto end = usecTimestampNow(); + _handleKillAvatarPacketElapsedTime += (end - start); } void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto start = usecTimestampNow(); senderNode->parseIgnoreRequestMessage(message); + auto end = usecTimestampNow(); + _handleNodeIgnoreRequestPacketElapsedTime += (end - start); } void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto start = usecTimestampNow(); sendingNode->parseIgnoreRadiusRequestMessage(packet); + auto end = usecTimestampNow(); + _handleRadiusIgnoreRequestPacketElapsedTime += (end - start); } void AvatarMixer::sendStatsPacket() { @@ -628,6 +785,33 @@ void AvatarMixer::sendStatsPacket() { statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; statsObject["broadcast_loop_rate"] = _broadcastRate.rate(); + statsObject["tight_loop_rate"] = _loopRate.rate(); + + // broadcastAvatarDataElapsed timing details... + statsObject["timing_average_a_displayNameManagement"] = (float)_displayNameManagementElapsedTime / (float)_numStatFrames; + statsObject["timing_average_b_ignoreCalculation"] = (float)_ignoreCalculationElapsedTime / (float)_numStatFrames; + statsObject["timing_average_c_avatarDataPacking"] = (float)_avatarDataPackingElapsedTime / (float)_numStatFrames; + statsObject["timing_average_d_packetSending"] = (float)_packetSendingElapsedTime / (float)_numStatFrames; + + statsObject["timing_average_x_total_broadcastAvatarData"] = (float)_broadcastAvatarDataElapsedTime / (float)_numStatFrames; + + + statsObject["timing_average_z_handleViewFrustumPacket"] = (float)_handleViewFrustumPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleAvatarDataPacket"] = (float)_handleAvatarDataPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleAvatarIdentityPacket"] = (float)_handleAvatarIdentityPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleKillAvatarPacket"] = (float)_handleKillAvatarPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = (float)_handleNodeIgnoreRequestPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = (float)_handleRadiusIgnoreRequestPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = (float)_handleRequestsDomainListDataPacketElapsedTime / (float)_numStatFrames; + + _handleViewFrustumPacketElapsedTime = 0; + _handleAvatarDataPacketElapsedTime = 0; + _handleAvatarIdentityPacketElapsedTime = 0; + _handleKillAvatarPacketElapsedTime = 0; + _handleNodeIgnoreRequestPacketElapsedTime = 0; + _handleRadiusIgnoreRequestPacketElapsedTime = 0; + _handleRequestsDomainListDataPacketElapsedTime = 0; + QJsonObject avatarsObject; @@ -666,6 +850,13 @@ void AvatarMixer::sendStatsPacket() { _sumListeners = 0; _sumIdentityPackets = 0; _numStatFrames = 0; + + _broadcastAvatarDataElapsedTime = 0; + _displayNameManagementElapsedTime = 0; + _ignoreCalculationElapsedTime = 0; + _avatarDataPackingElapsedTime = 0; + _packetSendingElapsedTime = 0; + } void AvatarMixer::run() { @@ -675,7 +866,7 @@ void AvatarMixer::run() { DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &AvatarMixer::domainSettingsRequestComplete); connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AvatarMixer::domainSettingsRequestFailed); - + ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); // setup the timer that will be fired on the broadcast thread @@ -687,6 +878,9 @@ void AvatarMixer::run() { // connect appropriate signals and slots connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection); connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start())); + + // start our tight loop... + start(); } void AvatarMixer::domainSettingsRequestComplete() { diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 5d54622ac9..cb06362b7f 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -45,6 +45,7 @@ private slots: void handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + void start(); private: @@ -73,6 +74,25 @@ private: RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; QHash> _sessionDisplayNames; + + quint64 _broadcastAvatarDataElapsedTime { 0 }; // total time spent in broadcastAvatarData since last stats window + quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window + quint64 _ignoreCalculationElapsedTime { 0 }; + quint64 _avatarDataPackingElapsedTime { 0 }; + quint64 _packetSendingElapsedTime { 0 }; + + + quint64 _handleViewFrustumPacketElapsedTime { 0 }; + quint64 _handleAvatarDataPacketElapsedTime { 0 }; + quint64 _handleAvatarIdentityPacketElapsedTime { 0 }; + quint64 _handleKillAvatarPacketElapsedTime { 0 }; + quint64 _handleNodeIgnoreRequestPacketElapsedTime { 0 }; + quint64 _handleRadiusIgnoreRequestPacketElapsedTime { 0 }; + quint64 _handleRequestsDomainListDataPacketElapsedTime { 0 }; + + + RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs + }; #endif // hifi_AvatarMixer_h From e642f6f96ab78e634d77a98097911e4b3d839d32 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Mon, 13 Feb 2017 18:29:45 -0800 Subject: [PATCH 03/35] add some various debug and stats code to avatarMixer --- assignment-client/src/avatars/AvatarMixer.cpp | 6 ++++++ assignment-client/src/avatars/AvatarMixer.h | 1 + 2 files changed, 7 insertions(+) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 261fb1b76e..87952f3264 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -159,6 +159,7 @@ void AvatarMixer::start() { //auto eventsTimer = _eventsTiming.timer(); // since we're a while loop we need to yield to qt's event processing + auto start = usecTimestampNow(); QCoreApplication::processEvents(); if (_isFinished) { @@ -166,6 +167,8 @@ void AvatarMixer::start() { QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete); break; } + auto end = usecTimestampNow(); + _processEventsElapsedTime += (end - start); } } } @@ -804,6 +807,8 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = (float)_handleRadiusIgnoreRequestPacketElapsedTime / (float)_numStatFrames; statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = (float)_handleRequestsDomainListDataPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_processEvents"] = (float)_processEventsElapsedTime / (float)_numStatFrames; + _handleViewFrustumPacketElapsedTime = 0; _handleAvatarDataPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; @@ -811,6 +816,7 @@ void AvatarMixer::sendStatsPacket() { _handleNodeIgnoreRequestPacketElapsedTime = 0; _handleRadiusIgnoreRequestPacketElapsedTime = 0; _handleRequestsDomainListDataPacketElapsedTime = 0; + _processEventsElapsedTime = 0; QJsonObject avatarsObject; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index cb06362b7f..4509b394f3 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -90,6 +90,7 @@ private: quint64 _handleRadiusIgnoreRequestPacketElapsedTime { 0 }; quint64 _handleRequestsDomainListDataPacketElapsedTime { 0 }; + quint64 _processEventsElapsedTime { 0 }; RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs From 3d874f6ad2b4e2c9147e58ea5fb4e2f343509725 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 14 Feb 2017 14:12:22 -0800 Subject: [PATCH 04/35] more hacking --- assignment-client/src/avatars/AvatarMixer.cpp | 99 +++++++------------ assignment-client/src/avatars/AvatarMixer.h | 4 + .../src/avatars/AvatarMixerClientData.cpp | 8 ++ .../src/avatars/AvatarMixerClientData.h | 5 +- 4 files changed, 52 insertions(+), 64 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 87952f3264..0fada86499 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -89,75 +89,29 @@ void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const Shar void AvatarMixer::start() { auto nodeList = DependencyManager::get(); - -/**** - // prepare the NodeList - nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); - nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); }; - - // parse out any AudioMixer settings - { - DomainHandler& domainHandler = nodeList->getDomainHandler(); - const QJsonObject& settingsObject = domainHandler.getSettingsObject(); - parseSettingsObject(settingsObject); - } - - // mix state - unsigned int frame = 1; - auto frameTimestamp = p_high_resolution_clock::now(); -****/ - while (!_isFinished) { _loopRate.increment(); + // FIXME - we really should sleep for the remainder of what we haven't spent time processing auto sleepAmount = std::chrono::milliseconds(AVATAR_DATA_SEND_INTERVAL_MSECS); std::this_thread::sleep_for(sleepAmount); - /* - auto ticTimer = _ticTiming.timer(); - + // Allow nodes to process any pending/queued packets across our worker threads { - auto timer = _sleepTiming.timer(); - auto frameDuration = timeFrame(frameTimestamp); - throttle(frameDuration, frame); + auto start = usecTimestampNow(); + auto nodeList = DependencyManager::get(); + nodeList->eachNode([](const SharedNodePointer& node) { + auto nodeData = dynamic_cast(node->getLinkedData()); + if (nodeData) { + nodeData->processQueuedAvatarDataPackets(); + } + }); + auto end = usecTimestampNow(); + _processQueuedAvatarDataPacketsElapsedTime += (end - start); } - auto frameTimer = _frameTiming.timer(); - - nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { - // prepare frames; pop off any new audio from their streams - { - auto prepareTimer = _prepareTiming.timer(); - std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { - _stats.sumStreams += prepareFrame(node, frame); - }); - } - - // mix across slave threads - { - auto mixTimer = _mixTiming.timer(); - _slavePool.mix(cbegin, cend, frame, _throttlingRatio); - } - }); - */ - - // gather stats - /* - _slavePool.each([&](AudioMixerSlave& slave) { - _stats.accumulate(slave.stats); - slave.stats.reset(); - }); - */ - - /* - ++frame; - ++_numStatFrames; - */ - // play nice with qt event-looping { - //auto eventsTimer = _eventsTiming.timer(); - // since we're a while loop we need to yield to qt's event processing auto start = usecTimestampNow(); QCoreApplication::processEvents(); @@ -723,8 +677,15 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode) { auto start = usecTimestampNow(); + auto nodeList = DependencyManager::get(); - nodeList->updateNodeWithDataFromPacket(message, senderNode); + AvatarMixerClientData* nodeData = dynamic_cast(nodeList->getOrCreateLinkedData(senderNode)); + + if (nodeData) { + QMutexLocker linkedDataLocker(&nodeData->getMutex()); // NOTE: we might be able to safely assume this doesn't need to be locked! + nodeData->queueAvatarDataPacket(message); + } + auto end = usecTimestampNow(); _handleAvatarDataPacketElapsedTime += (end - start); } @@ -780,6 +741,9 @@ void AvatarMixer::handleRadiusIgnoreRequestPacket(QSharedPointer _loopRate; // this is the rate that the main thread tight loop runs + std::unordered_map>> _pendingAvatarDataPackets; + }; #endif // hifi_AvatarMixer_h diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index a7a506e1d8..ffa525bc35 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,6 +16,14 @@ #include "AvatarMixerClientData.h" +void AvatarMixerClientData::processQueuedAvatarDataPackets() { + for (auto message : _queuedAvatarDataPackets) { + parseData(*message); + } + _queuedAvatarDataPackets.clear(); +} + + int AvatarMixerClientData::parseData(ReceivedMessage& message) { // pull the sequence number from the data first message.readPrimitive(&_lastReceivedSequenceNumber); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index aa011f8baf..611af1701d 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -118,9 +118,12 @@ public: return _lastOtherAvatarSentJoints[otherAvatar]; } - + void queueAvatarDataPacket(QSharedPointer message) { _queuedAvatarDataPackets.push_back(message); } + void processQueuedAvatarDataPackets(); private: + std::vector> _queuedAvatarDataPackets; + AvatarSharedPointer _avatar { new AvatarData() }; uint16_t _lastReceivedSequenceNumber { 0 }; From 67e6d654d904a98dba29669153295d6065490d13 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Tue, 14 Feb 2017 15:32:32 -0800 Subject: [PATCH 05/35] introducing AvatarMixerSlavePool concept --- assignment-client/src/avatars/AvatarMixer.cpp | 13 ++ assignment-client/src/avatars/AvatarMixer.h | 7 +- .../src/avatars/AvatarMixerSlave.cpp | 44 ++++ .../src/avatars/AvatarMixerSlave.h | 38 ++++ .../src/avatars/AvatarMixerSlavePool.cpp | 190 ++++++++++++++++++ .../src/avatars/AvatarMixerSlavePool.h | 101 ++++++++++ 6 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 assignment-client/src/avatars/AvatarMixerSlave.cpp create mode 100644 assignment-client/src/avatars/AvatarMixerSlave.h create mode 100644 assignment-client/src/avatars/AvatarMixerSlavePool.cpp create mode 100644 assignment-client/src/avatars/AvatarMixerSlavePool.h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index a422ce7b3b..0590210c0b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -100,12 +100,23 @@ void AvatarMixer::start() { { auto start = usecTimestampNow(); auto nodeList = DependencyManager::get(); + + /** nodeList->eachNode([](const SharedNodePointer& node) { auto nodeData = dynamic_cast(node->getLinkedData()); if (nodeData) { nodeData->processQueuedAvatarDataPackets(); } }); + **/ + + nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + // mix across slave threads + { + _slavePool.processIncomingPackets(cbegin, cend); + } + }); + auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsElapsedTime += (end - start); } @@ -745,6 +756,8 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; + statsObject["threads"] = _slavePool.numThreads(); + statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 7a668c0a7f..93e4087b36 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -15,12 +15,16 @@ #ifndef hifi_AvatarMixer_h #define hifi_AvatarMixer_h +#include + #include #include #include #include "AvatarMixerClientData.h" +#include "AvatarMixerSlavePool.h" + /// Handles assignments of type AvatarMixer - distribution of avatar data to various clients class AvatarMixer : public ThreadedAssignment { Q_OBJECT @@ -96,7 +100,8 @@ private: RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs - std::unordered_map>> _pendingAvatarDataPackets; + + AvatarMixerSlavePool _slavePool; }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp new file mode 100644 index 0000000000..b7676dac58 --- /dev/null +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -0,0 +1,44 @@ +// +// AvatarMixerSlave.cpp +// assignment-client/src/avatar +// +// Created by Brad Hefta-Gaub on 2/14/2017. +// Copyright 2017 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 +// + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AvatarMixer.h" +#include "AvatarMixerClientData.h" +#include "AvatarMixerSlave.h" + + +void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { + _begin = begin; + _end = end; +} + +void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { + auto nodeData = dynamic_cast(node->getLinkedData()); + if (nodeData) { + nodeData->processQueuedAvatarDataPackets(); + } +} + diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h new file mode 100644 index 0000000000..6bd2f5acd4 --- /dev/null +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -0,0 +1,38 @@ +// +// AvatarMixerSlave.h +// assignment-client/src/avatar +// +// Created by Brad Hefta-Gaub on 2/14/2017. +// Copyright 2017 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 +// + +#ifndef hifi_AvatarMixerSlave_h +#define hifi_AvatarMixerSlave_h + +/* +#include +#include +#include +#include +#include +#include +*/ + +class AvatarMixerSlave { +public: + using ConstIter = NodeList::const_iterator; + + void configure(ConstIter begin, ConstIter end); + + void processIncomingPackets(const SharedNodePointer& node); + +private: + // frame state + ConstIter _begin; + ConstIter _end; +}; + +#endif // hifi_AvatarMixerSlave_h diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp new file mode 100644 index 0000000000..e50a8475a0 --- /dev/null +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -0,0 +1,190 @@ +// +// AvatarMixerSlavePool.cpp +// assignment-client/src/avatar +// +// Created by Brad Hefta-Gaub on 2/14/2017. +// Copyright 2017 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 +// + +#include +#include + +#include "AvatarMixerSlavePool.h" + +void AvatarMixerSlaveThread::run() { + while (true) { + wait(); + + // iterate over all available nodes + SharedNodePointer node; + while (try_pop(node)) { + processIncomingPackets(node); + } + + bool stopping = _stop; + notify(stopping); + if (stopping) { + return; + } + } +} + +void AvatarMixerSlaveThread::wait() { + { + Lock lock(_pool._mutex); + _pool._slaveCondition.wait(lock, [&] { + assert(_pool._numStarted <= _pool._numThreads); + return _pool._numStarted != _pool._numThreads; + }); + ++_pool._numStarted; + } + configure(_pool._begin, _pool._end); +} + +void AvatarMixerSlaveThread::notify(bool stopping) { + { + Lock lock(_pool._mutex); + assert(_pool._numFinished < _pool._numThreads); + ++_pool._numFinished; + if (stopping) { + ++_pool._numStopped; + } + } + _pool._poolCondition.notify_one(); +} + +bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node) { + return _pool._queue.try_pop(node); +} + +#ifdef AVATAR_SINGLE_THREADED +static AvatarMixerSlave slave; +#endif + +void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { + _begin = begin; + _end = end; + + // ???? + //_frame = frame; + //_throttlingRatio = throttlingRatio; + +#ifdef AVATAR_SINGLE_THREADED + slave.configure(_begin, _end, frame, throttlingRatio); + std::for_each(begin, end, [&](const SharedNodePointer& node) { + slave.processIncomingPackets(node); + }); +#else + // fill the queue + std::for_each(_begin, _end, [&](const SharedNodePointer& node) { + _queue.emplace(node); + }); + + { + Lock lock(_mutex); + + // start the job + _numStarted = _numFinished = 0; + _slaveCondition.notify_all(); + + // wait + _poolCondition.wait(lock, [&] { + assert(_numFinished <= _numThreads); + return _numFinished == _numThreads; + }); + + assert(_numStarted == _numThreads); + } + + assert(_queue.empty()); +#endif +} + +void AvatarMixerSlavePool::each(std::function functor) { +#ifdef AVATAR_SINGLE_THREADED + functor(slave); +#else + for (auto& slave : _slaves) { + functor(*slave.get()); + } +#endif +} + +void AvatarMixerSlavePool::setNumThreads(int numThreads) { + // clamp to allowed size + { + int maxThreads = QThread::idealThreadCount(); + if (maxThreads == -1) { + // idealThreadCount returns -1 if cores cannot be detected + static const int MAX_THREADS_IF_UNKNOWN = 4; + maxThreads = MAX_THREADS_IF_UNKNOWN; + } + + int clampedThreads = std::min(std::max(1, numThreads), maxThreads); + if (clampedThreads != numThreads) { + qWarning("%s: clamped to %d (was %d)", __FUNCTION__, clampedThreads, numThreads); + numThreads = clampedThreads; + } + } + + resize(numThreads); +} + +void AvatarMixerSlavePool::resize(int numThreads) { + 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); + + Lock lock(_mutex); + + if (numThreads > _numThreads) { + // start new slaves + for (int i = 0; i < numThreads - _numThreads; ++i) { + auto slave = new AvatarMixerSlaveThread(*this); + slave->start(); + _slaves.emplace_back(slave); + } + } else if (numThreads < _numThreads) { + auto extraBegin = _slaves.begin() + numThreads; + + // mark slaves to stop... + auto slave = extraBegin; + while (slave != _slaves.end()) { + (*slave)->_stop = true; + ++slave; + } + + // ...cycle them until they do stop... + _numStopped = 0; + while (_numStopped != (_numThreads - numThreads)) { + _numStarted = _numFinished = _numStopped; + _slaveCondition.notify_all(); + _poolCondition.wait(lock, [&] { + assert(_numFinished <= _numThreads); + return _numFinished == _numThreads; + }); + } + + // ...wait for threads to finish... + slave = extraBegin; + while (slave != _slaves.end()) { + QThread* thread = reinterpret_cast(slave->get()); + static const int MAX_THREAD_WAIT_TIME = 10; + thread->wait(MAX_THREAD_WAIT_TIME); + ++slave; + } + + // ...and erase them + _slaves.erase(extraBegin, _slaves.end()); + } + + _numThreads = _numStarted = _numFinished = numThreads; + assert(_numThreads == (int)_slaves.size()); +#endif +} diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h new file mode 100644 index 0000000000..ae4433d1c0 --- /dev/null +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -0,0 +1,101 @@ +// +// AvatarMixerSlavePool.h +// assignment-client/src/avatar +// +// Created by Brad Hefta-Gaub on 2/14/2017. +// Copyright 2017 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 +// + +#ifndef hifi_AvatarMixerSlavePool_h +#define hifi_AvatarMixerSlavePool_h + +#include +#include +#include + +#include + +#include + +#include + +#include "AvatarMixerSlave.h" + +class AvatarMixerSlavePool; + +class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave { + Q_OBJECT + using ConstIter = NodeList::const_iterator; + using Mutex = std::mutex; + using Lock = std::unique_lock; + +public: + AvatarMixerSlaveThread(AvatarMixerSlavePool& pool) : _pool(pool) {} + + void run() override final; + +private: + friend class AvatarMixerSlavePool; + + void wait(); + void notify(bool stopping); + bool try_pop(SharedNodePointer& node); + + AvatarMixerSlavePool& _pool; + bool _stop { false }; +}; + +// Slave pool for audio mixers +// AvatarMixerSlavePool is not thread-safe! It should be instantiated and used from a single thread. +class AvatarMixerSlavePool { + using Queue = tbb::concurrent_queue; + using Mutex = std::mutex; + using Lock = std::unique_lock; + using ConditionVariable = std::condition_variable; + +public: + using ConstIter = NodeList::const_iterator; + + AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); } + ~AvatarMixerSlavePool() { resize(0); } + + // iterate over all slaves + void each(std::function functor); + + void setNumThreads(int numThreads); + int numThreads() { return _numThreads; } + + // Jobs the slave pool can do... + void processIncomingPackets(ConstIter begin, ConstIter end); + + +private: + void resize(int numThreads); + + std::vector> _slaves; + + friend void AvatarMixerSlaveThread::wait(); + friend void AvatarMixerSlaveThread::notify(bool stopping); + friend bool AvatarMixerSlaveThread::try_pop(SharedNodePointer& node); + + // synchronization state + Mutex _mutex; + ConditionVariable _slaveCondition; + ConditionVariable _poolCondition; + int _numThreads { 0 }; + int _numStarted { 0 }; // guarded by _mutex + int _numFinished { 0 }; // guarded by _mutex + int _numStopped { 0 }; // guarded by _mutex + + // frame state + Queue _queue; + unsigned int _frame { 0 }; + float _throttlingRatio { 0.0f }; + ConstIter _begin; + ConstIter _end; +}; + +#endif // hifi_AvatarMixerSlavePool_h From 4f655bba3ff5b4ea49f11c97cb69bbf61d978c31 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 09:29:45 -0800 Subject: [PATCH 06/35] more hacking --- assignment-client/src/Agent.cpp | 12 ++- assignment-client/src/avatars/AvatarMixer.cpp | 85 ++++++++----------- assignment-client/src/avatars/AvatarMixer.h | 6 +- .../src/avatars/AvatarMixerClientData.cpp | 38 ++++++++- .../src/avatars/AvatarMixerClientData.h | 10 ++- .../src/avatars/AvatarMixerSlave.cpp | 2 +- .../src/avatars/AvatarMixerSlavePool.h | 2 - 7 files changed, 93 insertions(+), 62 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index bea677aeb6..806608cd5f 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -434,8 +434,16 @@ void Agent::executeScript() { connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater); _avatarAudioTimerThread.start(); - // 60Hz timer for avatar - QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatar); + // Agents should run at 45hz + static const int AVATAR_DATA_HZ = 45; + static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; + QTimer* avatarDataTimer = new QTimer(this); + connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); + avatarDataTimer->setSingleShot(false); + avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); + avatarDataTimer->setTimerType(Qt::PreciseTimer); + avatarDataTimer->start(); + _scriptEngine->run(); Frame::clearFrameHandler(AUDIO_FRAME_TYPE); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0590210c0b..1405e40f1b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -44,8 +44,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket"); + packetReceiver.registerListener(PacketType::ViewFrustum, this, "handleViewFrustumPacket"); - packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); @@ -56,6 +57,14 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); } +void AvatarMixer::queueIncomingPacket(QSharedPointer message, SharedNodePointer node) { + auto start = usecTimestampNow(); + getOrCreateClientData(node)->queuePacket(message, node); + auto end = usecTimestampNow(); + _queueIncomingPacketElapsedTime += (end - start); +} + + AvatarMixer::~AvatarMixer() { if (_broadcastTimer) { _broadcastTimer->deleteLater(); @@ -87,8 +96,11 @@ void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const Shar #include void AvatarMixer::start() { + auto nodeList = DependencyManager::get(); + //_slavePool.setNumThreads(1); // grins + while (!_isFinished) { _loopRate.increment(); @@ -99,34 +111,21 @@ void AvatarMixer::start() { // Allow nodes to process any pending/queued packets across our worker threads { auto start = usecTimestampNow(); - auto nodeList = DependencyManager::get(); - - /** - nodeList->eachNode([](const SharedNodePointer& node) { - auto nodeData = dynamic_cast(node->getLinkedData()); - if (nodeData) { - nodeData->processQueuedAvatarDataPackets(); - } - }); - **/ - nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { - // mix across slave threads - { - _slavePool.processIncomingPackets(cbegin, cend); - } + _slavePool.processIncomingPackets(cbegin, cend); }); - auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsElapsedTime += (end - start); } + + + // play nice with qt event-looping { // since we're a while loop we need to yield to qt's event processing auto start = usecTimestampNow(); QCoreApplication::processEvents(); - if (_isFinished) { // alert qt eventing that this is finished QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete); @@ -654,8 +653,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { void AvatarMixer::handleViewFrustumPacket(QSharedPointer message, SharedNodePointer senderNode) { auto start = usecTimestampNow(); - auto nodeList = DependencyManager::get(); - nodeList->getOrCreateLinkedData(senderNode); + getOrCreateClientData(senderNode); if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); @@ -671,8 +669,7 @@ void AvatarMixer::handleViewFrustumPacket(QSharedPointer messag void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode) { auto start = usecTimestampNow(); - auto nodeList = DependencyManager::get(); - nodeList->getOrCreateLinkedData(senderNode); + getOrCreateClientData(senderNode); if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); @@ -686,25 +683,10 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode) { - auto start = usecTimestampNow(); - - auto nodeList = DependencyManager::get(); - AvatarMixerClientData* nodeData = dynamic_cast(nodeList->getOrCreateLinkedData(senderNode)); - - if (nodeData) { - QMutexLocker linkedDataLocker(&nodeData->getMutex()); // NOTE: we might be able to safely assume this doesn't need to be locked! - nodeData->queueAvatarDataPacket(message); - } - - auto end = usecTimestampNow(); - _handleAvatarDataPacketElapsedTime += (end - start); -} - void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode) { auto start = usecTimestampNow(); auto nodeList = DependencyManager::get(); - nodeList->getOrCreateLinkedData(senderNode); + getOrCreateClientData(senderNode); if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); @@ -777,6 +759,8 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_y_processEvents"] = (float)_processEventsElapsedTime / (float)_numStatFrames; + statsObject["timing_average_y_queueIncomingPacket"] = (float)_queueIncomingPacketElapsedTime / (float)_numStatFrames; + statsObject["timing_average_z_handleAvatarDataPacket"] = (float)_handleAvatarDataPacketElapsedTime / (float)_numStatFrames; statsObject["timing_average_z_handleAvatarIdentityPacket"] = (float)_handleAvatarIdentityPacketElapsedTime / (float)_numStatFrames; @@ -799,6 +783,7 @@ void AvatarMixer::sendStatsPacket() { _handleRadiusIgnoreRequestPacketElapsedTime = 0; _handleRequestsDomainListDataPacketElapsedTime = 0; _processEventsElapsedTime = 0; + _queueIncomingPacketElapsedTime = 0; _processQueuedAvatarDataPacketsElapsedTime = 0; QJsonObject avatarsObject; @@ -875,23 +860,25 @@ void AvatarMixer::run() { start(); } +AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node) { + auto clientData = dynamic_cast(node->getLinkedData()); + + if (!clientData) { + node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); + clientData = dynamic_cast(node->getLinkedData()); + clientData->getAvatar().setDomainMinimumScale(_domainMinimumScale); + clientData->getAvatar().setDomainMaximumScale(_domainMaximumScale); + } + + return clientData; +} + void AvatarMixer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); - - float domainMinimumScale = _domainMinimumScale; - float domainMaximumScale = _domainMaximumScale; - - nodeList->linkedDataCreateCallback = [domainMinimumScale, domainMaximumScale] (Node* node) { - auto clientData = std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }; - clientData->getAvatar().setDomainMinimumScale(domainMinimumScale); - clientData->getAvatar().setDomainMaximumScale(domainMaximumScale); - - node->setLinkedData(std::move(clientData)); - }; // start the broadcastThread _broadcastThread.start(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 93e4087b36..b1d1dbef0b 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -40,8 +40,9 @@ public slots: void sendStatsPacket() override; private slots: + void queueIncomingPacket(QSharedPointer message, SharedNodePointer node); void handleViewFrustumPacket(QSharedPointer message, SharedNodePointer senderNode); - void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); + //void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); void handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode); @@ -53,6 +54,8 @@ private slots: private: + AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node); + void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); @@ -97,6 +100,7 @@ private: quint64 _processEventsElapsedTime { 0 }; quint64 _sendStatsElapsedTime { 0 }; + quint64 _queueIncomingPacketElapsedTime { 0 }; RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index ffa525bc35..5ba346ade9 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,13 +16,43 @@ #include "AvatarMixerClientData.h" -void AvatarMixerClientData::processQueuedAvatarDataPackets() { - for (auto message : _queuedAvatarDataPackets) { - parseData(*message); + + +void AvatarMixerClientData::queuePacket(QSharedPointer message, SharedNodePointer node) { + if (!_packetQueue.node) { + _packetQueue.node = node; } - _queuedAvatarDataPackets.clear(); + _packetQueue.push(message); } +// +// packetReceiver.registerListener(PacketType::ViewFrustum, this, "handleViewFrustumPacket"); +// packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); +// packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); +// packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); +// packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); +// packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); +// packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); + +void AvatarMixerClientData::processPackets() { + SharedNodePointer node = _packetQueue.node; + assert(_packetQueue.empty() || node); + _packetQueue.node.clear(); + + while (!_packetQueue.empty()) { + auto& packet = _packetQueue.back(); + + switch (packet->getType()) { + case PacketType::AvatarData: + parseData(*packet); + break; + default: + Q_UNREACHABLE(); + } + _packetQueue.pop(); + } + assert(_packetQueue.empty()); +} int AvatarMixerClientData::parseData(ReceivedMessage& message) { // pull the sequence number from the data first diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 611af1701d..f43df28052 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -118,11 +119,14 @@ public: return _lastOtherAvatarSentJoints[otherAvatar]; } - void queueAvatarDataPacket(QSharedPointer message) { _queuedAvatarDataPackets.push_back(message); } - void processQueuedAvatarDataPackets(); + void queuePacket(QSharedPointer message, SharedNodePointer node); + void processPackets(); private: - std::vector> _queuedAvatarDataPackets; + struct PacketQueue : public std::queue> { + QWeakPointer node; + }; + PacketQueue _packetQueue; AvatarSharedPointer _avatar { new AvatarData() }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index b7676dac58..44f2060b52 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -38,7 +38,7 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { auto nodeData = dynamic_cast(node->getLinkedData()); if (nodeData) { - nodeData->processQueuedAvatarDataPackets(); + nodeData->processPackets(); } } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index ae4433d1c0..9865f897b9 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -92,8 +92,6 @@ private: // frame state Queue _queue; - unsigned int _frame { 0 }; - float _throttlingRatio { 0.0f }; ConstIter _begin; ConstIter _end; }; From cddb72bbd77772374435309c03474b6120654bb7 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 12:11:17 -0800 Subject: [PATCH 07/35] clean up stats, add slave stats --- assignment-client/src/avatars/AvatarMixer.cpp | 48 ++++++++++++------- assignment-client/src/avatars/AvatarMixer.h | 1 + .../src/avatars/AvatarMixerClientData.cpp | 7 ++- .../src/avatars/AvatarMixerClientData.h | 2 +- .../src/avatars/AvatarMixerSlave.cpp | 23 +++++++-- .../src/avatars/AvatarMixerSlave.h | 6 +++ 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 1405e40f1b..94e78968bb 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -102,6 +102,7 @@ void AvatarMixer::start() { //_slavePool.setNumThreads(1); // grins while (!_isFinished) { + _numTightLoopFrames++; _loopRate.increment(); // FIXME - we really should sleep for the remainder of what we haven't spent time processing @@ -754,24 +755,19 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_b_ignoreCalculation"] = (float)_ignoreCalculationElapsedTime / (float)_numStatFrames; statsObject["timing_average_c_avatarDataPacking"] = (float)_avatarDataPackingElapsedTime / (float)_numStatFrames; statsObject["timing_average_d_packetSending"] = (float)_packetSendingElapsedTime / (float)_numStatFrames; - statsObject["timing_average_e_total_broadcastAvatarData"] = (float)_broadcastAvatarDataElapsedTime / (float)_numStatFrames; - - statsObject["timing_average_y_processEvents"] = (float)_processEventsElapsedTime / (float)_numStatFrames; - statsObject["timing_average_y_queueIncomingPacket"] = (float)_queueIncomingPacketElapsedTime / (float)_numStatFrames; - - - statsObject["timing_average_z_handleAvatarDataPacket"] = (float)_handleAvatarDataPacketElapsedTime / (float)_numStatFrames; - statsObject["timing_average_z_handleAvatarIdentityPacket"] = (float)_handleAvatarIdentityPacketElapsedTime / (float)_numStatFrames; - statsObject["timing_average_z_handleKillAvatarPacket"] = (float)_handleKillAvatarPacketElapsedTime / (float)_numStatFrames; - statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = (float)_handleNodeIgnoreRequestPacketElapsedTime / (float)_numStatFrames; - statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = (float)_handleRadiusIgnoreRequestPacketElapsedTime / (float)_numStatFrames; - statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = (float)_handleRequestsDomainListDataPacketElapsedTime / (float)_numStatFrames; - statsObject["timing_average_z_handleViewFrustumPacket"] = (float)_handleViewFrustumPacketElapsedTime / (float)_numStatFrames; - - statsObject["timing_average_z_processQueuedAvatarDataPackets"] = (float)_processQueuedAvatarDataPacketsElapsedTime / (float)_numStatFrames; - + // this things all occur on the frequency of the tight loop + statsObject["timing_average_y_processEvents"] = (float)_processEventsElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_y_queueIncomingPacket"] = (float)_queueIncomingPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleAvatarDataPacket"] = (float)_handleAvatarDataPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleAvatarIdentityPacket"] = (float)_handleAvatarIdentityPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleKillAvatarPacket"] = (float)_handleKillAvatarPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = (float)_handleNodeIgnoreRequestPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = (float)_handleRadiusIgnoreRequestPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = (float)_handleRequestsDomainListDataPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_handleViewFrustumPacket"] = (float)_handleViewFrustumPacketElapsedTime / (float)_numTightLoopFrames; + statsObject["timing_average_z_processQueuedAvatarDataPackets"] = (float)_processQueuedAvatarDataPacketsElapsedTime / (float)_numTightLoopFrames; statsObject["timing_sendStats"] = (float)_sendStatsElapsedTime; @@ -786,8 +782,24 @@ void AvatarMixer::sendStatsPacket() { _queueIncomingPacketElapsedTime = 0; _processQueuedAvatarDataPacketsElapsedTime = 0; - QJsonObject avatarsObject; + QJsonObject slavesObject; + // gather stats + int slaveNumber = 1; + _slavePool.each([&](AvatarMixerSlave& slave) { + QJsonObject slaveObject; + int nodesProcessed, packetsProcessed; + quint64 processIncomingPacketsElapsedTime; + slave.harvestStats(nodesProcessed, packetsProcessed, processIncomingPacketsElapsedTime); + slaveObject["nodesProcessed"] = nodesProcessed; + slaveObject["packetsProcessed"] = packetsProcessed; + slaveObject["timing_average_processIncomingPackets"] = (float)processIncomingPacketsElapsedTime / (float)_numTightLoopFrames; + slavesObject[QString::number(slaveNumber)] = slaveObject; + slaveNumber++; + }); + statsObject["timing_slaves"] = slavesObject; + + QJsonObject avatarsObject; auto nodeList = DependencyManager::get(); // add stats for each listerner nodeList->eachNode([&](const SharedNodePointer& node) { @@ -818,11 +830,13 @@ void AvatarMixer::sendStatsPacket() { }); statsObject["z_avatars"] = avatarsObject; + ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; _sumIdentityPackets = 0; _numStatFrames = 0; + _numTightLoopFrames = 0; _broadcastAvatarDataElapsedTime = 0; _displayNameManagementElapsedTime = 0; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index b1d1dbef0b..c8d1516e0d 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -69,6 +69,7 @@ private: int _sumListeners { 0 }; int _numStatFrames { 0 }; + std::atomic _numTightLoopFrames; int _sumIdentityPackets { 0 }; float _maxKbpsPerNode = 0.0f; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 5ba346ade9..09e3a6a87a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -34,7 +34,8 @@ void AvatarMixerClientData::queuePacket(QSharedPointer message, // packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); // packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); -void AvatarMixerClientData::processPackets() { +int AvatarMixerClientData::processPackets() { + int packetsProcessed = 0; SharedNodePointer node = _packetQueue.node; assert(_packetQueue.empty() || node); _packetQueue.node.clear(); @@ -42,6 +43,8 @@ void AvatarMixerClientData::processPackets() { while (!_packetQueue.empty()) { auto& packet = _packetQueue.back(); + packetsProcessed++; + switch (packet->getType()) { case PacketType::AvatarData: parseData(*packet); @@ -52,6 +55,8 @@ void AvatarMixerClientData::processPackets() { _packetQueue.pop(); } assert(_packetQueue.empty()); + + return packetsProcessed; } int AvatarMixerClientData::parseData(ReceivedMessage& message) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index f43df28052..c69599cc26 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -120,7 +120,7 @@ public: } void queuePacket(QSharedPointer message, SharedNodePointer node); - void processPackets(); + int processPackets(); // returns number of packets processed private: struct PacketQueue : public std::queue> { diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 44f2060b52..06d21b51b9 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -35,10 +35,23 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _end = end; } -void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { - auto nodeData = dynamic_cast(node->getLinkedData()); - if (nodeData) { - nodeData->processPackets(); - } +void AvatarMixerSlave::harvestStats(int& nodesProcessed, int& packetsProcessed, quint64& processIncomingPacketsElapsedTime) { + nodesProcessed = _nodesProcessed; + packetsProcessed = _packetsProcessed; + processIncomingPacketsElapsedTime = _processIncomingPacketsElapsedTime; + _packetsProcessed = _nodesProcessed = 0; + _processIncomingPacketsElapsedTime = 0; +} + + +void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { + auto start = usecTimestampNow(); + auto nodeData = dynamic_cast(node->getLinkedData()); + if (nodeData) { + _nodesProcessed++; + _packetsProcessed += nodeData->processPackets(); + } + auto end = usecTimestampNow(); + _processIncomingPacketsElapsedTime += (end - start); } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 6bd2f5acd4..1abba8d46c 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -29,10 +29,16 @@ public: void processIncomingPackets(const SharedNodePointer& node); + void harvestStats(int& nodesProcessed, int& packetsProcessed, quint64& processIncomingPacketsElapsedTime); + private: // frame state ConstIter _begin; ConstIter _end; + + int _nodesProcessed { 0 }; + int _packetsProcessed { 0 }; + quint64 _processIncomingPacketsElapsedTime { 0 }; }; #endif // hifi_AvatarMixerSlave_h From 00086fcc061a2c1b095b8d0223e8b8911f705ef2 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 13:00:30 -0800 Subject: [PATCH 08/35] wire up thread count to settings, tweak stats --- assignment-client/src/avatars/AvatarMixer.cpp | 31 ++++++++++++++++--- .../resources/describe-settings.json | 16 ++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 94e78968bb..73c9aef8e1 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -785,14 +785,17 @@ void AvatarMixer::sendStatsPacket() { QJsonObject slavesObject; // gather stats int slaveNumber = 1; + int tightLoopFrames = _numTightLoopFrames; + int tenTimesPerFrame = tightLoopFrames * 10; _slavePool.each([&](AvatarMixerSlave& slave) { QJsonObject slaveObject; int nodesProcessed, packetsProcessed; quint64 processIncomingPacketsElapsedTime; slave.harvestStats(nodesProcessed, packetsProcessed, processIncomingPacketsElapsedTime); - slaveObject["nodesProcessed"] = nodesProcessed; - slaveObject["packetsProcessed"] = packetsProcessed; - slaveObject["timing_average_processIncomingPackets"] = (float)processIncomingPacketsElapsedTime / (float)_numTightLoopFrames; + slaveObject["nodesProcessed"] = (nodesProcessed > tenTimesPerFrame) ? nodesProcessed / tightLoopFrames : ((float)nodesProcessed / (float)tightLoopFrames); + slaveObject["packetsProcessed"] = (packetsProcessed > tenTimesPerFrame) ? packetsProcessed / tightLoopFrames : ((float)packetsProcessed / (float)tightLoopFrames); + slaveObject["timing_average_processIncomingPackets"] = (processIncomingPacketsElapsedTime > tenTimesPerFrame) + ? processIncomingPacketsElapsedTime / tightLoopFrames : ((float)processIncomingPacketsElapsedTime / (float)tightLoopFrames); slavesObject[QString::number(slaveNumber)] = slaveObject; slaveNumber++; @@ -914,10 +917,13 @@ void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAdd void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATAR_MIXER_SETTINGS_KEY = "avatar_mixer"; + QJsonObject avatarMixerGroupObject = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject(); + + const QString NODE_SEND_BANDWIDTH_KEY = "max_node_send_bandwidth"; const float DEFAULT_NODE_SEND_BANDWIDTH = 5.0f; - QJsonValue nodeBandwidthValue = domainSettings[AVATAR_MIXER_SETTINGS_KEY].toObject()[NODE_SEND_BANDWIDTH_KEY]; + QJsonValue nodeBandwidthValue = avatarMixerGroupObject[NODE_SEND_BANDWIDTH_KEY]; if (!nodeBandwidthValue.isDouble()) { qDebug() << NODE_SEND_BANDWIDTH_KEY << "is not a double - will continue with default value"; } @@ -925,6 +931,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { _maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA; qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps."; + const QString AUTO_THREADS = "auto_threads"; + bool autoThreads = avatarMixerGroupObject[AUTO_THREADS].toBool(); + if (!autoThreads) { + bool ok; + const QString NUM_THREADS = "num_threads"; + int numThreads = avatarMixerGroupObject[NUM_THREADS].toString().toInt(&ok); + if (!ok) { + qWarning() << "Avatar mixer: Error reading thread count. Using 1 thread."; + numThreads = 1; + } + qDebug() << "Avatar mixer will use specified number of threads:" << numThreads; + _slavePool.setNumThreads(numThreads); + } else { + qDebug() << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads."; + } + const QString AVATARS_SETTINGS_KEY = "avatars"; static const QString MIN_SCALE_OPTION = "min_avatar_scale"; @@ -942,4 +964,5 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { qDebug() << "This domain requires a minimum avatar scale of" << _domainMinimumScale << "and a maximum avatar scale of" << _domainMaximumScale; + } diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 045af9dc09..12dcb90f47 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1299,6 +1299,22 @@ "placeholder": 5.0, "default": 5.0, "advanced": true + }, + { + "name": "auto_threads", + "label": "Automatically determine thread count", + "type": "checkbox", + "help": "Allow system to determine number of threads (recommended)", + "default": false, + "advanced": true + }, + { + "name": "num_threads", + "label": "Number of Threads", + "help": "Threads to spin up for avatar mixing (if not automatically set)", + "placeholder": "1", + "default": "1", + "advanced": true } ] } From babeabede5ed0f37eacf6c7545606a779a15bbe1 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 13:49:16 -0800 Subject: [PATCH 09/35] more cleanup and stats rework --- assignment-client/src/avatars/AvatarMixer.cpp | 146 ++++++++++-------- assignment-client/src/avatars/AvatarMixer.h | 3 + 2 files changed, 87 insertions(+), 62 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 73c9aef8e1..f5455cbe67 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -113,13 +113,26 @@ void AvatarMixer::start() { { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + auto end = usecTimestampNow(); + _processQueuedAvatarDataPacketsLockWaitElapsedTime += (end - start); + _slavePool.processIncomingPackets(cbegin, cend); }); auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsElapsedTime += (end - start); } - + // this is where we need to put the real work... + { + /* + auto start = usecTimestampNow(); + nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + _slavePool.processIncomingPackets(cbegin, cend); + }); + auto end = usecTimestampNow(); + _processQueuedAvatarDataPacketsElapsedTime += (end - start); + */ + } // play nice with qt event-looping @@ -139,6 +152,41 @@ void AvatarMixer::start() { } +void AvatarMixer::manageDisplayName(AvatarMixerClientData* nodeData, const SharedNodePointer& node) { + AvatarData& avatar = nodeData->getAvatar(); + + quint64 startDisplayNameManagement = usecTimestampNow(); + if (nodeData->getAvatarSessionDisplayNameMustChange()) { + const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); + if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { + _sessionDisplayNames.remove(existingBaseDisplayName); + } + + QString baseName = avatar.getDisplayName().trimmed(); + const QRegularExpression curses { "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?). + baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk. + const QRegularExpression trailingDigits { "\\s*_\\d+$" }; // whitespace "_123" + baseName = baseName.remove(trailingDigits); + if (baseName.isEmpty()) { + baseName = "anonymous"; + } + + QPair& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want. + int& highWater = soFar.first; + nodeData->setBaseDisplayName(baseName); + QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName; + avatar.setSessionDisplayName(sessionDisplayName); + highWater++; + soFar.second++; // refcount + nodeData->flagIdentityChange(); + nodeData->setAvatarSessionDisplayNameMustChange(false); + sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. + qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); + } + quint64 endDisplayNameManagement = usecTimestampNow(); + _displayNameManagementElapsedTime += (endDisplayNameManagement - startDisplayNameManagement); +} + // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. @@ -319,37 +367,7 @@ void AvatarMixer::broadcastAvatarData() { // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - quint64 startDisplayNameManagement = usecTimestampNow(); - if (nodeData->getAvatarSessionDisplayNameMustChange()) { - const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); - if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { - _sessionDisplayNames.remove(existingBaseDisplayName); - } - - QString baseName = avatar.getDisplayName().trimmed(); - const QRegularExpression curses{ "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?). - baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk. - const QRegularExpression trailingDigits{ "\\s*_\\d+$" }; // whitespace "_123" - baseName = baseName.remove(trailingDigits); - if (baseName.isEmpty()) { - baseName = "anonymous"; - } - - QPair& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want. - int& highWater = soFar.first; - nodeData->setBaseDisplayName(baseName); - QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName; - avatar.setSessionDisplayName(sessionDisplayName); - highWater++; - soFar.second++; // refcount - nodeData->flagIdentityChange(); - nodeData->setAvatarSessionDisplayNameMustChange(false); - sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. - qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); - } - quint64 endDisplayNameManagement = usecTimestampNow(); - _displayNameManagementElapsedTime += (endDisplayNameManagement - startDisplayNameManagement); - + manageDisplayName(nodeData, node); // this is an AGENT we have received head data from // send back a packet with other active node data to this node @@ -758,19 +776,42 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_e_total_broadcastAvatarData"] = (float)_broadcastAvatarDataElapsedTime / (float)_numStatFrames; // this things all occur on the frequency of the tight loop - statsObject["timing_average_y_processEvents"] = (float)_processEventsElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_y_queueIncomingPacket"] = (float)_queueIncomingPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleAvatarDataPacket"] = (float)_handleAvatarDataPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleAvatarIdentityPacket"] = (float)_handleAvatarIdentityPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleKillAvatarPacket"] = (float)_handleKillAvatarPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = (float)_handleNodeIgnoreRequestPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = (float)_handleRadiusIgnoreRequestPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = (float)_handleRequestsDomainListDataPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_handleViewFrustumPacket"] = (float)_handleViewFrustumPacketElapsedTime / (float)_numTightLoopFrames; - statsObject["timing_average_z_processQueuedAvatarDataPackets"] = (float)_processQueuedAvatarDataPacketsElapsedTime / (float)_numTightLoopFrames; + int tightLoopFrames = _numTightLoopFrames; + int tenTimesPerFrame = tightLoopFrames * 10; + #define TIGHT_LOOP_STAT(x) (x > tenTimesPerFrame) ? x / tightLoopFrames : ((float)x / (float)tightLoopFrames); + + statsObject["timing_average_y_processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); + statsObject["timing_average_y_queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); + statsObject["timing_average_z_handleAvatarDataPacket"] = TIGHT_LOOP_STAT(_handleAvatarDataPacketElapsedTime); + statsObject["timing_average_z_handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); + statsObject["timing_average_z_handleKillAvatarPacket"] = TIGHT_LOOP_STAT(_handleKillAvatarPacketElapsedTime); + statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleNodeIgnoreRequestPacketElapsedTime); + statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleRadiusIgnoreRequestPacketElapsedTime); + statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT(_handleRequestsDomainListDataPacketElapsedTime); + statsObject["timing_average_z_handleViewFrustumPacket"] = TIGHT_LOOP_STAT(_handleViewFrustumPacketElapsedTime); + statsObject["timing_average_z_processQueuedAvatarDataPackets"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsElapsedTime); + statsObject["timing_average_z_processQueuedAvatarDataPacketsLockWait"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsLockWaitElapsedTime); statsObject["timing_sendStats"] = (float)_sendStatsElapsedTime; + + QJsonObject slavesObject; + // gather stats + int slaveNumber = 1; + _slavePool.each([&](AvatarMixerSlave& slave) { + QJsonObject slaveObject; + int nodesProcessed, packetsProcessed; + quint64 processIncomingPacketsElapsedTime; + slave.harvestStats(nodesProcessed, packetsProcessed, processIncomingPacketsElapsedTime); + slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(nodesProcessed); + slaveObject["packetsProcessed"] = TIGHT_LOOP_STAT(packetsProcessed); + slaveObject["timing_average_processIncomingPackets"] = TIGHT_LOOP_STAT(processIncomingPacketsElapsedTime); + + slavesObject[QString::number(slaveNumber)] = slaveObject; + slaveNumber++; + }); + statsObject["timing_slaves"] = slavesObject; + _handleViewFrustumPacketElapsedTime = 0; _handleAvatarDataPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; @@ -781,26 +822,7 @@ void AvatarMixer::sendStatsPacket() { _processEventsElapsedTime = 0; _queueIncomingPacketElapsedTime = 0; _processQueuedAvatarDataPacketsElapsedTime = 0; - - QJsonObject slavesObject; - // gather stats - int slaveNumber = 1; - int tightLoopFrames = _numTightLoopFrames; - int tenTimesPerFrame = tightLoopFrames * 10; - _slavePool.each([&](AvatarMixerSlave& slave) { - QJsonObject slaveObject; - int nodesProcessed, packetsProcessed; - quint64 processIncomingPacketsElapsedTime; - slave.harvestStats(nodesProcessed, packetsProcessed, processIncomingPacketsElapsedTime); - slaveObject["nodesProcessed"] = (nodesProcessed > tenTimesPerFrame) ? nodesProcessed / tightLoopFrames : ((float)nodesProcessed / (float)tightLoopFrames); - slaveObject["packetsProcessed"] = (packetsProcessed > tenTimesPerFrame) ? packetsProcessed / tightLoopFrames : ((float)packetsProcessed / (float)tightLoopFrames); - slaveObject["timing_average_processIncomingPackets"] = (processIncomingPacketsElapsedTime > tenTimesPerFrame) - ? processIncomingPacketsElapsedTime / tightLoopFrames : ((float)processIncomingPacketsElapsedTime / (float)tightLoopFrames); - - slavesObject[QString::number(slaveNumber)] = slaveObject; - slaveNumber++; - }); - statsObject["timing_slaves"] = slavesObject; + _processQueuedAvatarDataPacketsLockWaitElapsedTime = 0; QJsonObject avatarsObject; auto nodeList = DependencyManager::get(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index c8d1516e0d..8c06f7fc43 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -60,6 +60,8 @@ private: void parseDomainServerSettings(const QJsonObject& domainSettings); void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); + void manageDisplayName(AvatarMixerClientData* nodeData, const SharedNodePointer& node); + QThread _broadcastThread; p_high_resolution_clock::time_point _lastFrameTimestamp; @@ -98,6 +100,7 @@ private: quint64 _handleRadiusIgnoreRequestPacketElapsedTime { 0 }; quint64 _handleRequestsDomainListDataPacketElapsedTime { 0 }; quint64 _processQueuedAvatarDataPacketsElapsedTime { 0 }; + quint64 _processQueuedAvatarDataPacketsLockWaitElapsedTime { 0 }; quint64 _processEventsElapsedTime { 0 }; quint64 _sendStatsElapsedTime { 0 }; From faa8e629a03f39c4b5a0acd3da2431462f17006f Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 20:28:32 -0800 Subject: [PATCH 10/35] checkpoint --- assignment-client/src/avatars/AvatarMixer.cpp | 33 +++++++++++------ assignment-client/src/avatars/AvatarMixer.h | 2 +- libraries/avatars/src/AvatarData.cpp | 36 ------------------- libraries/avatars/src/AvatarData.h | 30 ++++++++-------- libraries/shared/src/SpatiallyNestable.h | 6 ++-- 5 files changed, 40 insertions(+), 67 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index f5455cbe67..acb62e6225 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -99,8 +99,6 @@ void AvatarMixer::start() { auto nodeList = DependencyManager::get(); - //_slavePool.setNumThreads(1); // grins - while (!_isFinished) { _numTightLoopFrames++; _loopRate.increment(); @@ -122,6 +120,19 @@ void AvatarMixer::start() { _processQueuedAvatarDataPacketsElapsedTime += (end - start); } + // process pending display names... this doesn't currently run on multiple threads, because it + // side-effects the mixer's data, which is fine because it's a very low cost operation + { + auto start = usecTimestampNow(); + nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { + manageDisplayName(node); + }); + }); + auto end = usecTimestampNow(); + _displayNameManagementElapsedTime += (end - start); + } + // this is where we need to put the real work... { /* @@ -152,11 +163,12 @@ void AvatarMixer::start() { } -void AvatarMixer::manageDisplayName(AvatarMixerClientData* nodeData, const SharedNodePointer& node) { - AvatarData& avatar = nodeData->getAvatar(); - - quint64 startDisplayNameManagement = usecTimestampNow(); - if (nodeData->getAvatarSessionDisplayNameMustChange()) { +// NOTE: nodeData->getAvatar() might be side effected, most be called when access to node/nodeData +// is guarenteed to not be accessed by other thread +void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { + AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) { + AvatarData& avatar = nodeData->getAvatar(); const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { _sessionDisplayNames.remove(existingBaseDisplayName); @@ -183,8 +195,6 @@ void AvatarMixer::manageDisplayName(AvatarMixerClientData* nodeData, const Share sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } - quint64 endDisplayNameManagement = usecTimestampNow(); - _displayNameManagementElapsedTime += (endDisplayNameManagement - startDisplayNameManagement); } // NOTE: some additional optimizations to consider. @@ -367,7 +377,7 @@ void AvatarMixer::broadcastAvatarData() { // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - manageDisplayName(nodeData, node); + //manageDisplayName(node); // this is an AGENT we have received head data from // send back a packet with other active node data to this node @@ -769,7 +779,6 @@ void AvatarMixer::sendStatsPacket() { statsObject["tight_loop_rate"] = _loopRate.rate(); // broadcastAvatarDataElapsed timing details... - statsObject["timing_average_a_displayNameManagement"] = (float)_displayNameManagementElapsedTime / (float)_numStatFrames; statsObject["timing_average_b_ignoreCalculation"] = (float)_ignoreCalculationElapsedTime / (float)_numStatFrames; statsObject["timing_average_c_avatarDataPacking"] = (float)_avatarDataPackingElapsedTime / (float)_numStatFrames; statsObject["timing_average_d_packetSending"] = (float)_packetSendingElapsedTime / (float)_numStatFrames; @@ -782,6 +791,8 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_y_processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); statsObject["timing_average_y_queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); + + statsObject["timing_average_z_displayNameManagement"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); statsObject["timing_average_z_handleAvatarDataPacket"] = TIGHT_LOOP_STAT(_handleAvatarDataPacketElapsedTime); statsObject["timing_average_z_handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); statsObject["timing_average_z_handleKillAvatarPacket"] = TIGHT_LOOP_STAT(_handleKillAvatarPacketElapsedTime); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 8c06f7fc43..e0b03804a9 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -60,7 +60,7 @@ private: void parseDomainServerSettings(const QJsonObject& domainSettings); void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); - void manageDisplayName(AvatarMixerClientData* nodeData, const SharedNodePointer& node); + void manageDisplayName(const SharedNodePointer& node); QThread _broadcastThread; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 47a8cc6e6e..c55d06f2e7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -120,10 +120,6 @@ void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { updateAttitude(); } -float AvatarData::getTargetScale() const { - return _targetScale; -} - void AvatarData::setTargetScale(float targetScale) { auto newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); if (_targetScale != newValue) { @@ -152,38 +148,6 @@ void AvatarData::lazyInitHeadData() { } -bool AvatarData::avatarBoundingBoxChangedSince(quint64 time) { - return _avatarBoundingBoxChanged >= time; -} - -bool AvatarData::avatarScaleChangedSince(quint64 time) { - return _avatarScaleChanged >= time; -} - -bool AvatarData::lookAtPositionChangedSince(quint64 time) { - return _headData->lookAtPositionChangedSince(time); -} - -bool AvatarData::audioLoudnessChangedSince(quint64 time) { - return _headData->audioLoudnessChangedSince(time); -} - -bool AvatarData::sensorToWorldMatrixChangedSince(quint64 time) { - return _sensorToWorldMatrixChanged >= time; -} - -bool AvatarData::additionalFlagsChangedSince(quint64 time) { - return _additionalFlagsChanged >= time; -} - -bool AvatarData::parentInfoChangedSince(quint64 time) { - return _parentChanged >= time; -} - -bool AvatarData::faceTrackerInfoChangedSince(quint64 time) { - return true; // FIXME! -} - float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) { auto distance = glm::distance(_globalPosition, viewerPosition); float result = ROTATION_CHANGE_179D; // assume worst diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b28501eead..964bc4a6df 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -419,7 +419,6 @@ public: void setAudioAverageLoudness(float value) { _headData->setAudioAverageLoudness(value); } // Scale - float getTargetScale() const; virtual void setTargetScale(float targetScale); float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } @@ -534,8 +533,8 @@ public: QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); - glm::vec3 getClientGlobalPosition() { return _globalPosition; } - glm::vec3 getGlobalBoundingBoxCorner() { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } + glm::vec3 getClientGlobalPosition() const { return _globalPosition; } + glm::vec3 getGlobalBoundingBoxCorner() const { return _globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions; } Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); @@ -550,7 +549,7 @@ public: Q_INVOKABLE float getDataRate(const QString& rateName = QString("")) const; Q_INVOKABLE float getUpdateRate(const QString& rateName = QString("")) const; - int getJointCount() { return _jointData.size(); } + int getJointCount() const { return _jointData.size(); } QVector getLastSentJointData() { QReadLocker readLock(&_jointDataLock); @@ -571,7 +570,7 @@ public slots: virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } - float getTargetScale() { return _targetScale; } + float getTargetScale() const { return _targetScale; } // why is this a slot? void resetLastSent() { _lastToByteArray = 0; } @@ -581,18 +580,17 @@ protected: float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition); float getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition); - bool avatarBoundingBoxChangedSince(quint64 time); - bool avatarScaleChangedSince(quint64 time); - bool lookAtPositionChangedSince(quint64 time); - bool audioLoudnessChangedSince(quint64 time); - bool sensorToWorldMatrixChangedSince(quint64 time); - bool additionalFlagsChangedSince(quint64 time); + bool avatarBoundingBoxChangedSince(quint64 time) const { return _avatarBoundingBoxChanged >= time; } + bool avatarScaleChangedSince(quint64 time) const { return _avatarScaleChanged >= time; } + bool lookAtPositionChangedSince(quint64 time) const { return _headData->lookAtPositionChangedSince(time); } + bool audioLoudnessChangedSince(quint64 time) const { return _headData->audioLoudnessChangedSince(time); } + bool sensorToWorldMatrixChangedSince(quint64 time) const { return _sensorToWorldMatrixChanged >= time; } + bool additionalFlagsChangedSince(quint64 time) const { return _additionalFlagsChanged >= time; } + bool parentInfoChangedSince(quint64 time) const { return _parentChanged >= time; } + bool faceTrackerInfoChangedSince(quint64 time) const { return true; } // FIXME - bool hasParent() { return !getParentID().isNull(); } - bool parentInfoChangedSince(quint64 time); - - bool hasFaceTracker() { return _headData ? _headData->_isFaceTrackerConnected : false; } - bool faceTrackerInfoChangedSince(quint64 time); + bool hasParent() const { return !getParentID().isNull(); } + bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } glm::vec3 _handPosition; virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; } diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index be285eff53..820c8685d7 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -179,9 +179,9 @@ public: const glm::vec3& localVelocity, const glm::vec3& localAngularVelocity); - bool scaleChangedSince(quint64 time) { return _scaleChanged > time; } - bool tranlationChangedSince(quint64 time) { return _translationChanged > time; } - bool rotationChangedSince(quint64 time) { return _rotationChanged > time; } + bool scaleChangedSince(quint64 time) const { return _scaleChanged > time; } + bool tranlationChangedSince(quint64 time) const { return _translationChanged > time; } + bool rotationChangedSince(quint64 time) const { return _rotationChanged > time; } protected: const NestableType _nestableType; // EntityItem or an AvatarData From bd722165743e0fd6ed65adf2b675f3874b420d90 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 20:49:45 -0800 Subject: [PATCH 11/35] add support for multiple jobs types on the slave pool --- .../src/avatars/AvatarMixerSlave.cpp | 2 ++ .../src/avatars/AvatarMixerSlave.h | 1 + .../src/avatars/AvatarMixerSlavePool.cpp | 34 +++++++++++++------ .../src/avatars/AvatarMixerSlavePool.h | 12 ++++--- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 06d21b51b9..784b10ebfe 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -55,3 +55,5 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { _processIncomingPacketsElapsedTime += (end - start); } +void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { +} diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 1abba8d46c..d75c6ae396 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -28,6 +28,7 @@ public: void configure(ConstIter begin, ConstIter end); void processIncomingPackets(const SharedNodePointer& node); + void anotherJob(const SharedNodePointer& node); void harvestStats(int& nodesProcessed, int& packetsProcessed, quint64& processIncomingPacketsElapsedTime); diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index e50a8475a0..1b335f8383 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -21,7 +21,7 @@ void AvatarMixerSlaveThread::run() { // iterate over all available nodes SharedNodePointer node; while (try_pop(node)) { - processIncomingPackets(node); + (this->*_function)(node); } bool stopping = _stop; @@ -41,7 +41,10 @@ void AvatarMixerSlaveThread::wait() { }); ++_pool._numStarted; } - configure(_pool._begin, _pool._end); + if (_pool._configure) { + _pool._configure(*this); + } + _function = _pool._function; } void AvatarMixerSlaveThread::notify(bool stopping) { @@ -65,18 +68,26 @@ static AvatarMixerSlave slave; #endif void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { + _function = &AvatarMixerSlave::processIncomingPackets; + _configure = [](AvatarMixerSlave& slave) {}; + run(begin, end); +} + +void AvatarMixerSlavePool::anotherJob(ConstIter begin, ConstIter end) { + _function = &AvatarMixerSlave::anotherJob; + _configure = [](AvatarMixerSlave& slave) {}; + run(begin, end); +} + +void AvatarMixerSlavePool::run(ConstIter begin, ConstIter end) { _begin = begin; _end = end; - // ???? - //_frame = frame; - //_throttlingRatio = throttlingRatio; - -#ifdef AVATAR_SINGLE_THREADED - slave.configure(_begin, _end, frame, throttlingRatio); +#ifdef AUDIO_SINGLE_THREADED + _configure(slave); std::for_each(begin, end, [&](const SharedNodePointer& node) { - slave.processIncomingPackets(node); - }); + _function(slave, node); +}); #else // fill the queue std::for_each(_begin, _end, [&](const SharedNodePointer& node) { @@ -86,7 +97,7 @@ void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end { Lock lock(_mutex); - // start the job + // run _numStarted = _numFinished = 0; _slaveCondition.notify_all(); @@ -103,6 +114,7 @@ void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end #endif } + void AvatarMixerSlavePool::each(std::function functor) { #ifdef AVATAR_SINGLE_THREADED functor(slave); diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index 9865f897b9..8f13477a93 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -45,6 +45,7 @@ private: bool try_pop(SharedNodePointer& node); AvatarMixerSlavePool& _pool; + void (AvatarMixerSlave::*_function)(const SharedNodePointer& node) { nullptr }; bool _stop { false }; }; @@ -62,17 +63,18 @@ public: AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); } ~AvatarMixerSlavePool() { resize(0); } + // Jobs the slave pool can do... + void processIncomingPackets(ConstIter begin, ConstIter end); + void anotherJob(ConstIter begin, ConstIter end); + // iterate over all slaves void each(std::function functor); void setNumThreads(int numThreads); int numThreads() { return _numThreads; } - // Jobs the slave pool can do... - void processIncomingPackets(ConstIter begin, ConstIter end); - - private: + void run(ConstIter begin, ConstIter end); void resize(int numThreads); std::vector> _slaves; @@ -85,6 +87,8 @@ private: Mutex _mutex; ConditionVariable _slaveCondition; ConditionVariable _poolCondition; + void (AvatarMixerSlave::*_function)(const SharedNodePointer& node); + std::function _configure; int _numThreads { 0 }; int _numStarted { 0 }; // guarded by _mutex int _numFinished { 0 }; // guarded by _mutex From d22f4c1dd7be72600021de00bd289d0284b30ef3 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 21:29:03 -0800 Subject: [PATCH 12/35] partial const migration work so mixer will not side-effect AvatarData --- assignment-client/src/avatars/AvatarMixer.cpp | 13 +- libraries/avatars/src/AvatarData.cpp | 406 +++++++++++++++++- libraries/avatars/src/AvatarData.h | 14 +- 3 files changed, 417 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index acb62e6225..de5a9b3f2c 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -301,7 +301,7 @@ void AvatarMixer::broadcastAvatarData() { ++_sumListeners; nodeData->resetInViewStats(); - AvatarData& avatar = nodeData->getAvatar(); + const AvatarData& avatar = nodeData->getAvatar(); glm::vec3 myPosition = avatar.getClientGlobalPosition(); // reset the internal state for correct random number distribution @@ -377,8 +377,6 @@ void AvatarMixer::broadcastAvatarData() { // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - //manageDisplayName(node); - // this is an AGENT we have received head data from // send back a packet with other active node data to this node nodeList->eachMatchingNode( @@ -470,7 +468,7 @@ void AvatarMixer::broadcastAvatarData() { sendIdentityPacket(otherNodeData, node); } - AvatarData& otherAvatar = otherNodeData->getAvatar(); + const AvatarData& otherAvatar = otherNodeData->getAvatar(); // Decide whether to send this avatar's data based on it's distance from us // The full rate distance is the distance at which EVERY update will be sent for this avatar @@ -550,7 +548,7 @@ void AvatarMixer::broadcastAvatarData() { QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; - auto bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); + QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); numAvatarDataBytes += avatarPacketList->write(bytes); avatarPacketList->endSegment(); @@ -916,8 +914,9 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node if (!clientData) { node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); clientData = dynamic_cast(node->getLinkedData()); - clientData->getAvatar().setDomainMinimumScale(_domainMinimumScale); - clientData->getAvatar().setDomainMaximumScale(_domainMaximumScale); + auto& avatar = clientData->getAvatar(); + avatar.setDomainMinimumScale(_domainMinimumScale); + avatar.setDomainMaximumScale(_domainMaximumScale); } return clientData; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c55d06f2e7..cbd5d42263 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -137,10 +137,10 @@ void AvatarData::setHandPosition(const glm::vec3& handPosition) { _handPosition = glm::inverse(getOrientation()) * (handPosition - getPosition()); } -void AvatarData::lazyInitHeadData() { +void AvatarData::lazyInitHeadData() const { // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { - _headData = new HeadData(this); + _headData = new HeadData(const_cast(this)); } if (_forceFaceTrackerConnected) { _headData->_isFaceTrackerConnected = true; @@ -148,7 +148,7 @@ void AvatarData::lazyInitHeadData() { } -float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) { +float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const { auto distance = glm::distance(_globalPosition, viewerPosition); float result = ROTATION_CHANGE_179D; // assume worst if (distance < AVATAR_DISTANCE_LEVEL_1) { @@ -163,7 +163,7 @@ float AvatarData::getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) { return result; } -float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) { +float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) const { return AVATAR_MIN_TRANSLATION; // Eventually make this distance sensitive as well } @@ -574,6 +574,404 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent return avatarDataByteArray.left(avatarDataSize); } +QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, + bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const { + + bool cullSmallChanges = (dataDetail == CullSmallData); + bool sendAll = (dataDetail == SendAllData); + bool sendMinimum = (dataDetail == MinimumData); + + lazyInitHeadData(); + + QByteArray avatarDataByteArray(udt::MAX_PACKET_SIZE, 0); + unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); + unsigned char* startPosition = destinationBuffer; + + // FIXME - + // + // BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens... + // this is an iFrame issue... what to do about that? + // + // BUG -- Resizing avatar seems to "take too long"... the avatar doesn't redraw at smaller size right away + // + // TODO consider these additional optimizations in the future + // 1) SensorToWorld - should we only send this for avatars with attachments?? - 20 bytes - 7.20 kbps + // 2) GUIID for the session change to 2byte index (savings) - 14 bytes - 5.04 kbps + // 3) Improve Joints -- currently we use rotational tolerances, but if we had skeleton/bone length data + // we could do a better job of determining if the change in joints actually translates to visible + // changes at distance. + // + // Potential savings: + // 63 rotations * 6 bytes = 136kbps + // 3 translations * 6 bytes = 6.48kbps + // + + auto parentID = getParentID(); + + bool hasAvatarGlobalPosition = true; // always include global position + bool hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); + bool hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); + bool hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); + bool hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); + bool hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); + bool hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); + bool hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); + + // local position, and parent info only apply to avatars that are parented. The local position + // and the parent info can change independently though, so we track their "changed since" + // separately + bool hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); + bool hasAvatarLocalPosition = hasParent() && (sendAll || + tranlationChangedSince(lastSentTime) || + parentInfoChangedSince(lastSentTime)); + + bool hasFaceTrackerInfo = hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + bool hasJointData = sendAll || !sendMinimum; + + // Leading flags, to indicate how much data is actually included in the packet... + AvatarDataPacket::HasFlags packetStateFlags = + (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) + | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) + | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) + | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) + | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) + | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) + | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) + | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) + | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) + | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) + | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) + | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0); + + memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); + destinationBuffer += sizeof(packetStateFlags); + + if (hasAvatarGlobalPosition) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + data->globalPosition[0] = _globalPosition.x; + data->globalPosition[1] = _globalPosition.y; + data->globalPosition[2] = _globalPosition.z; + destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); + + int numBytes = destinationBuffer - startSection; + + //_globalPositionRateOutbound.increment(numBytes); + } + + if (hasAvatarBoundingBox) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + + data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; + data->avatarDimensions[1] = _globalBoundingBoxDimensions.y; + data->avatarDimensions[2] = _globalBoundingBoxDimensions.z; + + data->boundOriginOffset[0] = _globalBoundingBoxOffset.x; + data->boundOriginOffset[1] = _globalBoundingBoxOffset.y; + data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; + + destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); + + int numBytes = destinationBuffer - startSection; + //_avatarBoundingBoxRateOutbound.increment(numBytes); + } + + if (hasAvatarOrientation) { + auto startSection = destinationBuffer; + auto localOrientation = getLocalOrientation(); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); + + int numBytes = destinationBuffer - startSection; + //_avatarOrientationRateOutbound.increment(numBytes); + } + + if (hasAvatarScale) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + auto scale = getDomainLimitedScale(); + packFloatRatioToTwoByte((uint8_t*)(&data->scale), scale); + destinationBuffer += sizeof(AvatarDataPacket::AvatarScale); + + int numBytes = destinationBuffer - startSection; + //_avatarScaleRateOutbound.increment(numBytes); + } + + if (hasLookAtPosition) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + auto lookAt = _headData->getLookAtPosition(); + data->lookAtPosition[0] = lookAt.x; + data->lookAtPosition[1] = lookAt.y; + data->lookAtPosition[2] = lookAt.z; + destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); + + int numBytes = destinationBuffer - startSection; + //_lookAtPositionRateOutbound.increment(numBytes); + } + + if (hasAudioLoudness) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + data->audioLoudness = packFloatGainToByte(_headData->getAudioLoudness() / AUDIO_LOUDNESS_SCALE); + destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness); + + int numBytes = destinationBuffer - startSection; + //_audioLoudnessRateOutbound.increment(numBytes); + } + + if (hasSensorToWorldMatrix) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); + packOrientationQuatToSixBytes(data->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix)); + glm::vec3 scale = extractScale(sensorToWorldMatrix); + packFloatScalarToSignedTwoByteFixed((uint8_t*)&data->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX); + data->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0]; + data->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1]; + data->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2]; + destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); + + int numBytes = destinationBuffer - startSection; + //_sensorToWorldRateOutbound.increment(numBytes); + } + + if (hasAdditionalFlags) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + + uint8_t flags { 0 }; + + setSemiNibbleAt(flags, KEY_STATE_START_BIT, _keyState); + + // hand state + bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; + setSemiNibbleAt(flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); + if (isFingerPointing) { + setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT); + } + // faceshift state + if (_headData->_isFaceTrackerConnected) { + setAtBit(flags, IS_FACESHIFT_CONNECTED); + } + // eye tracker state + if (_headData->_isEyeTrackerConnected) { + setAtBit(flags, IS_EYE_TRACKER_CONNECTED); + } + // referential state + if (!parentID.isNull()) { + setAtBit(flags, HAS_REFERENTIAL); + } + data->flags = flags; + destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); + + int numBytes = destinationBuffer - startSection; + //_additionalFlagsRateOutbound.increment(numBytes); + } + + if (hasParentInfo) { + auto startSection = destinationBuffer; + auto parentInfo = reinterpret_cast(destinationBuffer); + QByteArray referentialAsBytes = parentID.toRfc4122(); + memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); + parentInfo->parentJointIndex = getParentJointIndex(); + destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); + + int numBytes = destinationBuffer - startSection; + //_parentInfoRateOutbound.increment(numBytes); + } + + if (hasAvatarLocalPosition) { + auto startSection = destinationBuffer; + auto data = reinterpret_cast(destinationBuffer); + auto localPosition = getLocalPosition(); + data->localPosition[0] = localPosition.x; + data->localPosition[1] = localPosition.y; + data->localPosition[2] = localPosition.z; + destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); + + int numBytes = destinationBuffer - startSection; + //_localPositionRateOutbound.increment(numBytes); + } + + // If it is connected, pack up the data + if (hasFaceTrackerInfo) { + auto startSection = destinationBuffer; + auto faceTrackerInfo = reinterpret_cast(destinationBuffer); + + faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; + faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; + faceTrackerInfo->averageLoudness = _headData->_averageLoudness; + faceTrackerInfo->browAudioLift = _headData->_browAudioLift; + faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); + destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); + + // followed by a variable number of float coefficients + memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); + destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); + + int numBytes = destinationBuffer - startSection; + //_faceTrackerRateOutbound.increment(numBytes); + } + + // If it is connected, pack up the data + if (hasJointData) { + auto startSection = destinationBuffer; + QReadLocker readLock(&_jointDataLock); + + // joint rotation data + int numJoints = _jointData.size(); + *destinationBuffer++ = (uint8_t)numJoints; + + unsigned char* validityPosition = destinationBuffer; + unsigned char validity = 0; + int validityBit = 0; + +#ifdef WANT_DEBUG + int rotationSentCount = 0; + unsigned char* beforeRotations = destinationBuffer; +#endif + + if (sentJointDataOut) { + sentJointDataOut->resize(_jointData.size()); // Make sure the destination is resized before using it + } + float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition); + + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + + // The dot product for smaller rotations is a smaller number. + // So if the dot() is less than the value, then the rotation is a larger angle of rotation + bool largeEnoughRotation = fabsf(glm::dot(data.rotation, lastSentJointData[i].rotation)) < minRotationDOT; + + if (sendAll || lastSentJointData[i].rotation != data.rotation) { + if (sendAll || !cullSmallChanges || largeEnoughRotation) { + if (data.rotationSet) { + validity |= (1 << validityBit); +#ifdef WANT_DEBUG + rotationSentCount++; +#endif + if (sentJointDataOut) { + auto jointDataOut = *sentJointDataOut; + jointDataOut[i].rotation = data.rotation; + } + + } + } + } + if (++validityBit == BITS_IN_BYTE) { + *destinationBuffer++ = validity; + validityBit = validity = 0; + } + } + if (validityBit != 0) { + *destinationBuffer++ = validity; + } + + validityBit = 0; + validity = *validityPosition++; + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + if (validity & (1 << validityBit)) { + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); + } + if (++validityBit == BITS_IN_BYTE) { + validityBit = 0; + validity = *validityPosition++; + } + } + + + // joint translation data + validityPosition = destinationBuffer; + validity = 0; + validityBit = 0; + +#ifdef WANT_DEBUG + int translationSentCount = 0; + unsigned char* beforeTranslations = destinationBuffer; +#endif + + float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition); + + float maxTranslationDimension = 0.0; + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + if (sendAll || lastSentJointData[i].translation != data.translation) { + if (sendAll || + !cullSmallChanges || + glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { + if (data.translationSet) { + validity |= (1 << validityBit); +#ifdef WANT_DEBUG + translationSentCount++; +#endif + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + + if (sentJointDataOut) { + auto jointDataOut = *sentJointDataOut; + jointDataOut[i].translation = data.translation; + } + + } + } + } + if (++validityBit == BITS_IN_BYTE) { + *destinationBuffer++ = validity; + validityBit = validity = 0; + } + } + + if (validityBit != 0) { + *destinationBuffer++ = validity; + } + + validityBit = 0; + validity = *validityPosition++; + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData[i]; + if (validity & (1 << validityBit)) { + destinationBuffer += + packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + } + if (++validityBit == BITS_IN_BYTE) { + validityBit = 0; + validity = *validityPosition++; + } + } + + // faux joints + Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); + destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), + TRANSLATION_COMPRESSION_RADIX); + +#ifdef WANT_DEBUG + if (sendAll) { + qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll + << "rotations:" << rotationSentCount << "translations:" << translationSentCount + << "largest:" << maxTranslationDimension + << "size:" + << (beforeRotations - startPosition) << "+" + << (beforeTranslations - beforeRotations) << "+" + << (destinationBuffer - beforeTranslations) << "=" + << (destinationBuffer - startPosition); + } +#endif + + int numBytes = destinationBuffer - startSection; + //_jointDataRateOutbound.increment(numBytes); + } + + int avatarDataSize = destinationBuffer - startPosition; + return avatarDataByteArray.left(avatarDataSize); +} // NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation void AvatarData::doneEncoding(bool cullSmallChanges) { // The server has finished sending this version of the joint-data to other nodes. Update _lastSentJointData. diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 964bc4a6df..45e62d4045 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -354,6 +354,10 @@ public: virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector* sentJointDataOut = nullptr); + // FIXME + virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, + bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const; + virtual void doneEncoding(bool cullSmallChanges); /// \return true if an error should be logged @@ -380,7 +384,7 @@ public: void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. virtual void updateAttitude() {} // Tell skeleton mesh about changes - glm::quat getHeadOrientation() { + glm::quat getHeadOrientation() const { lazyInitHeadData(); return _headData->getOrientation(); } @@ -575,10 +579,10 @@ public slots: void resetLastSent() { _lastToByteArray = 0; } protected: - void lazyInitHeadData(); + void lazyInitHeadData() const; - float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition); - float getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition); + float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const; + float getDistanceBasedMinTranslationDistance(glm::vec3 viewerPosition) const; bool avatarBoundingBoxChangedSince(quint64 time) const { return _avatarBoundingBoxChanged >= time; } bool avatarScaleChangedSince(quint64 time) const { return _avatarScaleChanged >= time; } @@ -614,7 +618,7 @@ protected: bool _forceFaceTrackerConnected; bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar - HeadData* _headData { nullptr }; + mutable HeadData* _headData { nullptr }; QUrl _skeletonModelURL; bool _firstSkeletonCheck { true }; From 2d300a6643b36687801c5a885c069fb9f361c162 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Wed, 15 Feb 2017 22:14:26 -0800 Subject: [PATCH 13/35] checkpoint with eachNode() changed to nestedEach() --- assignment-client/src/avatars/AvatarMixer.cpp | 277 +++++++++--------- assignment-client/src/avatars/AvatarMixer.h | 4 - 2 files changed, 137 insertions(+), 144 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index de5a9b3f2c..7aa0a61de7 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -37,8 +37,7 @@ const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float) AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000; AvatarMixer::AvatarMixer(ReceivedMessage& message) : - ThreadedAssignment(message), - _broadcastThread() + ThreadedAssignment(message) { // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); @@ -66,12 +65,6 @@ void AvatarMixer::queueIncomingPacket(QSharedPointer message, S AvatarMixer::~AvatarMixer() { - if (_broadcastTimer) { - _broadcastTimer->deleteLater(); - } - - _broadcastThread.quit(); - _broadcastThread.wait(); } // An 80% chance of sending a identity packet within a 5 second interval. @@ -135,6 +128,10 @@ void AvatarMixer::start() { // this is where we need to put the real work... { + // for now, call the single threaded version + broadcastAvatarData(); + + /* auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { @@ -197,6 +194,16 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { } } +void avatarLoops(); + +// only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame +// Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times +// per second. +// This value should be a power of two for performance purposes, as the mixer performs a modulo operation every frame +// to determine whether the extra data should be sent. +static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; + + // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. @@ -224,13 +231,6 @@ void AvatarMixer::broadcastAvatarData() { const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; - // only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame - // Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times - // per second. - // This value should be a power of two for performance purposes, as the mixer performs a modulo operation every frame - // to determine whether the extra data should be sent. - const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; - // NOTE: The following code calculates the _performanceThrottlingRatio based on how much the avatar-mixer was // able to sleep. This will eventually be used to ask for an additional avatar-mixer to help out. Currently the value // is unused as it is assumed this should not be hit before the avatar-mixer hits the desired bandwidth limit per client. @@ -272,6 +272,35 @@ void AvatarMixer::broadcastAvatarData() { ++framesSinceCutoffEvent; } + avatarLoops(); + + _lastFrameTimestamp = p_high_resolution_clock::now(); + +#ifdef WANT_DEBUG + auto sinceLastDebug = p_high_resolution_clock::now() - _lastDebugMessage; + auto sinceLastDebugUsecs = std::chrono::duration_cast(sinceLastDebug).count(); + quint64 DEBUG_INTERVAL = USECS_PER_SECOND * 5; + + if (sinceLastDebugUsecs > DEBUG_INTERVAL) { + qDebug() << "broadcast rate:" << _broadcastRate.rate() << "hz"; + _lastDebugMessage = p_high_resolution_clock::now(); + } +#endif + + quint64 endBroadcastAvatarData = usecTimestampNow(); + _broadcastAvatarDataElapsedTime += (endBroadcastAvatarData - startBroadcastAvatarData); +} + +void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend); + +void avatarLoops() { + auto nodeList = DependencyManager::get(); + nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + avatarLoopsInner(cbegin, cend); + }); +} + +void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend) { auto nodeList = DependencyManager::get(); // setup for distributed random floating point values @@ -279,26 +308,19 @@ void AvatarMixer::broadcastAvatarData() { std::mt19937 generator(randomDevice()); std::uniform_real_distribution distribution; - nodeList->eachMatchingNode( - [&](const SharedNodePointer& node)->bool { - if (!node->getLinkedData()) { - return false; - } - if (node->getType() != NodeType::Agent) { - return false; - } - if (!node->getActiveSocket()) { - return false; - } - return true; - }, - [&](const SharedNodePointer& node) { + std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { + if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) { AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); MutexTryLocker lock(nodeData->getMutex()); + + // FIXME???? if (!lock.isLocked()) { return; } - ++_sumListeners; + + // FIXME -- mixer data + // ++_sumListeners; + nodeData->resetInViewStats(); const AvatarData& avatar = nodeData->getAvatar(); @@ -349,6 +371,8 @@ void AvatarMixer::broadcastAvatarData() { // get the current full rate distance so we can work with it float currentFullRateDistance = nodeData->getFullRateDistance(); + // FIXME -- mixer data + float _maxKbpsPerNode = 5000.0f; if (avatarDataRateLastSecond > _maxKbpsPerNode) { // is the FRD greater than the farthest avatar? @@ -379,82 +403,81 @@ void AvatarMixer::broadcastAvatarData() { // this is an AGENT we have received head data from // send back a packet with other active node data to this node - nodeList->eachMatchingNode( - [&](const SharedNodePointer& otherNode)->bool { + std::for_each(cbegin, cend, [&](const SharedNodePointer& otherNode) { + bool shouldConsider = false; + quint64 startIgnoreCalculation = usecTimestampNow(); - bool shouldConsider = false; - quint64 startIgnoreCalculation = usecTimestampNow(); + // 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 + // or that has ignored the viewing node + if (!otherNode->getLinkedData() + || otherNode->getUUID() == node->getUUID() + || (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe) + || (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { - // 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 - // or that has ignored the viewing node - if (!otherNode->getLinkedData() - || otherNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe) - || (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + shouldConsider = false; - shouldConsider = false; + } else { + AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); + AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + // Check to see if the space bubble is enabled + if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { + // Define the minimum bubble size + static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); + // Define the scale of the box for the current node + glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f; + // Define the scale of the box for the current other node + glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f; - } else { - AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); - // Check to see if the space bubble is enabled - if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { - // Define the minimum bubble size - static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); - // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f; - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f; - - // Set up the bounding box for the current node - AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { - nodeBox.setScaleStayCentered(minBubbleSize); - } - // Set up the bounding box for the current other node - AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of both bounding boxes - nodeBox.embiggen(4.0f); - otherNodeBox.embiggen(4.0f); - - // Perform the collision check between the two bounding boxes - if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, otherNode); - shouldConsider = getsAnyIgnored; - } + // Set up the bounding box for the current node + AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); + // Clamp the size of the bounding box to a minimum scale + if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { + nodeBox.setScaleStayCentered(minBubbleSize); } - // Not close enough to ignore - if (shouldConsider) { - nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); + // Set up the bounding box for the current other node + AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + // Clamp the size of the bounding box to a minimum scale + if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { + otherNodeBox.setScaleStayCentered(minBubbleSize); } + // Quadruple the scale of both bounding boxes + nodeBox.embiggen(4.0f); + otherNodeBox.embiggen(4.0f); - shouldConsider = true; + // Perform the collision check between the two bounding boxes + if (nodeBox.touches(otherNodeBox)) { + nodeData->ignoreOther(node, otherNode); + shouldConsider = getsAnyIgnored; + } + } + // Not close enough to ignore + if (shouldConsider) { + nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); } + shouldConsider = true; + quint64 endIgnoreCalculation = usecTimestampNow(); - _ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); - - return shouldConsider; - }, - [&](const SharedNodePointer& otherNode) { + // FIXME -- mixer data + //_ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); + } + if (shouldConsider) { quint64 startAvatarDataPacking = usecTimestampNow(); ++numOtherAvatars; AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); MutexTryLocker lock(otherNodeData->getMutex()); + + // FIXME -- might want to track this lock failed... if (!lock.isLocked()) { - // FIXME -- might want to track this lock failed... quint64 endAvatarDataPacking = usecTimestampNow(); - _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } @@ -463,9 +486,17 @@ void AvatarMixer::broadcastAvatarData() { if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0 && (forceSend - || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp + //|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp // FIXME - mixer data || distribution(generator) < IDENTITY_SEND_PROBABILITY)) { - sendIdentityPacket(otherNodeData, node); + + // FIXME --- used to be.../ mixer data dependency + //sendIdentityPacket(otherNodeData, node); + + QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); + auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); + individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNodeData->getNodeID().toRfc4122()); + identityPacket->write(individualData); + DependencyManager::get()->sendPacket(std::move(identityPacket), *node); } const AvatarData& otherAvatar = otherNodeData->getAvatar(); @@ -484,7 +515,8 @@ void AvatarMixer::broadcastAvatarData() { && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { quint64 endAvatarDataPacking = usecTimestampNow(); - _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } @@ -502,7 +534,8 @@ void AvatarMixer::broadcastAvatarData() { ++numAvatarsHeldBack; quint64 endAvatarDataPacking = usecTimestampNow(); - _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } 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 @@ -526,7 +559,8 @@ void AvatarMixer::broadcastAvatarData() { // this throttles the extra data to only be sent every Nth message if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { quint64 endAvatarDataPacking = usecTimestampNow(); - _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); return; } @@ -554,7 +588,9 @@ void AvatarMixer::broadcastAvatarData() { avatarPacketList->endSegment(); quint64 endAvatarDataPacking = usecTimestampNow(); - _avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + } }); quint64 startPacketSending = usecTimestampNow(); @@ -580,30 +616,19 @@ void AvatarMixer::broadcastAvatarData() { } quint64 endPacketSending = usecTimestampNow(); - _packetSendingElapsedTime += (endPacketSending - startPacketSending); + // FIXME - mixer data + //_packetSendingElapsedTime += (endPacketSending - startPacketSending); } - ); + }); // We're done encoding this version of the otherAvatars. Update their "lastSent" joint-states so // that we can notice differences, next time around. // // FIXME - this seems suspicious, the code seems to consider all avatars, but not all avatars will // have had their joints sent, so actually we should consider the time since they actually were sent???? - nodeList->eachMatchingNode( - [&](const SharedNodePointer& otherNode)->bool { - if (!otherNode->getLinkedData()) { - return false; - } - if (otherNode->getType() != NodeType::Agent) { - return false; - } - if (!otherNode->getActiveSocket()) { - return false; - } - return true; - }, - [&](const SharedNodePointer& otherNode) { + std::for_each(cbegin, cend, [&](const SharedNodePointer& otherNode) { + if (otherNode->getLinkedData() && (otherNode->getType() == NodeType::Agent) && otherNode->getActiveSocket()) { AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); MutexTryLocker lock(otherNodeData->getMutex()); if (!lock.isLocked()) { @@ -611,24 +636,8 @@ void AvatarMixer::broadcastAvatarData() { } AvatarData& otherAvatar = otherNodeData->getAvatar(); otherAvatar.doneEncoding(false); - }); - - _lastFrameTimestamp = p_high_resolution_clock::now(); - -#ifdef WANT_DEBUG - auto sinceLastDebug = p_high_resolution_clock::now() - _lastDebugMessage; - auto sinceLastDebugUsecs = std::chrono::duration_cast(sinceLastDebug).count(); - quint64 DEBUG_INTERVAL = USECS_PER_SECOND * 5; - - if (sinceLastDebugUsecs > DEBUG_INTERVAL) { - qDebug() << "broadcast rate:" << _broadcastRate.rate() << "hz"; - _lastDebugMessage = p_high_resolution_clock::now(); - } -#endif - - quint64 endBroadcastAvatarData = usecTimestampNow(); - _broadcastAvatarDataElapsedTime += (endBroadcastAvatarData - startBroadcastAvatarData); - + } + }); } void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { @@ -894,18 +903,6 @@ void AvatarMixer::run() { ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); - // setup the timer that will be fired on the broadcast thread - _broadcastTimer = new QTimer; - _broadcastTimer->setTimerType(Qt::PreciseTimer); - _broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); - _broadcastTimer->moveToThread(&_broadcastThread); - - // connect appropriate signals and slots - connect(_broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection); - connect(&_broadcastThread, SIGNAL(started()), _broadcastTimer, SLOT(start())); - - // start our tight loop... - start(); } AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node) { @@ -929,8 +926,8 @@ void AvatarMixer::domainSettingsRequestComplete() { // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); - // start the broadcastThread - _broadcastThread.start(); + // start our tight loop... + start(); } void AvatarMixer::handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID) { diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index e0b03804a9..628c44abaf 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -62,8 +62,6 @@ private: void manageDisplayName(const SharedNodePointer& node); - QThread _broadcastThread; - p_high_resolution_clock::time_point _lastFrameTimestamp; float _trailingSleepRatio { 1.0f }; @@ -79,8 +77,6 @@ private: float _domainMinimumScale { MIN_AVATAR_SCALE }; float _domainMaximumScale { MAX_AVATAR_SCALE }; - QTimer* _broadcastTimer = nullptr; - RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; QHash> _sessionDisplayNames; From 23790b93e3e1f36fc5ce85ad8cf587bc83239989 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 01:07:21 -0800 Subject: [PATCH 14/35] first cut at slaves doing broadcast --- assignment-client/src/avatars/AvatarMixer.cpp | 19 +- .../src/avatars/AvatarMixerClientData.h | 6 +- .../src/avatars/AvatarMixerSlave.cpp | 349 ++++++++++++++++++ .../src/avatars/AvatarMixerSlavePool.cpp | 4 +- 4 files changed, 362 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 7aa0a61de7..626c001218 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -129,17 +129,14 @@ void AvatarMixer::start() { // this is where we need to put the real work... { // for now, call the single threaded version - broadcastAvatarData(); + //broadcastAvatarData(); - - /* auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { - _slavePool.processIncomingPackets(cbegin, cend); + _slavePool.anotherJob(cbegin, cend); }); auto end = usecTimestampNow(); - _processQueuedAvatarDataPacketsElapsedTime += (end - start); - */ + _broadcastAvatarDataElapsedTime += (end - start); } @@ -194,7 +191,7 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { } } -void avatarLoops(); +static void avatarLoops(); // only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame // Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times @@ -291,16 +288,16 @@ void AvatarMixer::broadcastAvatarData() { _broadcastAvatarDataElapsedTime += (endBroadcastAvatarData - startBroadcastAvatarData); } -void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend); +static void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend); -void avatarLoops() { +static void avatarLoops() { auto nodeList = DependencyManager::get(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { avatarLoopsInner(cbegin, cend); }); } -void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend) { +static void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend) { auto nodeList = DependencyManager::get(); // setup for distributed random floating point values @@ -789,7 +786,6 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_b_ignoreCalculation"] = (float)_ignoreCalculationElapsedTime / (float)_numStatFrames; statsObject["timing_average_c_avatarDataPacking"] = (float)_avatarDataPackingElapsedTime / (float)_numStatFrames; statsObject["timing_average_d_packetSending"] = (float)_packetSendingElapsedTime / (float)_numStatFrames; - statsObject["timing_average_e_total_broadcastAvatarData"] = (float)_broadcastAvatarDataElapsedTime / (float)_numStatFrames; // this things all occur on the frequency of the tight loop int tightLoopFrames = _numTightLoopFrames; @@ -799,6 +795,7 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_y_processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); statsObject["timing_average_y_queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); + statsObject["timing_average_z_broadcastAvatarData"] = TIGHT_LOOP_STAT(_broadcastAvatarDataElapsedTime); statsObject["timing_average_z_displayNameManagement"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); statsObject["timing_average_z_handleAvatarDataPacket"] = TIGHT_LOOP_STAT(_handleAvatarDataPacketElapsedTime); statsObject["timing_average_z_handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index c69599cc26..8cd72050f7 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -86,9 +86,9 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; - glm::vec3 getPosition() { return _avatar ? _avatar->getPosition() : glm::vec3(0); } - glm::vec3 getGlobalBoundingBoxCorner() { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } - bool isRadiusIgnoring(const QUuid& other) { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } + glm::vec3 getPosition() const { return _avatar ? _avatar->getPosition() : glm::vec3(0); } + glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); } + bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); } void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); } void removeFromRadiusIgnoringSet(SharedNodePointer self, const QUuid& other); void ignoreOther(SharedNodePointer self, SharedNodePointer other); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 784b10ebfe..37fd258bfa 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -55,5 +55,354 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { _processIncomingPacketsElapsedTime += (end - start); } +#include +#include + +static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; +static const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float)AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000; + +// only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame +// Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times +// per second. +// This value should be a power of two for performance purposes, as the mixer performs a modulo operation every frame +// to determine whether the extra data should be sent. +static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; + +// An 80% chance of sending a identity packet within a 5 second interval. +// assuming 60 htz update rate. +const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // FIXME... this is wrong for 45hz + void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { + //qDebug() << __FUNCTION__ << "node:" << node; + + auto nodeList = DependencyManager::get(); + + // setup for distributed random floating point values + std::random_device randomDevice; + std::mt19937 generator(randomDevice()); + std::uniform_real_distribution distribution; + + if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) { + AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + MutexTryLocker lock(nodeData->getMutex()); + + // FIXME???? + if (!lock.isLocked()) { + //qDebug() << __FUNCTION__ << "unable to lock... node:" << node << " would BAIL???... line:" << __LINE__; + //return; + } + + // FIXME -- mixer data + // ++_sumListeners; + + nodeData->resetInViewStats(); + + const AvatarData& avatar = nodeData->getAvatar(); + glm::vec3 myPosition = avatar.getClientGlobalPosition(); + + // reset the internal state for correct random number distribution + distribution.reset(); + + // reset the max distance for this frame + float maxAvatarDistanceThisFrame = 0.0f; + + // reset the number of sent avatars + nodeData->resetNumAvatarsSentLastFrame(); + + // keep a counter of the number of considered avatars + int numOtherAvatars = 0; + + // keep track of outbound data rate specifically for avatar data + int numAvatarDataBytes = 0; + + // keep track of the number of other avatars held back in this frame + int numAvatarsHeldBack = 0; + + // keep track of the number of other avatar frames skipped + int numAvatarsWithSkippedFrames = 0; + + // use the data rate specifically for avatar data for FRD adjustment checks + float avatarDataRateLastSecond = nodeData->getOutboundAvatarDataKbps(); + + // When this is true, the AvatarMixer will send Avatar data to a client about avatars that are not in the view frustrum + bool getsOutOfView = nodeData->getRequestsDomainListData(); + + // When this is true, the AvatarMixer will send Avatar data to a client about avatars that they've ignored + bool getsIgnoredByMe = getsOutOfView; + + // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them + bool getsAnyIgnored = getsIgnoredByMe && node->getCanKick(); + + // Check if it is time to adjust what we send this client based on the observed + // bandwidth to this node. We do this once a second, which is also the window for + // the bandwidth reported by node->getOutboundBandwidth(); + if (nodeData->getNumFramesSinceFRDAdjustment() > AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) { + + const float FRD_ADJUSTMENT_ACCEPTABLE_RATIO = 0.8f; + const float HYSTERISIS_GAP = (1 - FRD_ADJUSTMENT_ACCEPTABLE_RATIO); + const float HYSTERISIS_MIDDLE_PERCENTAGE = (1 - (HYSTERISIS_GAP * 0.5f)); + + // get the current full rate distance so we can work with it + float currentFullRateDistance = nodeData->getFullRateDistance(); + + // FIXME -- mixer data + float _maxKbpsPerNode = 5000.0f; + if (avatarDataRateLastSecond > _maxKbpsPerNode) { + + // is the FRD greater than the farthest avatar? + // if so, before we calculate anything, set it to that distance + currentFullRateDistance = std::min(currentFullRateDistance, nodeData->getMaxAvatarDistance()); + + // we're adjusting the full rate distance to target a bandwidth in the middle + // of the hysterisis gap + currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond; + + nodeData->setFullRateDistance(currentFullRateDistance); + nodeData->resetNumFramesSinceFRDAdjustment(); + } else if (currentFullRateDistance < nodeData->getMaxAvatarDistance() + && avatarDataRateLastSecond < _maxKbpsPerNode * FRD_ADJUSTMENT_ACCEPTABLE_RATIO) { + // we are constrained AND we've recovered to below the acceptable ratio + // lets adjust the full rate distance to target a bandwidth in the middle of the hyterisis gap + currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond; + + nodeData->setFullRateDistance(currentFullRateDistance); + nodeData->resetNumFramesSinceFRDAdjustment(); + } + } else { + nodeData->incrementNumFramesSinceFRDAdjustment(); + } + + // setup a PacketList for the avatarPackets + auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); + + // this is an AGENT we have received head data from + // send back a packet with other active node data to this node + std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode; + + bool shouldConsider = false; + quint64 startIgnoreCalculation = usecTimestampNow(); + + // 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 + // or that has ignored the viewing node + if (!otherNode->getLinkedData() + || otherNode->getUUID() == node->getUUID() + || (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe) + || (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + + shouldConsider = false; + + } else { + const AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); + //AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + // Check to see if the space bubble is enabled + if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { + // Define the minimum bubble size + static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); + // Define the scale of the box for the current node + glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f; + // Define the scale of the box for the current other node + glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f; + + // Set up the bounding box for the current node + AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); + // Clamp the size of the bounding box to a minimum scale + if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { + nodeBox.setScaleStayCentered(minBubbleSize); + } + // Set up the bounding box for the current other node + AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + // Clamp the size of the bounding box to a minimum scale + if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { + otherNodeBox.setScaleStayCentered(minBubbleSize); + } + // Quadruple the scale of both bounding boxes + nodeBox.embiggen(4.0f); + otherNodeBox.embiggen(4.0f); + + // Perform the collision check between the two bounding boxes + if (nodeBox.touches(otherNodeBox)) { + nodeData->ignoreOther(node, otherNode); + shouldConsider = getsAnyIgnored; + } + } + // Not close enough to ignore + if (shouldConsider) { + nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); + } + + shouldConsider = true; + + quint64 endIgnoreCalculation = usecTimestampNow(); + // FIXME -- mixer data + //_ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); + } + + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << "shouldConsider:" << shouldConsider; + + + if (shouldConsider) { + quint64 startAvatarDataPacking = usecTimestampNow(); + + ++numOtherAvatars; + + AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + MutexTryLocker lock(otherNodeData->getMutex()); + + // FIXME -- might want to track this lock failed... + if (!lock.isLocked()) { + + quint64 endAvatarDataPacking = usecTimestampNow(); + + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " failed to lock... would BAIL??... line:" << __LINE__; + //return; + } + + // make sure we send out identity packets to and from new arrivals. + bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); + + if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0 + && (forceSend + //|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp // FIXME - mixer data + || distribution(generator) < IDENTITY_SEND_PROBABILITY)) { + + // FIXME --- used to be.../ mixer data dependency + //sendIdentityPacket(otherNodeData, node); + + QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); + auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); + individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNodeData->getNodeID().toRfc4122()); + identityPacket->write(individualData); + DependencyManager::get()->sendPacket(std::move(identityPacket), *node); + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " sending itentity packet for otherNode to node..."; + } + + const AvatarData& otherAvatar = otherNodeData->getAvatar(); + // Decide whether to send this avatar's data based on it's distance from us + + // The full rate distance is the distance at which EVERY update will be sent for this avatar + // at twice the full rate distance, there will be a 50% chance of sending this avatar's update + glm::vec3 otherPosition = otherAvatar.getClientGlobalPosition(); + float distanceToAvatar = glm::length(myPosition - otherPosition); + + // potentially update the max full rate distance for this frame + maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); + + if (distanceToAvatar != 0.0f + && !getsOutOfView + && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { + + quint64 endAvatarDataPacking = usecTimestampNow(); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " distance/getsOutOfView... BAILING... line:" << __LINE__; + return; + } + + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); + AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); + + if (lastSeqToReceiver > lastSeqFromSender && lastSeqToReceiver != UINT16_MAX) { + // we got out out of order packets from the sender, track it + otherNodeData->incrementNumOutOfOrderSends(); + } + + // make sure we haven't already sent this data from this sender to this receiver + // or that somehow we haven't sent + if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { + ++numAvatarsHeldBack; + + quint64 endAvatarDataPacking = usecTimestampNow(); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " lastSeqToReceiver... BAILING... line:" << __LINE__; + return; + } 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 + ++numAvatarsWithSkippedFrames; + } + + // we're going to send this avatar + + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + + // determine if avatar is in view, to determine how much data to include... + glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; + AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + bool isInView = nodeData->otherAvatarInView(otherNodeBox); + + // this throttles the extra data to only be sent every Nth message + if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { + quint64 endAvatarDataPacking = usecTimestampNow(); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)... BAILING... line:" << __LINE__; + return; + } + + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); + + AvatarData::AvatarDataDetail detail; + if (!isInView && !getsOutOfView) { + detail = AvatarData::MinimumData; + nodeData->incrementAvatarOutOfView(); + } else { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO + ? AvatarData::SendAllData : AvatarData::CullSmallData; + nodeData->incrementAvatarInView(); + } + + numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); + auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + bool distanceAdjust = true; + glm::vec3 viewerPosition = myPosition; + QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); + numAvatarDataBytes += avatarPacketList->write(bytes); + + avatarPacketList->endSegment(); + + quint64 endAvatarDataPacking = usecTimestampNow(); + // FIXME - mixer data + //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + } + }); + + quint64 startPacketSending = usecTimestampNow(); + + // close the current packet so that we're always sending something + avatarPacketList->closeCurrentPacket(true); + + // send the avatar data PacketList + //qDebug() << "about to call nodeList->sendPacketList() for node:" << node; + nodeList->sendPacketList(std::move(avatarPacketList), *node); + + // record the bytes sent for other avatar data in the AvatarMixerClientData + nodeData->recordSentAvatarData(numAvatarDataBytes); + + // record the number of avatars held back this frame + nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); + nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); + + if (numOtherAvatars == 0) { + // update the full rate distance to FLOAT_MAX since we didn't have any other avatars to send + nodeData->setMaxAvatarDistance(FLT_MAX); + } else { + nodeData->setMaxAvatarDistance(maxAvatarDistanceThisFrame); + } + + quint64 endPacketSending = usecTimestampNow(); + // FIXME - mixer data + //_packetSendingElapsedTime += (endPacketSending - startPacketSending); + } } + diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 1b335f8383..28a3dce0e3 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -69,13 +69,13 @@ static AvatarMixerSlave slave; void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { _function = &AvatarMixerSlave::processIncomingPackets; - _configure = [](AvatarMixerSlave& slave) {}; + _configure = [&](AvatarMixerSlave& slave) { slave.configure(begin, end); }; run(begin, end); } void AvatarMixerSlavePool::anotherJob(ConstIter begin, ConstIter end) { _function = &AvatarMixerSlave::anotherJob; - _configure = [](AvatarMixerSlave& slave) {}; + _configure = [&](AvatarMixerSlave& slave) { slave.configure(begin, end); }; run(begin, end); } From 755c690030c2bb0949da51716ff52e72829a078e Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 10:07:49 -0800 Subject: [PATCH 15/35] some cleanup, better sleep management --- assignment-client/src/avatars/AvatarMixer.cpp | 432 +++--------------- assignment-client/src/avatars/AvatarMixer.h | 2 + .../src/avatars/AvatarMixerSlave.cpp | 177 ++++--- .../src/avatars/AvatarMixerSlave.h | 37 +- 4 files changed, 182 insertions(+), 466 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 626c001218..73b0514161 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -88,17 +88,51 @@ void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const Shar #include #include +std::chrono::microseconds AvatarMixer::timeFrame(p_high_resolution_clock::time_point& timestamp) { + // advance the next frame + auto nextTimestamp = timestamp + std::chrono::microseconds((int)((float)USECS_PER_SECOND / (float)AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND)); + auto now = p_high_resolution_clock::now(); + + // compute how long the last frame took + auto duration = std::chrono::duration_cast(now - timestamp); + + // set the new frame timestamp + timestamp = std::max(now, nextTimestamp); + + // sleep until the next frame should start + // WIN32 sleep_until is broken until VS2015 Update 2 + // instead, std::max (above) guarantees that timestamp >= now, so we can sleep_for + std::this_thread::sleep_for(timestamp - now); + + return duration; +} + + void AvatarMixer::start() { auto nodeList = DependencyManager::get(); + auto frameTimestamp = p_high_resolution_clock::now(); + while (!_isFinished) { _numTightLoopFrames++; _loopRate.increment(); - // FIXME - we really should sleep for the remainder of what we haven't spent time processing - auto sleepAmount = std::chrono::milliseconds(AVATAR_DATA_SEND_INTERVAL_MSECS); - std::this_thread::sleep_for(sleepAmount); + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // DO THIS FIRST!!!!!!!! + // + // DONE --- 1) only sleep for remainder + // 2) clean up stats, add slave stats + // 3) delete dead code from mixer (now that it's in slave) + // 4) audit the locking and side-effects to node, otherNode, and nodeData + // 5) throttling?? + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // calculates last frame duration and sleeps for the remainder of the target amount + auto frameDuration = timeFrame(frameTimestamp); // Allow nodes to process any pending/queued packets across our worker threads { @@ -201,9 +235,9 @@ static void avatarLoops(); static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; -// NOTE: some additional optimizations to consider. -// 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present -// if the avatar is not in view or in the keyhole. +// FIXME -- this is dead code... it needs to be removed... +// this "throttle" logic is the old approach. need to consider some +// reasonable throttle approach in new multi-core design void AvatarMixer::broadcastAvatarData() { quint64 startBroadcastAvatarData = usecTimestampNow(); _broadcastRate.increment(); @@ -269,7 +303,7 @@ void AvatarMixer::broadcastAvatarData() { ++framesSinceCutoffEvent; } - avatarLoops(); + //avatarLoops(); _lastFrameTimestamp = p_high_resolution_clock::now(); @@ -288,355 +322,6 @@ void AvatarMixer::broadcastAvatarData() { _broadcastAvatarDataElapsedTime += (endBroadcastAvatarData - startBroadcastAvatarData); } -static void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend); - -static void avatarLoops() { - auto nodeList = DependencyManager::get(); - nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { - avatarLoopsInner(cbegin, cend); - }); -} - -static void avatarLoopsInner(NodeList::const_iterator cbegin, NodeList::const_iterator cend) { - auto nodeList = DependencyManager::get(); - - // setup for distributed random floating point values - std::random_device randomDevice; - std::mt19937 generator(randomDevice()); - std::uniform_real_distribution distribution; - - std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { - if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) { - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); - MutexTryLocker lock(nodeData->getMutex()); - - // FIXME???? - if (!lock.isLocked()) { - return; - } - - // FIXME -- mixer data - // ++_sumListeners; - - nodeData->resetInViewStats(); - - const AvatarData& avatar = nodeData->getAvatar(); - glm::vec3 myPosition = avatar.getClientGlobalPosition(); - - // reset the internal state for correct random number distribution - distribution.reset(); - - // reset the max distance for this frame - float maxAvatarDistanceThisFrame = 0.0f; - - // reset the number of sent avatars - nodeData->resetNumAvatarsSentLastFrame(); - - // keep a counter of the number of considered avatars - int numOtherAvatars = 0; - - // keep track of outbound data rate specifically for avatar data - int numAvatarDataBytes = 0; - - // keep track of the number of other avatars held back in this frame - int numAvatarsHeldBack = 0; - - // keep track of the number of other avatar frames skipped - int numAvatarsWithSkippedFrames = 0; - - // use the data rate specifically for avatar data for FRD adjustment checks - float avatarDataRateLastSecond = nodeData->getOutboundAvatarDataKbps(); - - // When this is true, the AvatarMixer will send Avatar data to a client about avatars that are not in the view frustrum - bool getsOutOfView = nodeData->getRequestsDomainListData(); - - // When this is true, the AvatarMixer will send Avatar data to a client about avatars that they've ignored - bool getsIgnoredByMe = getsOutOfView; - - // When this is true, the AvatarMixer will send Avatar data to a client about avatars that have ignored them - bool getsAnyIgnored = getsIgnoredByMe && node->getCanKick(); - - // Check if it is time to adjust what we send this client based on the observed - // bandwidth to this node. We do this once a second, which is also the window for - // the bandwidth reported by node->getOutboundBandwidth(); - if (nodeData->getNumFramesSinceFRDAdjustment() > AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) { - - const float FRD_ADJUSTMENT_ACCEPTABLE_RATIO = 0.8f; - const float HYSTERISIS_GAP = (1 - FRD_ADJUSTMENT_ACCEPTABLE_RATIO); - const float HYSTERISIS_MIDDLE_PERCENTAGE = (1 - (HYSTERISIS_GAP * 0.5f)); - - // get the current full rate distance so we can work with it - float currentFullRateDistance = nodeData->getFullRateDistance(); - - // FIXME -- mixer data - float _maxKbpsPerNode = 5000.0f; - if (avatarDataRateLastSecond > _maxKbpsPerNode) { - - // is the FRD greater than the farthest avatar? - // if so, before we calculate anything, set it to that distance - currentFullRateDistance = std::min(currentFullRateDistance, nodeData->getMaxAvatarDistance()); - - // we're adjusting the full rate distance to target a bandwidth in the middle - // of the hysterisis gap - currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond; - - nodeData->setFullRateDistance(currentFullRateDistance); - nodeData->resetNumFramesSinceFRDAdjustment(); - } else if (currentFullRateDistance < nodeData->getMaxAvatarDistance() - && avatarDataRateLastSecond < _maxKbpsPerNode * FRD_ADJUSTMENT_ACCEPTABLE_RATIO) { - // we are constrained AND we've recovered to below the acceptable ratio - // lets adjust the full rate distance to target a bandwidth in the middle of the hyterisis gap - currentFullRateDistance *= (_maxKbpsPerNode * HYSTERISIS_MIDDLE_PERCENTAGE) / avatarDataRateLastSecond; - - nodeData->setFullRateDistance(currentFullRateDistance); - nodeData->resetNumFramesSinceFRDAdjustment(); - } - } else { - nodeData->incrementNumFramesSinceFRDAdjustment(); - } - - // setup a PacketList for the avatarPackets - auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - - // this is an AGENT we have received head data from - // send back a packet with other active node data to this node - std::for_each(cbegin, cend, [&](const SharedNodePointer& otherNode) { - bool shouldConsider = false; - quint64 startIgnoreCalculation = usecTimestampNow(); - - // 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 - // or that has ignored the viewing node - if (!otherNode->getLinkedData() - || otherNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(otherNode->getUUID()) && !getsIgnoredByMe) - || (otherNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { - - shouldConsider = false; - - } else { - AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); - // Check to see if the space bubble is enabled - if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { - // Define the minimum bubble size - static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); - // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f; - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (otherData->getPosition() - otherData->getGlobalBoundingBoxCorner()) * 2.0f; - - // Set up the bounding box for the current node - AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(nodeBoxScale, minBubbleSize))) { - nodeBox.setScaleStayCentered(minBubbleSize); - } - // Set up the bounding box for the current other node - AABox otherNodeBox(otherData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of both bounding boxes - nodeBox.embiggen(4.0f); - otherNodeBox.embiggen(4.0f); - - // Perform the collision check between the two bounding boxes - if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, otherNode); - shouldConsider = getsAnyIgnored; - } - } - // Not close enough to ignore - if (shouldConsider) { - nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); - } - - shouldConsider = true; - - quint64 endIgnoreCalculation = usecTimestampNow(); - // FIXME -- mixer data - //_ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); - } - - if (shouldConsider) { - quint64 startAvatarDataPacking = usecTimestampNow(); - - ++numOtherAvatars; - - AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - MutexTryLocker lock(otherNodeData->getMutex()); - - // FIXME -- might want to track this lock failed... - if (!lock.isLocked()) { - - quint64 endAvatarDataPacking = usecTimestampNow(); - - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - return; - } - - // make sure we send out identity packets to and from new arrivals. - bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); - - if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0 - && (forceSend - //|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp // FIXME - mixer data - || distribution(generator) < IDENTITY_SEND_PROBABILITY)) { - - // FIXME --- used to be.../ mixer data dependency - //sendIdentityPacket(otherNodeData, node); - - QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); - auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); - individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNodeData->getNodeID().toRfc4122()); - identityPacket->write(individualData); - DependencyManager::get()->sendPacket(std::move(identityPacket), *node); - } - - const AvatarData& otherAvatar = otherNodeData->getAvatar(); - // Decide whether to send this avatar's data based on it's distance from us - - // The full rate distance is the distance at which EVERY update will be sent for this avatar - // at twice the full rate distance, there will be a 50% chance of sending this avatar's update - glm::vec3 otherPosition = otherAvatar.getClientGlobalPosition(); - float distanceToAvatar = glm::length(myPosition - otherPosition); - - // potentially update the max full rate distance for this frame - maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); - - if (distanceToAvatar != 0.0f - && !getsOutOfView - && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { - - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - return; - } - - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); - - if (lastSeqToReceiver > lastSeqFromSender && lastSeqToReceiver != UINT16_MAX) { - // we got out out of order packets from the sender, track it - otherNodeData->incrementNumOutOfOrderSends(); - } - - // make sure we haven't already sent this data from this sender to this receiver - // or that somehow we haven't sent - if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { - ++numAvatarsHeldBack; - - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - return; - } 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 - ++numAvatarsWithSkippedFrames; - } - - // we're going to send this avatar - - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); - - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); - - // determine if avatar is in view, to determine how much data to include... - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); - - // this throttles the extra data to only be sent every Nth message - if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - return; - } - - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - - AvatarData::AvatarDataDetail detail; - if (!isInView && !getsOutOfView) { - detail = AvatarData::MinimumData; - nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; - nodeData->incrementAvatarInView(); - } - - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; - QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); - numAvatarDataBytes += avatarPacketList->write(bytes); - - avatarPacketList->endSegment(); - - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - } - }); - - quint64 startPacketSending = usecTimestampNow(); - - // close the current packet so that we're always sending something - avatarPacketList->closeCurrentPacket(true); - - // send the avatar data PacketList - nodeList->sendPacketList(std::move(avatarPacketList), *node); - - // record the bytes sent for other avatar data in the AvatarMixerClientData - nodeData->recordSentAvatarData(numAvatarDataBytes); - - // record the number of avatars held back this frame - nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); - nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); - - if (numOtherAvatars == 0) { - // update the full rate distance to FLOAT_MAX since we didn't have any other avatars to send - nodeData->setMaxAvatarDistance(FLT_MAX); - } else { - nodeData->setMaxAvatarDistance(maxAvatarDistanceThisFrame); - } - - quint64 endPacketSending = usecTimestampNow(); - // FIXME - mixer data - //_packetSendingElapsedTime += (endPacketSending - startPacketSending); - - } - }); - - // We're done encoding this version of the otherAvatars. Update their "lastSent" joint-states so - // that we can notice differences, next time around. - // - // FIXME - this seems suspicious, the code seems to consider all avatars, but not all avatars will - // have had their joints sent, so actually we should consider the time since they actually were sent???? - std::for_each(cbegin, cend, [&](const SharedNodePointer& otherNode) { - if (otherNode->getLinkedData() && (otherNode->getType() == NodeType::Agent) && otherNode->getActiveSocket()) { - AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - MutexTryLocker lock(otherNodeData->getMutex()); - if (!lock.isLocked()) { - return; - } - AvatarData& otherAvatar = otherNodeData->getAvatar(); - otherAvatar.doneEncoding(false); - } - }); -} - void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { if (killedNode->getType() == NodeType::Agent && killedNode->getLinkedData()) { @@ -774,18 +459,11 @@ void AvatarMixer::sendStatsPacket() { statsObject["threads"] = _slavePool.numThreads(); statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; - statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; - statsObject["broadcast_loop_rate"] = _broadcastRate.rate(); - statsObject["tight_loop_rate"] = _loopRate.rate(); - - // broadcastAvatarDataElapsed timing details... - statsObject["timing_average_b_ignoreCalculation"] = (float)_ignoreCalculationElapsedTime / (float)_numStatFrames; - statsObject["timing_average_c_avatarDataPacking"] = (float)_avatarDataPackingElapsedTime / (float)_numStatFrames; - statsObject["timing_average_d_packetSending"] = (float)_packetSendingElapsedTime / (float)_numStatFrames; + statsObject["broadcast_loop_rate"] = _loopRate.rate(); // this things all occur on the frequency of the tight loop int tightLoopFrames = _numTightLoopFrames; @@ -810,23 +488,37 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_sendStats"] = (float)_sendStatsElapsedTime; + AvatarMixerSlaveStats aggregateStats; + QJsonObject slavesObject; // gather stats int slaveNumber = 1; _slavePool.each([&](AvatarMixerSlave& slave) { QJsonObject slaveObject; - int nodesProcessed, packetsProcessed; - quint64 processIncomingPacketsElapsedTime; - slave.harvestStats(nodesProcessed, packetsProcessed, processIncomingPacketsElapsedTime); - slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(nodesProcessed); - slaveObject["packetsProcessed"] = TIGHT_LOOP_STAT(packetsProcessed); - slaveObject["timing_average_processIncomingPackets"] = TIGHT_LOOP_STAT(processIncomingPacketsElapsedTime); + AvatarMixerSlaveStats stats; + slave.harvestStats(stats); + slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed); + slaveObject["packetsProcessed"] = TIGHT_LOOP_STAT(stats.packetsProcessed); + slaveObject["processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); + slaveObject["ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); + slaveObject["avatarDataPacking"] = TIGHT_LOOP_STAT(stats.avatarDataPackingElapsedTime); + slaveObject["packetSending"] = TIGHT_LOOP_STAT(stats.packetSendingElapsedTime); slavesObject[QString::number(slaveNumber)] = slaveObject; slaveNumber++; + + aggregateStats += stats; }); statsObject["timing_slaves"] = slavesObject; + // broadcastAvatarDataElapsed timing details... + statsObject["timing_aggregate_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); + statsObject["timing_aggregate_packetsProcessed"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); + statsObject["timing_aggregate_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); + statsObject["timing_aggregate_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); + statsObject["timing_aggregate_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); + statsObject["timing_aggregate_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); + _handleViewFrustumPacketElapsedTime = 0; _handleAvatarDataPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 628c44abaf..f0a52c4157 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -55,6 +55,8 @@ private slots: private: AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node); + std::chrono::microseconds AvatarMixer::timeFrame(p_high_resolution_clock::time_point& timestamp); + void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 37fd258bfa..676dd55858 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -35,12 +35,9 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _end = end; } -void AvatarMixerSlave::harvestStats(int& nodesProcessed, int& packetsProcessed, quint64& processIncomingPacketsElapsedTime) { - nodesProcessed = _nodesProcessed; - packetsProcessed = _packetsProcessed; - processIncomingPacketsElapsedTime = _processIncomingPacketsElapsedTime; - _packetsProcessed = _nodesProcessed = 0; - _processIncomingPacketsElapsedTime = 0; +void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) { + stats = _stats; + _stats.reset(); } @@ -48,11 +45,11 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { auto start = usecTimestampNow(); auto nodeData = dynamic_cast(node->getLinkedData()); if (nodeData) { - _nodesProcessed++; - _packetsProcessed += nodeData->processPackets(); + _stats.nodesProcessed++; + _stats.packetsProcessed += nodeData->processPackets(); } auto end = usecTimestampNow(); - _processIncomingPacketsElapsedTime += (end - start); + _stats.processIncomingPacketsElapsedTime += (end - start); } #include @@ -235,8 +232,7 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { shouldConsider = true; quint64 endIgnoreCalculation = usecTimestampNow(); - // FIXME -- mixer data - //_ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); + _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); } //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << "shouldConsider:" << shouldConsider; @@ -252,11 +248,6 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { // FIXME -- might want to track this lock failed... if (!lock.isLocked()) { - - quint64 endAvatarDataPacking = usecTimestampNow(); - - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " failed to lock... would BAIL??... line:" << __LINE__; //return; } @@ -291,89 +282,92 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { // potentially update the max full rate distance for this frame maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); + // FIXME-- understand this code... WHAT IS IT DOING!!! if (distanceToAvatar != 0.0f && !getsOutOfView && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " distance/getsOutOfView... BAILING... line:" << __LINE__; - return; + _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; + shouldConsider = false; } - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); + if (shouldConsider) { + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); + AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); - if (lastSeqToReceiver > lastSeqFromSender && lastSeqToReceiver != UINT16_MAX) { - // we got out out of order packets from the sender, track it - otherNodeData->incrementNumOutOfOrderSends(); + if (lastSeqToReceiver > lastSeqFromSender && lastSeqToReceiver != UINT16_MAX) { + // we got out out of order packets from the sender, track it + otherNodeData->incrementNumOutOfOrderSends(); + } + + // make sure we haven't already sent this data from this sender to this receiver + // or that somehow we haven't sent + if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { + ++numAvatarsHeldBack; + + quint64 endAvatarDataPacking = usecTimestampNow(); + _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; + shouldConsider = false; + } 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 + ++numAvatarsWithSkippedFrames; + } + + // we're going to send this avatar + if (shouldConsider) { + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); // FIXME - this seems weird... + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + + // determine if avatar is in view, to determine how much data to include... + glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; + AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + bool isInView = nodeData->otherAvatarInView(otherNodeBox); + + // this throttles the extra data to only be sent every Nth message + if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { + quint64 endAvatarDataPacking = usecTimestampNow(); + + _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; + shouldConsider = false; + } + + if (shouldConsider) { + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); + + AvatarData::AvatarDataDetail detail; + if (!isInView && !getsOutOfView) { + detail = AvatarData::MinimumData; + nodeData->incrementAvatarOutOfView(); + } else { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO + ? AvatarData::SendAllData : AvatarData::CullSmallData; + nodeData->incrementAvatarInView(); + } + + numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); + auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + bool distanceAdjust = true; + glm::vec3 viewerPosition = myPosition; + QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); + numAvatarDataBytes += avatarPacketList->write(bytes); + + avatarPacketList->endSegment(); + + quint64 endAvatarDataPacking = usecTimestampNow(); + _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + } + } } - - // make sure we haven't already sent this data from this sender to this receiver - // or that somehow we haven't sent - if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { - ++numAvatarsHeldBack; - - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " lastSeqToReceiver... BAILING... line:" << __LINE__; - return; - } 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 - ++numAvatarsWithSkippedFrames; - } - - // we're going to send this avatar - - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); - - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); - - // determine if avatar is in view, to determine how much data to include... - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); - - // this throttles the extra data to only be sent every Nth message - if (!isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)) { - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " isInView && getsOutOfView && (lastSeqToReceiver % EXTRA_AVATAR_DATA_FRAME_RATIO > 0)... BAILING... line:" << __LINE__; - return; - } - - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); - - AvatarData::AvatarDataDetail detail; - if (!isInView && !getsOutOfView) { - detail = AvatarData::MinimumData; - nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; - nodeData->incrementAvatarInView(); - } - - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; - QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); - numAvatarDataBytes += avatarPacketList->write(bytes); - - avatarPacketList->endSegment(); - - quint64 endAvatarDataPacking = usecTimestampNow(); - // FIXME - mixer data - //_avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); } }); @@ -401,8 +395,7 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { } quint64 endPacketSending = usecTimestampNow(); - // FIXME - mixer data - //_packetSendingElapsedTime += (endPacketSending - startPacketSending); + _stats.packetSendingElapsedTime += (endPacketSending - startPacketSending); } } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index d75c6ae396..2e5d3df513 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -21,6 +21,37 @@ #include */ +class AvatarMixerSlaveStats { +public: + int nodesProcessed { 0 }; + int packetsProcessed { 0 }; + quint64 processIncomingPacketsElapsedTime { 0 }; + + quint64 ignoreCalculationElapsedTime { 0 }; + quint64 avatarDataPackingElapsedTime { 0 }; + quint64 packetSendingElapsedTime { 0 }; + + void reset() { + nodesProcessed = 0; + packetsProcessed = 0; + processIncomingPacketsElapsedTime = 0; + ignoreCalculationElapsedTime = 0; + avatarDataPackingElapsedTime = 0; + packetSendingElapsedTime = 0; + } + + AvatarMixerSlaveStats& operator+=(const AvatarMixerSlaveStats& rhs) { + nodesProcessed += rhs.nodesProcessed; + packetsProcessed += rhs.packetsProcessed; + processIncomingPacketsElapsedTime += rhs.processIncomingPacketsElapsedTime; + ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; + avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; + packetSendingElapsedTime += rhs.packetSendingElapsedTime; + return *this; + } + +}; + class AvatarMixerSlave { public: using ConstIter = NodeList::const_iterator; @@ -30,16 +61,14 @@ public: void processIncomingPackets(const SharedNodePointer& node); void anotherJob(const SharedNodePointer& node); - void harvestStats(int& nodesProcessed, int& packetsProcessed, quint64& processIncomingPacketsElapsedTime); + void harvestStats(AvatarMixerSlaveStats& stats); private: // frame state ConstIter _begin; ConstIter _end; - int _nodesProcessed { 0 }; - int _packetsProcessed { 0 }; - quint64 _processIncomingPacketsElapsedTime { 0 }; + AvatarMixerSlaveStats _stats; }; #endif // hifi_AvatarMixerSlave_h From 0a48ea75a78d4bb6774ca7c24db9d00df9c8d8ca Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 10:45:58 -0800 Subject: [PATCH 16/35] fix unix build error --- assignment-client/src/avatars/AvatarMixer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index f0a52c4157..e932601483 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -55,7 +55,7 @@ private slots: private: AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node); - std::chrono::microseconds AvatarMixer::timeFrame(p_high_resolution_clock::time_point& timestamp); + std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp); void broadcastAvatarData(); From d532b3a4b8dcb46da7a114bf6d2f6911c5e6cf72 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 13:25:51 -0800 Subject: [PATCH 17/35] more work on multi-core --- assignment-client/src/avatars/AvatarMixer.cpp | 62 +++++++++++++------ assignment-client/src/avatars/AvatarMixer.h | 8 ++- .../src/avatars/AvatarMixerSlave.cpp | 23 ++++--- .../src/avatars/AvatarMixerSlave.h | 3 + libraries/networking/src/LimitedNodeList.h | 32 +++++++--- 5 files changed, 91 insertions(+), 37 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 73b0514161..cc6100f282 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -134,17 +134,22 @@ void AvatarMixer::start() { // calculates last frame duration and sleeps for the remainder of the target amount auto frameDuration = timeFrame(frameTimestamp); + int lockWait, nodeTransform, functor; + // Allow nodes to process any pending/queued packets across our worker threads { auto start = usecTimestampNow(); + nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsLockWaitElapsedTime += (end - start); _slavePool.processIncomingPackets(cbegin, cend); - }); + }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsElapsedTime += (end - start); + + //qDebug() << "PROCESS PACKETS... " << "lockWait:" << lockWait << "nodeTransform:" << nodeTransform << "functor:" << functor; } // process pending display names... this doesn't currently run on multiple threads, because it @@ -155,9 +160,11 @@ void AvatarMixer::start() { std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { manageDisplayName(node); }); - }); + }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _displayNameManagementElapsedTime += (end - start); + + //qDebug() << "PROCESS PACKETS... " << "lockWait:" << lockWait << "nodeTransform:" << nodeTransform << "functor:" << functor; } // this is where we need to put the real work... @@ -167,10 +174,17 @@ void AvatarMixer::start() { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { + auto start = usecTimestampNow(); _slavePool.anotherJob(cbegin, cend); - }); + auto end = usecTimestampNow(); + _broadcastAvatarDataInner += (end - start); + }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _broadcastAvatarDataElapsedTime += (end - start); + + _broadcastAvatarDataLockWait += lockWait; + _broadcastAvatarDataNodeTransform += nodeTransform; + _broadcastAvatarDataNodeFunctor += functor; } @@ -239,9 +253,6 @@ static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; // this "throttle" logic is the old approach. need to consider some // reasonable throttle approach in new multi-core design void AvatarMixer::broadcastAvatarData() { - quint64 startBroadcastAvatarData = usecTimestampNow(); - _broadcastRate.increment(); - int idleTime = AVATAR_DATA_SEND_INTERVAL_MSECS; if (_lastFrameTimestamp.time_since_epoch().count() > 0) { @@ -317,9 +328,6 @@ void AvatarMixer::broadcastAvatarData() { _lastDebugMessage = p_high_resolution_clock::now(); } #endif - - quint64 endBroadcastAvatarData = usecTimestampNow(); - _broadcastAvatarDataElapsedTime += (endBroadcastAvatarData - startBroadcastAvatarData); } void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { @@ -473,7 +481,12 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_y_processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); statsObject["timing_average_y_queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); - statsObject["timing_average_z_broadcastAvatarData"] = TIGHT_LOOP_STAT(_broadcastAvatarDataElapsedTime); + statsObject["timing_average_a1_broadcastAvatarData"] = TIGHT_LOOP_STAT(_broadcastAvatarDataElapsedTime); + statsObject["timing_average_a2_innnerBroadcastAvatarData"] = TIGHT_LOOP_STAT(_broadcastAvatarDataInner); + statsObject["timing_average_a3_broadcastAvatarDataLockWait"] = TIGHT_LOOP_STAT(_broadcastAvatarDataLockWait); + statsObject["timing_average_a4_broadcastAvatarDataNodeTransform"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeTransform); + statsObject["timing_average_a5_broadcastAvatarDataNodeFunctor"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeFunctor); + statsObject["timing_average_z_displayNameManagement"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); statsObject["timing_average_z_handleAvatarDataPacket"] = TIGHT_LOOP_STAT(_handleAvatarDataPacketElapsedTime); statsObject["timing_average_z_handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); @@ -499,10 +512,13 @@ void AvatarMixer::sendStatsPacket() { slave.harvestStats(stats); slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed); slaveObject["packetsProcessed"] = TIGHT_LOOP_STAT(stats.packetsProcessed); - slaveObject["processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); - slaveObject["ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); - slaveObject["avatarDataPacking"] = TIGHT_LOOP_STAT(stats.avatarDataPackingElapsedTime); - slaveObject["packetSending"] = TIGHT_LOOP_STAT(stats.packetSendingElapsedTime); + slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); + slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); + slaveObject["timing_3_avatarDataPacking"] = TIGHT_LOOP_STAT(stats.avatarDataPackingElapsedTime); + slaveObject["timing_4_packetSending"] = TIGHT_LOOP_STAT(stats.packetSendingElapsedTime); + slaveObject["timing_5_jobElapsedTime"] = TIGHT_LOOP_STAT(stats.jobElapsedTime); + + slavesObject[QString::number(slaveNumber)] = slaveObject; slaveNumber++; @@ -512,12 +528,13 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_slaves"] = slavesObject; // broadcastAvatarDataElapsed timing details... - statsObject["timing_aggregate_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); - statsObject["timing_aggregate_packetsProcessed"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); - statsObject["timing_aggregate_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); - statsObject["timing_aggregate_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); - statsObject["timing_aggregate_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); - statsObject["timing_aggregate_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); + statsObject["aggregate_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); + statsObject["aggregate_packetsProcessed"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); + statsObject["timing_aggregate_1_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); + statsObject["timing_aggregate_2_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); + statsObject["timing_aggregate_3_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); + statsObject["timing_aggregate_4_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); + statsObject["timing_aggregate_4_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); _handleViewFrustumPacketElapsedTime = 0; _handleAvatarDataPacketElapsedTime = 0; @@ -571,6 +588,11 @@ void AvatarMixer::sendStatsPacket() { _numTightLoopFrames = 0; _broadcastAvatarDataElapsedTime = 0; + _broadcastAvatarDataInner = 0; + _broadcastAvatarDataLockWait = 0; + _broadcastAvatarDataNodeTransform = 0; + _broadcastAvatarDataNodeFunctor = 0; + _displayNameManagementElapsedTime = 0; _ignoreCalculationElapsedTime = 0; _avatarDataPackingElapsedTime = 0; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index e932601483..1e9e3bfb95 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -83,12 +83,18 @@ private: p_high_resolution_clock::time_point _lastDebugMessage; QHash> _sessionDisplayNames; - quint64 _broadcastAvatarDataElapsedTime { 0 }; // total time spent in broadcastAvatarData since last stats window quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window quint64 _ignoreCalculationElapsedTime { 0 }; quint64 _avatarDataPackingElapsedTime { 0 }; quint64 _packetSendingElapsedTime { 0 }; + quint64 _broadcastAvatarDataElapsedTime { 0 }; // total time spent in broadcastAvatarData since last stats window + quint64 _broadcastAvatarDataInner { 0 }; + quint64 _broadcastAvatarDataLockWait { 0 }; + quint64 _broadcastAvatarDataNodeTransform { 0 }; + quint64 _broadcastAvatarDataNodeFunctor { 0 }; + + quint64 _handleViewFrustumPacketElapsedTime { 0 }; quint64 _handleAvatarDataPacketElapsedTime { 0 }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 676dd55858..036eba5b36 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -70,6 +70,8 @@ static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // FIXME... this is wrong for 45hz void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { + quint64 start = usecTimestampNow(); + //qDebug() << __FUNCTION__ << "node:" << node; auto nodeList = DependencyManager::get(); @@ -81,13 +83,13 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) { AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); - MutexTryLocker lock(nodeData->getMutex()); + //MutexTryLocker lock(nodeData->getMutex()); // FIXME???? - if (!lock.isLocked()) { + //if (!lock.isLocked()) { //qDebug() << __FUNCTION__ << "unable to lock... node:" << node << " would BAIL???... line:" << __LINE__; //return; - } + //} // FIXME -- mixer data // ++_sumListeners; @@ -244,13 +246,13 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { ++numOtherAvatars; AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - MutexTryLocker lock(otherNodeData->getMutex()); + //MutexTryLocker lock(otherNodeData->getMutex()); // FIXME -- might want to track this lock failed... - if (!lock.isLocked()) { + //if (!lock.isLocked()) { //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " failed to lock... would BAIL??... line:" << __LINE__; //return; - } + //} // make sure we send out identity packets to and from new arrivals. bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); @@ -289,7 +291,7 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; + //qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; shouldConsider = false; } @@ -309,7 +311,7 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; + //qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; shouldConsider = false; } 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 @@ -335,7 +337,7 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; + //qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; shouldConsider = false; } @@ -397,5 +399,8 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { quint64 endPacketSending = usecTimestampNow(); _stats.packetSendingElapsedTime += (endPacketSending - startPacketSending); } + + quint64 end = usecTimestampNow(); + _stats.jobElapsedTime += (end - start); } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 2e5d3df513..ac204ec0f6 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -30,6 +30,7 @@ public: quint64 ignoreCalculationElapsedTime { 0 }; quint64 avatarDataPackingElapsedTime { 0 }; quint64 packetSendingElapsedTime { 0 }; + quint64 jobElapsedTime { 0 }; void reset() { nodesProcessed = 0; @@ -38,6 +39,7 @@ public: ignoreCalculationElapsedTime = 0; avatarDataPackingElapsedTime = 0; packetSendingElapsedTime = 0; + jobElapsedTime = 0; } AvatarMixerSlaveStats& operator+=(const AvatarMixerSlaveStats& rhs) { @@ -47,6 +49,7 @@ public: ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; packetSendingElapsedTime += rhs.packetSendingElapsedTime; + jobElapsedTime += rhs.jobElapsedTime; return *this; } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 5256e55397..cbca64bcd8 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -182,15 +182,33 @@ public: // This allows multiple threads (i.e. a thread pool) to share a lock // without deadlocking when a dying node attempts to acquire a write lock template - void nestedEach(NestedNodeLambda functor) { - QReadLocker readLock(&_nodeMutex); + void nestedEach(NestedNodeLambda functor, + int* lockWaitOut = nullptr, + int* nodeTransformOut = nullptr, + int* functorOut = nullptr) { + auto start = usecTimestampNow(); + { + QReadLocker readLock(&_nodeMutex); + auto endLock = usecTimestampNow(); + if (lockWaitOut) { + *lockWaitOut = (endLock - start); + } - std::vector nodes(_nodeHash.size()); - std::transform(_nodeHash.cbegin(), _nodeHash.cend(), nodes.begin(), [](const NodeHash::value_type& it) { - return it.second; - }); + std::vector nodes(_nodeHash.size()); + std::transform(_nodeHash.cbegin(), _nodeHash.cend(), nodes.begin(), [](const NodeHash::value_type& it) { + return it.second; + }); + auto endTransform = usecTimestampNow(); + if (nodeTransformOut) { + *nodeTransformOut = (endTransform - endLock); + } - functor(nodes.cbegin(), nodes.cend()); + functor(nodes.cbegin(), nodes.cend()); + auto endFunctor = usecTimestampNow(); + if (functorOut) { + *functorOut = (endFunctor - endTransform); + } + } } template From d49c83cac3485792a9a4f2f091a7edb725a337dd Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 14:19:14 -0800 Subject: [PATCH 18/35] fix build buster, some tweaks --- assignment-client/src/avatars/AvatarMixer.cpp | 16 +++++++------- .../src/avatars/AvatarMixerClientData.cpp | 2 +- .../src/avatars/AvatarMixerSlave.cpp | 21 ++++++++++++------- .../src/avatars/AvatarMixerSlave.h | 3 +++ libraries/networking/src/LimitedNodeList.h | 1 + 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index cc6100f282..363fb41ee3 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -514,11 +514,10 @@ void AvatarMixer::sendStatsPacket() { slaveObject["packetsProcessed"] = TIGHT_LOOP_STAT(stats.packetsProcessed); slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); - slaveObject["timing_3_avatarDataPacking"] = TIGHT_LOOP_STAT(stats.avatarDataPackingElapsedTime); - slaveObject["timing_4_packetSending"] = TIGHT_LOOP_STAT(stats.packetSendingElapsedTime); - slaveObject["timing_5_jobElapsedTime"] = TIGHT_LOOP_STAT(stats.jobElapsedTime); - - + slaveObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT(stats.toByteArrayElapsedTime); + slaveObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT(stats.avatarDataPackingElapsedTime); + slaveObject["timing_5_packetSending"] = TIGHT_LOOP_STAT(stats.packetSendingElapsedTime); + slaveObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT(stats.jobElapsedTime); slavesObject[QString::number(slaveNumber)] = slaveObject; slaveNumber++; @@ -532,9 +531,10 @@ void AvatarMixer::sendStatsPacket() { statsObject["aggregate_packetsProcessed"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); statsObject["timing_aggregate_1_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); statsObject["timing_aggregate_2_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); - statsObject["timing_aggregate_3_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); - statsObject["timing_aggregate_4_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); - statsObject["timing_aggregate_4_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); + statsObject["timing_aggregate_3_toByteArray"] = TIGHT_LOOP_STAT(aggregateStats.toByteArrayElapsedTime); + statsObject["timing_aggregate_4_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); + statsObject["timing_aggregate_5_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); + statsObject["timing_aggregate_6_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); _handleViewFrustumPacketElapsedTime = 0; _handleAvatarDataPacketElapsedTime = 0; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 09e3a6a87a..a598495269 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -41,7 +41,7 @@ int AvatarMixerClientData::processPackets() { _packetQueue.node.clear(); while (!_packetQueue.empty()) { - auto& packet = _packetQueue.back(); + auto& packet = _packetQueue.front(); packetsProcessed++; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 036eba5b36..89218c6ea6 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -355,13 +355,20 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { nodeData->incrementAvatarInView(); } - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; - QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); - numAvatarDataBytes += avatarPacketList->write(bytes); + { + numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); + auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + bool distanceAdjust = true; + glm::vec3 viewerPosition = myPosition; + + quint64 start = usecTimestampNow(); + QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); + quint64 end = usecTimestampNow(); + _stats.toByteArrayElapsedTime += (end - start); + + numAvatarDataBytes += avatarPacketList->write(bytes); + } avatarPacketList->endSegment(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index ac204ec0f6..cbdfc4a08c 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -30,6 +30,7 @@ public: quint64 ignoreCalculationElapsedTime { 0 }; quint64 avatarDataPackingElapsedTime { 0 }; quint64 packetSendingElapsedTime { 0 }; + quint64 toByteArrayElapsedTime { 0 }; quint64 jobElapsedTime { 0 }; void reset() { @@ -39,6 +40,7 @@ public: ignoreCalculationElapsedTime = 0; avatarDataPackingElapsedTime = 0; packetSendingElapsedTime = 0; + toByteArrayElapsedTime = 0; jobElapsedTime = 0; } @@ -49,6 +51,7 @@ public: ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; packetSendingElapsedTime += rhs.packetSendingElapsedTime; + toByteArrayElapsedTime += rhs.toByteArrayElapsedTime; jobElapsedTime += rhs.jobElapsedTime; return *this; } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index cbca64bcd8..3eb898463a 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -34,6 +34,7 @@ #include #include +#include #include "DomainHandler.h" #include "Node.h" From df54762da91c2526d9f83ff9cd9760dd38a60d2f Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 15:56:02 -0800 Subject: [PATCH 19/35] add some more stats --- assignment-client/src/avatars/AvatarMixer.cpp | 9 +++++++-- assignment-client/src/avatars/AvatarMixerSlave.cpp | 2 ++ assignment-client/src/avatars/AvatarMixerSlave.h | 8 ++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 363fb41ee3..c60b2c8fbc 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -511,7 +511,9 @@ void AvatarMixer::sendStatsPacket() { AvatarMixerSlaveStats stats; slave.harvestStats(stats); slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed); - slaveObject["packetsProcessed"] = TIGHT_LOOP_STAT(stats.packetsProcessed); + slaveObject["numPacketsReceived"] = TIGHT_LOOP_STAT(stats.packetsProcessed); + statsObject["numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent); + slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); slaveObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT(stats.toByteArrayElapsedTime); @@ -528,7 +530,10 @@ void AvatarMixer::sendStatsPacket() { // broadcastAvatarDataElapsed timing details... statsObject["aggregate_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); - statsObject["aggregate_packetsProcessed"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); + statsObject["aggregate_numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); + statsObject["aggregate_numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent); + + statsObject["timing_aggregate_1_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); statsObject["timing_aggregate_2_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); statsObject["timing_aggregate_3_toByteArray"] = TIGHT_LOOP_STAT(aggregateStats.toByteArrayElapsedTime); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 89218c6ea6..ca1d84ecfb 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -385,6 +385,8 @@ void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { // close the current packet so that we're always sending something avatarPacketList->closeCurrentPacket(true); + _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); + // send the avatar data PacketList //qDebug() << "about to call nodeList->sendPacketList() for node:" << node; nodeList->sendPacketList(std::move(avatarPacketList), *node); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index cbdfc4a08c..939fa39116 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -27,6 +27,7 @@ public: int packetsProcessed { 0 }; quint64 processIncomingPacketsElapsedTime { 0 }; + int numPacketsSent { 0 }; quint64 ignoreCalculationElapsedTime { 0 }; quint64 avatarDataPackingElapsedTime { 0 }; quint64 packetSendingElapsedTime { 0 }; @@ -34,9 +35,14 @@ public: quint64 jobElapsedTime { 0 }; void reset() { + // receiving job stats nodesProcessed = 0; packetsProcessed = 0; + numPacketsSent = 0; processIncomingPacketsElapsedTime = 0; + + // sending job stats + numPacketsSent = 0; ignoreCalculationElapsedTime = 0; avatarDataPackingElapsedTime = 0; packetSendingElapsedTime = 0; @@ -48,6 +54,8 @@ public: nodesProcessed += rhs.nodesProcessed; packetsProcessed += rhs.packetsProcessed; processIncomingPacketsElapsedTime += rhs.processIncomingPacketsElapsedTime; + + numPacketsSent += rhs.numPacketsSent; ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; packetSendingElapsedTime += rhs.packetSendingElapsedTime; From ece32a3c684e5d5f2178e69a1227046749ce88d3 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 16:19:40 -0800 Subject: [PATCH 20/35] cleanup --- assignment-client/src/avatars/AvatarMixer.cpp | 15 ++++++--------- assignment-client/src/avatars/AvatarMixer.h | 8 +------- .../src/avatars/AvatarMixerClientData.cpp | 9 --------- .../src/avatars/AvatarMixerSlave.cpp | 2 +- assignment-client/src/avatars/AvatarMixerSlave.h | 11 +---------- .../src/avatars/AvatarMixerSlavePool.cpp | 4 ++-- .../src/avatars/AvatarMixerSlavePool.h | 2 +- 7 files changed, 12 insertions(+), 39 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c60b2c8fbc..8249daed9d 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -124,10 +124,12 @@ void AvatarMixer::start() { // DO THIS FIRST!!!!!!!! // // DONE --- 1) only sleep for remainder - // 2) clean up stats, add slave stats + // DONE --- 2) clean up stats, add slave stats // 3) delete dead code from mixer (now that it's in slave) // 4) audit the locking and side-effects to node, otherNode, and nodeData // 5) throttling?? + // 6) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. + // 7) fix two different versions of toByteArray() ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -175,7 +177,7 @@ void AvatarMixer::start() { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { auto start = usecTimestampNow(); - _slavePool.anotherJob(cbegin, cend); + _slavePool.broadcastAvatarData(cbegin, cend); auto end = usecTimestampNow(); _broadcastAvatarDataInner += (end - start); }, &lockWait, &nodeTransform, &functor); @@ -260,8 +262,6 @@ void AvatarMixer::broadcastAvatarData() { idleTime = std::chrono::duration_cast(idleDuration).count(); } - ++_numStatFrames; - const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; @@ -466,8 +466,8 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; statsObject["threads"] = _slavePool.numThreads(); - statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; - statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; + //statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; + //statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; @@ -488,7 +488,6 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_average_a5_broadcastAvatarDataNodeFunctor"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeFunctor); statsObject["timing_average_z_displayNameManagement"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); - statsObject["timing_average_z_handleAvatarDataPacket"] = TIGHT_LOOP_STAT(_handleAvatarDataPacketElapsedTime); statsObject["timing_average_z_handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); statsObject["timing_average_z_handleKillAvatarPacket"] = TIGHT_LOOP_STAT(_handleKillAvatarPacketElapsedTime); statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleNodeIgnoreRequestPacketElapsedTime); @@ -542,7 +541,6 @@ void AvatarMixer::sendStatsPacket() { statsObject["timing_aggregate_6_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); _handleViewFrustumPacketElapsedTime = 0; - _handleAvatarDataPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; _handleKillAvatarPacketElapsedTime = 0; _handleNodeIgnoreRequestPacketElapsedTime = 0; @@ -589,7 +587,6 @@ void AvatarMixer::sendStatsPacket() { _sumListeners = 0; _sumIdentityPackets = 0; - _numStatFrames = 0; _numTightLoopFrames = 0; _broadcastAvatarDataElapsedTime = 0; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 1e9e3bfb95..7a79ec1d57 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -15,8 +15,6 @@ #ifndef hifi_AvatarMixer_h #define hifi_AvatarMixer_h -#include - #include #include @@ -42,7 +40,6 @@ public slots: private slots: void queueIncomingPacket(QSharedPointer message, SharedNodePointer node); void handleViewFrustumPacket(QSharedPointer message, SharedNodePointer senderNode); - //void handleAvatarDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode); void handleKillAvatarPacket(QSharedPointer message); void handleNodeIgnoreRequestPacket(QSharedPointer message, SharedNodePointer senderNode); @@ -71,7 +68,7 @@ private: int _sumListeners { 0 }; int _numStatFrames { 0 }; - std::atomic _numTightLoopFrames; + int _numTightLoopFrames { 0 }; int _sumIdentityPackets { 0 }; float _maxKbpsPerNode = 0.0f; @@ -94,10 +91,7 @@ private: quint64 _broadcastAvatarDataNodeTransform { 0 }; quint64 _broadcastAvatarDataNodeFunctor { 0 }; - - quint64 _handleViewFrustumPacketElapsedTime { 0 }; - quint64 _handleAvatarDataPacketElapsedTime { 0 }; quint64 _handleAvatarIdentityPacketElapsedTime { 0 }; quint64 _handleKillAvatarPacketElapsedTime { 0 }; quint64 _handleNodeIgnoreRequestPacketElapsedTime { 0 }; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index a598495269..b5d4e390bb 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -25,15 +25,6 @@ void AvatarMixerClientData::queuePacket(QSharedPointer message, _packetQueue.push(message); } -// -// packetReceiver.registerListener(PacketType::ViewFrustum, this, "handleViewFrustumPacket"); -// packetReceiver.registerListener(PacketType::AvatarData, this, "handleAvatarDataPacket"); -// packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); -// packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); -// packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); -// packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); -// packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); - int AvatarMixerClientData::processPackets() { int packetsProcessed = 0; SharedNodePointer node = _packetQueue.node; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ca1d84ecfb..a7d0dc9c52 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -69,7 +69,7 @@ static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; // assuming 60 htz update rate. const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // FIXME... this is wrong for 45hz -void AvatarMixerSlave::anotherJob(const SharedNodePointer& node) { +void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 start = usecTimestampNow(); //qDebug() << __FUNCTION__ << "node:" << node; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 939fa39116..b693356349 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -12,15 +12,6 @@ #ifndef hifi_AvatarMixerSlave_h #define hifi_AvatarMixerSlave_h -/* -#include -#include -#include -#include -#include -#include -*/ - class AvatarMixerSlaveStats { public: int nodesProcessed { 0 }; @@ -73,7 +64,7 @@ public: void configure(ConstIter begin, ConstIter end); void processIncomingPackets(const SharedNodePointer& node); - void anotherJob(const SharedNodePointer& node); + void broadcastAvatarData(const SharedNodePointer& node); void harvestStats(AvatarMixerSlaveStats& stats); diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 28a3dce0e3..f410bb566d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -73,8 +73,8 @@ void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end run(begin, end); } -void AvatarMixerSlavePool::anotherJob(ConstIter begin, ConstIter end) { - _function = &AvatarMixerSlave::anotherJob; +void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end) { + _function = &AvatarMixerSlave::broadcastAvatarData; _configure = [&](AvatarMixerSlave& slave) { slave.configure(begin, end); }; run(begin, end); } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index 8f13477a93..0a689b35ed 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -65,7 +65,7 @@ public: // Jobs the slave pool can do... void processIncomingPackets(ConstIter begin, ConstIter end); - void anotherJob(ConstIter begin, ConstIter end); + void broadcastAvatarData(ConstIter begin, ConstIter end); // iterate over all slaves void each(std::function functor); From be12a8ffa4636c4e0910da44b42f5e2ef1736cd9 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 16 Feb 2017 16:58:06 -0800 Subject: [PATCH 21/35] stats cleanup --- assignment-client/src/avatars/AvatarMixer.cpp | 89 ++++++++++++------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 8249daed9d..5394b6efec 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -464,44 +464,63 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; - statsObject["threads"] = _slavePool.numThreads(); //statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; //statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; - statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; - statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; statsObject["broadcast_loop_rate"] = _loopRate.rate(); + statsObject["threads"] = _slavePool.numThreads(); + statsObject["throttling_1_trailing_sleep_percentage"] = _trailingSleepRatio * 100; + statsObject["throttling_2_performance_ratio"] = _performanceThrottlingRatio; + // this things all occur on the frequency of the tight loop int tightLoopFrames = _numTightLoopFrames; int tenTimesPerFrame = tightLoopFrames * 10; #define TIGHT_LOOP_STAT(x) (x > tenTimesPerFrame) ? x / tightLoopFrames : ((float)x / (float)tightLoopFrames); - statsObject["timing_average_y_processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); - statsObject["timing_average_y_queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); + QJsonObject singleCoreTasks; + singleCoreTasks["processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); + singleCoreTasks["queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); - statsObject["timing_average_a1_broadcastAvatarData"] = TIGHT_LOOP_STAT(_broadcastAvatarDataElapsedTime); - statsObject["timing_average_a2_innnerBroadcastAvatarData"] = TIGHT_LOOP_STAT(_broadcastAvatarDataInner); - statsObject["timing_average_a3_broadcastAvatarDataLockWait"] = TIGHT_LOOP_STAT(_broadcastAvatarDataLockWait); - statsObject["timing_average_a4_broadcastAvatarDataNodeTransform"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeTransform); - statsObject["timing_average_a5_broadcastAvatarDataNodeFunctor"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeFunctor); + QJsonObject incomingPacketStats; + incomingPacketStats["handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); + incomingPacketStats["handleKillAvatarPacket"] = TIGHT_LOOP_STAT(_handleKillAvatarPacketElapsedTime); + incomingPacketStats["handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleNodeIgnoreRequestPacketElapsedTime); + incomingPacketStats["handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleRadiusIgnoreRequestPacketElapsedTime); + incomingPacketStats["handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT(_handleRequestsDomainListDataPacketElapsedTime); + incomingPacketStats["handleViewFrustumPacket"] = TIGHT_LOOP_STAT(_handleViewFrustumPacketElapsedTime); - statsObject["timing_average_z_displayNameManagement"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); - statsObject["timing_average_z_handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); - statsObject["timing_average_z_handleKillAvatarPacket"] = TIGHT_LOOP_STAT(_handleKillAvatarPacketElapsedTime); - statsObject["timing_average_z_handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleNodeIgnoreRequestPacketElapsedTime); - statsObject["timing_average_z_handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleRadiusIgnoreRequestPacketElapsedTime); - statsObject["timing_average_z_handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT(_handleRequestsDomainListDataPacketElapsedTime); - statsObject["timing_average_z_handleViewFrustumPacket"] = TIGHT_LOOP_STAT(_handleViewFrustumPacketElapsedTime); - statsObject["timing_average_z_processQueuedAvatarDataPackets"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsElapsedTime); - statsObject["timing_average_z_processQueuedAvatarDataPacketsLockWait"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsLockWaitElapsedTime); + singleCoreTasks["incoming_packets"] = incomingPacketStats; + singleCoreTasks["sendStats"] = (float)_sendStatsElapsedTime; - statsObject["timing_sendStats"] = (float)_sendStatsElapsedTime; + statsObject["singleCoreTasks"] = singleCoreTasks; + + QJsonObject parallelTasks; + + QJsonObject processQueuedAvatarDataPacketsStats; + processQueuedAvatarDataPacketsStats["1_total"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsElapsedTime); + processQueuedAvatarDataPacketsStats["2_lockWait"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsLockWaitElapsedTime); + parallelTasks["processQueuedAvatarDataPackets"] = processQueuedAvatarDataPacketsStats; + + QJsonObject broadcastAvatarDataStats; + + broadcastAvatarDataStats["1_total"] = TIGHT_LOOP_STAT(_broadcastAvatarDataElapsedTime); + broadcastAvatarDataStats["2_innner"] = TIGHT_LOOP_STAT(_broadcastAvatarDataInner); + broadcastAvatarDataStats["3_lockWait"] = TIGHT_LOOP_STAT(_broadcastAvatarDataLockWait); + broadcastAvatarDataStats["4_NodeTransform"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeTransform); + broadcastAvatarDataStats["5_Functor"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeFunctor); + + parallelTasks["broadcastAvatarData"] = broadcastAvatarDataStats; + + QJsonObject displayNameManagementStats; + displayNameManagementStats["1_total"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); + parallelTasks["displayNameManagement"] = displayNameManagementStats; + + statsObject["parallelTasks"] = parallelTasks; AvatarMixerSlaveStats aggregateStats; - QJsonObject slavesObject; // gather stats int slaveNumber = 1; @@ -511,7 +530,7 @@ void AvatarMixer::sendStatsPacket() { slave.harvestStats(stats); slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed); slaveObject["numPacketsReceived"] = TIGHT_LOOP_STAT(stats.packetsProcessed); - statsObject["numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent); + slaveObject["numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent); slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); @@ -525,20 +544,22 @@ void AvatarMixer::sendStatsPacket() { aggregateStats += stats; }); - statsObject["timing_slaves"] = slavesObject; - // broadcastAvatarDataElapsed timing details... - statsObject["aggregate_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); - statsObject["aggregate_numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); - statsObject["aggregate_numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent); - + QJsonObject slavesAggregatObject; - statsObject["timing_aggregate_1_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); - statsObject["timing_aggregate_2_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); - statsObject["timing_aggregate_3_toByteArray"] = TIGHT_LOOP_STAT(aggregateStats.toByteArrayElapsedTime); - statsObject["timing_aggregate_4_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); - statsObject["timing_aggregate_5_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); - statsObject["timing_aggregate_6_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); + slavesAggregatObject["nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); + slavesAggregatObject["numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); + slavesAggregatObject["numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent); + + slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); + slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); + slavesAggregatObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT(aggregateStats.toByteArrayElapsedTime); + slavesAggregatObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); + slavesAggregatObject["timing_5_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); + slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); + + statsObject["slaves_aggregate"] = slavesAggregatObject; + statsObject["slaves_individual"] = slavesObject; _handleViewFrustumPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; From 78147c27ad45186765bcbfcb606a1c13debaa6b0 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 08:33:27 -0800 Subject: [PATCH 22/35] log too large byteArray instead of crashing --- assignment-client/src/avatars/AvatarMixer.cpp | 9 +++++---- assignment-client/src/avatars/AvatarMixerSlave.cpp | 6 +++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3587b675b1..db158c861c 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -125,11 +125,12 @@ void AvatarMixer::start() { // // DONE --- 1) only sleep for remainder // DONE --- 2) clean up stats, add slave stats - // 3) delete dead code from mixer (now that it's in slave) - // 4) audit the locking and side-effects to node, otherNode, and nodeData + // 3a) out of view??? is it broken? + // 3) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. + // 4) fix two different versions of toByteArray() // 5) throttling?? - // 6) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. - // 7) fix two different versions of toByteArray() + // 6) audit the locking and side-effects to node, otherNode, and nodeData + // 7) delete dead code from mixer (now that it's in slave) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index af76b52547..bff34d9751 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -367,7 +367,11 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - numAvatarDataBytes += avatarPacketList->write(bytes); + if (bytes.size() > 1400) { + qDebug() << "WARNING: otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size(); + } else { + numAvatarDataBytes += avatarPacketList->write(bytes); + } } avatarPacketList->endSegment(); From 92ca7de0bf1064574772ccf1a637658df2422539 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 10:12:35 -0800 Subject: [PATCH 23/35] some tweaks to support too large avatar data --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 5 ++++- libraries/avatars/src/AvatarData.cpp | 6 +++--- libraries/avatars/src/AvatarData.h | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index bff34d9751..ca2db55cdd 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -361,9 +361,12 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; + AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray + bool dropFaceTracking = true; // this is a hack for now... always drop face tracking quint64 start = usecTimestampNow(); - QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, distanceAdjust, viewerPosition, &lastSentJointsForOther); + QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index cbd5d42263..66d1fb39bf 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -169,7 +169,7 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) { + bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) { // if no timestamp was included, then assume the avatarData is single instance // and is tracking its own last encoding time. @@ -575,7 +575,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const { + AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); bool sendAll = (dataDetail == SendAllData); @@ -625,7 +625,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent tranlationChangedSince(lastSentTime) || parentInfoChangedSince(lastSentTime)); - bool hasFaceTrackerInfo = hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + bool hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); bool hasJointData = sendAll || !sendMinimum; // Leading flags, to indicate how much data is actually included in the packet... diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 45e62d4045..64c0e11577 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -356,7 +356,7 @@ public: // FIXME virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const; + AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const; virtual void doneEncoding(bool cullSmallChanges); From 291b823cfa7ff28e82c371fe5dba0abcad5688e7 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 18:28:07 -0800 Subject: [PATCH 24/35] const cleanup and fix crash --- assignment-client/src/Agent.cpp | 2 +- assignment-client/src/avatars/AvatarMixer.cpp | 18 +- .../src/avatars/AvatarMixerClientData.cpp | 1 + .../src/avatars/AvatarMixerClientData.h | 1 + .../src/avatars/AvatarMixerSlave.cpp | 24 +- .../src/avatars/ScriptableAvatar.cpp | 5 +- .../src/avatars/ScriptableAvatar.h | 3 +- interface/src/avatar/MyAvatar.cpp | 7 +- interface/src/avatar/MyAvatar.h | 3 +- libraries/avatars/src/AvatarData.cpp | 499 +++--------------- libraries/avatars/src/AvatarData.h | 40 +- 11 files changed, 117 insertions(+), 486 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 806608cd5f..a458719346 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -546,7 +546,7 @@ void Agent::processAgentAvatar() { auto scriptedAvatar = DependencyManager::get(); AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData; - QByteArray avatarByteArray = scriptedAvatar->toByteArray(dataDetail, 0, scriptedAvatar->getLastSentJointData()); + QByteArray avatarByteArray = scriptedAvatar->toByteArray(dataDetail); scriptedAvatar->doneEncoding(true); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index db158c861c..51f6067e6a 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -125,12 +125,18 @@ void AvatarMixer::start() { // // DONE --- 1) only sleep for remainder // DONE --- 2) clean up stats, add slave stats - // 3a) out of view??? is it broken? - // 3) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. - // 4) fix two different versions of toByteArray() - // 5) throttling?? - // 6) audit the locking and side-effects to node, otherNode, and nodeData - // 7) delete dead code from mixer (now that it's in slave) + // DONE --- 3) out of view??? is it broken? - verified - it's working + // 4) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. + // DONE --- 4a) hack to not send face data mostly seems to work... + // 4b) some kind of a better approach to handling otherAvatar.toByteArray() for content that is larger than MTU + // 5) fix two different versions of toByteArray() + // 6) throttling?? + // 7) audit the locking and side-effects to node, otherNode, and nodeData + // 8) delete dead code from mixer (now that it's in slave) + // 9) better stats in the nodes: + // how many avatars are actually "in view" for the avtar in question (even if they are over bandwidth budget) + // + // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index b5d4e390bb..6fdd2fd23e 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -58,6 +58,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); } +// FIXME -- this needs a mutex in new model. bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) { if (_hasReceivedFirstPacketsFrom.find(uuid) == _hasReceivedFirstPacketsFrom.end()) { _hasReceivedFirstPacketsFrom.insert(uuid); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 8cd72050f7..87f7bb9cc9 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -42,6 +42,7 @@ public: int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } + const AvatarData* getConstAvatarData() const { return _avatar.get(); } bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index ca2db55cdd..2a7f9a4d8d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -245,17 +245,12 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { ++numOtherAvatars; - AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - //MutexTryLocker lock(otherNodeData->getMutex()); - - // FIXME -- might want to track this lock failed... - //if (!lock.isLocked()) { - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " failed to lock... would BAIL??... line:" << __LINE__; - //return; - //} + const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); // make sure we send out identity packets to and from new arrivals. - bool forceSend = !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); + // FIXME this is where our crash was on friday!! + // this is getting called on multiple threads... needs mutex of better solution + bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID()); if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0 && (forceSend @@ -265,7 +260,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // FIXME --- used to be.../ mixer data dependency //sendIdentityPacket(otherNodeData, node); - QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); + QByteArray individualData = otherNodeData->getConstAvatarData()->identityByteArray(); auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNodeData->getNodeID().toRfc4122()); identityPacket->write(individualData); @@ -273,12 +268,12 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " sending itentity packet for otherNode to node..."; } - const AvatarData& otherAvatar = otherNodeData->getAvatar(); + const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); // Decide whether to send this avatar's data based on it's distance from us // The full rate distance is the distance at which EVERY update will be sent for this avatar // at twice the full rate distance, there will be a 50% chance of sending this avatar's update - glm::vec3 otherPosition = otherAvatar.getClientGlobalPosition(); + glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); float distanceToAvatar = glm::length(myPosition - otherPosition); // potentially update the max full rate distance for this frame @@ -299,10 +294,13 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); + /*** + // FIXME this does not belong here... it should be in the packet processing if (lastSeqToReceiver > lastSeqFromSender && lastSeqToReceiver != UINT16_MAX) { // we got out out of order packets from the sender, track it otherNodeData->incrementNumOutOfOrderSends(); } + ***/ // make sure we haven't already sent this data from this sender to this receiver // or that somehow we haven't sent @@ -365,7 +363,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { bool dropFaceTracking = true; // this is a hack for now... always drop face tracking quint64 start = usecTimestampNow(); - QByteArray bytes = otherAvatar.toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index 95bcbb587e..cdda070da2 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -14,10 +14,9 @@ #include #include "ScriptableAvatar.h" -QByteArray ScriptableAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) { +QByteArray ScriptableAvatar::toByteArray(AvatarDataDetail dataDetail) { _globalPosition = getPosition(); - return AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut); + return AvatarData::toByteArray(dataDetail); } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index be7a90adf9..b6804da43d 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -28,8 +28,7 @@ public: Q_INVOKABLE AnimationDetails getAnimationDetails(); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; - virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector* sentJointDataOut = nullptr) override; + virtual QByteArray toByteArray(AvatarDataDetail dataDetail) override; private slots: diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 29f41c89fd..21b75e4e71 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -227,8 +227,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() } -QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) { +QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail) { CameraMode mode = qApp->getCamera()->getMode(); _globalPosition = getPosition(); _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); @@ -239,12 +238,12 @@ QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTi // fake the avatar position that is sent up to the AvatarMixer glm::vec3 oldPosition = getPosition(); setPosition(getSkeletonPosition()); - QByteArray array = AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut); + QByteArray array = AvatarData::toByteArray(dataDetail); // copy the correct position back setPosition(oldPosition); return array; } - return AvatarData::toByteArray(dataDetail, lastSentTime, lastSentJointData, distanceAdjust, viewerPosition, sentJointDataOut); + return AvatarData::toByteArray(dataDetail); } void MyAvatar::centerBody() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c4fe86356d..982f0505ff 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -338,8 +338,7 @@ private: glm::quat getWorldBodyOrientation() const; - virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector* sentJointDataOut = nullptr) override; + virtual QByteArray toByteArray(AvatarDataDetail dataDetail) override; void simulate(float deltaTime); void updateFromTrackers(float deltaTime); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 66d1fb39bf..f48b31881a 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -168,414 +168,19 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio } -QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) { - - // if no timestamp was included, then assume the avatarData is single instance - // and is tracking its own last encoding time. - if (lastSentTime == 0) { - lastSentTime = _lastToByteArray; - _lastToByteArray = usecTimestampNow(); - } - - bool cullSmallChanges = (dataDetail == CullSmallData); - bool sendAll = (dataDetail == SendAllData); - bool sendMinimum = (dataDetail == MinimumData); - - lazyInitHeadData(); - - QByteArray avatarDataByteArray(udt::MAX_PACKET_SIZE, 0); - unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); - unsigned char* startPosition = destinationBuffer; - - // FIXME - - // - // BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens... - // this is an iFrame issue... what to do about that? - // - // BUG -- Resizing avatar seems to "take too long"... the avatar doesn't redraw at smaller size right away - // - // TODO consider these additional optimizations in the future - // 1) SensorToWorld - should we only send this for avatars with attachments?? - 20 bytes - 7.20 kbps - // 2) GUIID for the session change to 2byte index (savings) - 14 bytes - 5.04 kbps - // 3) Improve Joints -- currently we use rotational tolerances, but if we had skeleton/bone length data - // we could do a better job of determining if the change in joints actually translates to visible - // changes at distance. - // - // Potential savings: - // 63 rotations * 6 bytes = 136kbps - // 3 translations * 6 bytes = 6.48kbps - // - - auto parentID = getParentID(); - - bool hasAvatarGlobalPosition = true; // always include global position - bool hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime); - bool hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime); - bool hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime); - bool hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime); - bool hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime); - bool hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime); - bool hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime); - - // local position, and parent info only apply to avatars that are parented. The local position - // and the parent info can change independently though, so we track their "changed since" - // separately - bool hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime); - bool hasAvatarLocalPosition = hasParent() && (sendAll || - tranlationChangedSince(lastSentTime) || - parentInfoChangedSince(lastSentTime)); - - bool hasFaceTrackerInfo = hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); - bool hasJointData = sendAll || !sendMinimum; - - // Leading flags, to indicate how much data is actually included in the packet... - AvatarDataPacket::HasFlags packetStateFlags = - (hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0) - | (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0) - | (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0) - | (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0) - | (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0) - | (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0) - | (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0) - | (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0) - | (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0) - | (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0) - | (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0) - | (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0); - - memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags)); - destinationBuffer += sizeof(packetStateFlags); - - if (hasAvatarGlobalPosition) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - data->globalPosition[0] = _globalPosition.x; - data->globalPosition[1] = _globalPosition.y; - data->globalPosition[2] = _globalPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition); - - int numBytes = destinationBuffer - startSection; - - _globalPositionRateOutbound.increment(numBytes); - } - - if (hasAvatarBoundingBox) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - - data->avatarDimensions[0] = _globalBoundingBoxDimensions.x; - data->avatarDimensions[1] = _globalBoundingBoxDimensions.y; - data->avatarDimensions[2] = _globalBoundingBoxDimensions.z; - - data->boundOriginOffset[0] = _globalBoundingBoxOffset.x; - data->boundOriginOffset[1] = _globalBoundingBoxOffset.y; - data->boundOriginOffset[2] = _globalBoundingBoxOffset.z; - - destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); - - int numBytes = destinationBuffer - startSection; - _avatarBoundingBoxRateOutbound.increment(numBytes); - } - - if (hasAvatarOrientation) { - auto startSection = destinationBuffer; - auto localOrientation = getLocalOrientation(); - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); - - int numBytes = destinationBuffer - startSection; - _avatarOrientationRateOutbound.increment(numBytes); - } - - if (hasAvatarScale) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto scale = getDomainLimitedScale(); - packFloatRatioToTwoByte((uint8_t*)(&data->scale), scale); - destinationBuffer += sizeof(AvatarDataPacket::AvatarScale); - - int numBytes = destinationBuffer - startSection; - _avatarScaleRateOutbound.increment(numBytes); - } - - if (hasLookAtPosition) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto lookAt = _headData->getLookAtPosition(); - data->lookAtPosition[0] = lookAt.x; - data->lookAtPosition[1] = lookAt.y; - data->lookAtPosition[2] = lookAt.z; - destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); - - int numBytes = destinationBuffer - startSection; - _lookAtPositionRateOutbound.increment(numBytes); - } - - if (hasAudioLoudness) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - data->audioLoudness = packFloatGainToByte(_headData->getAudioLoudness() / AUDIO_LOUDNESS_SCALE); - destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness); - - int numBytes = destinationBuffer - startSection; - _audioLoudnessRateOutbound.increment(numBytes); - } - - if (hasSensorToWorldMatrix) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); - packOrientationQuatToSixBytes(data->sensorToWorldQuat, glmExtractRotation(sensorToWorldMatrix)); - glm::vec3 scale = extractScale(sensorToWorldMatrix); - packFloatScalarToSignedTwoByteFixed((uint8_t*)&data->sensorToWorldScale, scale.x, SENSOR_TO_WORLD_SCALE_RADIX); - data->sensorToWorldTrans[0] = sensorToWorldMatrix[3][0]; - data->sensorToWorldTrans[1] = sensorToWorldMatrix[3][1]; - data->sensorToWorldTrans[2] = sensorToWorldMatrix[3][2]; - destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); - - int numBytes = destinationBuffer - startSection; - _sensorToWorldRateOutbound.increment(numBytes); - } - - if (hasAdditionalFlags) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - - uint8_t flags { 0 }; - - setSemiNibbleAt(flags, KEY_STATE_START_BIT, _keyState); - - // hand state - bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; - setSemiNibbleAt(flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); - if (isFingerPointing) { - setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT); - } - // faceshift state - if (_headData->_isFaceTrackerConnected) { - setAtBit(flags, IS_FACESHIFT_CONNECTED); - } - // eye tracker state - if (_headData->_isEyeTrackerConnected) { - setAtBit(flags, IS_EYE_TRACKER_CONNECTED); - } - // referential state - if (!parentID.isNull()) { - setAtBit(flags, HAS_REFERENTIAL); - } - data->flags = flags; - destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); - - int numBytes = destinationBuffer - startSection; - _additionalFlagsRateOutbound.increment(numBytes); - } - - if (hasParentInfo) { - auto startSection = destinationBuffer; - auto parentInfo = reinterpret_cast(destinationBuffer); - QByteArray referentialAsBytes = parentID.toRfc4122(); - memcpy(parentInfo->parentUUID, referentialAsBytes.data(), referentialAsBytes.size()); - parentInfo->parentJointIndex = getParentJointIndex(); - destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); - - int numBytes = destinationBuffer - startSection; - _parentInfoRateOutbound.increment(numBytes); - } - - if (hasAvatarLocalPosition) { - auto startSection = destinationBuffer; - auto data = reinterpret_cast(destinationBuffer); - auto localPosition = getLocalPosition(); - data->localPosition[0] = localPosition.x; - data->localPosition[1] = localPosition.y; - data->localPosition[2] = localPosition.z; - destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); - - int numBytes = destinationBuffer - startSection; - _localPositionRateOutbound.increment(numBytes); - } - - // If it is connected, pack up the data - if (hasFaceTrackerInfo) { - auto startSection = destinationBuffer; - auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - - faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; - faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; - faceTrackerInfo->averageLoudness = _headData->_averageLoudness; - faceTrackerInfo->browAudioLift = _headData->_browAudioLift; - faceTrackerInfo->numBlendshapeCoefficients = _headData->_blendshapeCoefficients.size(); - destinationBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - - // followed by a variable number of float coefficients - memcpy(destinationBuffer, _headData->_blendshapeCoefficients.data(), _headData->_blendshapeCoefficients.size() * sizeof(float)); - destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); - - int numBytes = destinationBuffer - startSection; - _faceTrackerRateOutbound.increment(numBytes); - } - - // If it is connected, pack up the data - if (hasJointData) { - auto startSection = destinationBuffer; - QReadLocker readLock(&_jointDataLock); - - // joint rotation data - int numJoints = _jointData.size(); - *destinationBuffer++ = (uint8_t)numJoints; - - unsigned char* validityPosition = destinationBuffer; - unsigned char validity = 0; - int validityBit = 0; - -#ifdef WANT_DEBUG - int rotationSentCount = 0; - unsigned char* beforeRotations = destinationBuffer; -#endif - - if (sentJointDataOut) { - sentJointDataOut->resize(_jointData.size()); // Make sure the destination is resized before using it - } - float minRotationDOT = !distanceAdjust ? AVATAR_MIN_ROTATION_DOT : getDistanceBasedMinRotationDOT(viewerPosition); - - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - - // The dot product for smaller rotations is a smaller number. - // So if the dot() is less than the value, then the rotation is a larger angle of rotation - bool largeEnoughRotation = fabsf(glm::dot(data.rotation, lastSentJointData[i].rotation)) < minRotationDOT; - - if (sendAll || lastSentJointData[i].rotation != data.rotation) { - if (sendAll || !cullSmallChanges || largeEnoughRotation) { - if (data.rotationSet) { - validity |= (1 << validityBit); -#ifdef WANT_DEBUG - rotationSentCount++; -#endif - if (sentJointDataOut) { - auto jointDataOut = *sentJointDataOut; - jointDataOut[i].rotation = data.rotation; - } - - } - } - } - if (++validityBit == BITS_IN_BYTE) { - *destinationBuffer++ = validity; - validityBit = validity = 0; - } - } - if (validityBit != 0) { - *destinationBuffer++ = validity; - } - - validityBit = 0; - validity = *validityPosition++; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - if (validity & (1 << validityBit)) { - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation); - } - if (++validityBit == BITS_IN_BYTE) { - validityBit = 0; - validity = *validityPosition++; - } - } - - - // joint translation data - validityPosition = destinationBuffer; - validity = 0; - validityBit = 0; - -#ifdef WANT_DEBUG - int translationSentCount = 0; - unsigned char* beforeTranslations = destinationBuffer; -#endif - - float minTranslation = !distanceAdjust ? AVATAR_MIN_TRANSLATION : getDistanceBasedMinTranslationDistance(viewerPosition); - - float maxTranslationDimension = 0.0; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - if (sendAll || lastSentJointData[i].translation != data.translation) { - if (sendAll || - !cullSmallChanges || - glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation) { - if (data.translationSet) { - validity |= (1 << validityBit); - #ifdef WANT_DEBUG - translationSentCount++; - #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - - if (sentJointDataOut) { - auto jointDataOut = *sentJointDataOut; - jointDataOut[i].translation = data.translation; - } - - } - } - } - if (++validityBit == BITS_IN_BYTE) { - *destinationBuffer++ = validity; - validityBit = validity = 0; - } - } - - if (validityBit != 0) { - *destinationBuffer++ = validity; - } - - validityBit = 0; - validity = *validityPosition++; - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData[i]; - if (validity & (1 << validityBit)) { - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); - } - if (++validityBit == BITS_IN_BYTE) { - validityBit = 0; - validity = *validityPosition++; - } - } - - // faux joints - Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); - destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), - TRANSLATION_COMPRESSION_RADIX); - Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); - destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); - destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), - TRANSLATION_COMPRESSION_RADIX); - -#ifdef WANT_DEBUG - if (sendAll) { - qCDebug(avatars) << "AvatarData::toByteArray" << cullSmallChanges << sendAll - << "rotations:" << rotationSentCount << "translations:" << translationSentCount - << "largest:" << maxTranslationDimension - << "size:" - << (beforeRotations - startPosition) << "+" - << (beforeTranslations - beforeRotations) << "+" - << (destinationBuffer - beforeTranslations) << "=" - << (destinationBuffer - startPosition); - } -#endif - - int numBytes = destinationBuffer - startSection; - _jointDataRateOutbound.increment(numBytes); - } - - int avatarDataSize = destinationBuffer - startPosition; - return avatarDataByteArray.left(avatarDataSize); +// we want to track outbound data in this case... +QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) { + AvatarDataPacket::HasFlags hasFlagsOut; + auto lastSentTime = _lastToByteArray; + _lastToByteArray = usecTimestampNow(); + return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(), + hasFlagsOut, false, false, glm::vec3(0), nullptr, + &_outboundDataRate); } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const { + AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, + glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); bool sendAll = (dataDetail == SendAllData); @@ -656,7 +261,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent int numBytes = destinationBuffer - startSection; - //_globalPositionRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->globalPositionRate.increment(numBytes); + } } if (hasAvatarBoundingBox) { @@ -674,7 +281,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox); int numBytes = destinationBuffer - startSection; - //_avatarBoundingBoxRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->avatarBoundingBoxRate.increment(numBytes); + } } if (hasAvatarOrientation) { @@ -683,7 +292,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation); int numBytes = destinationBuffer - startSection; - //_avatarOrientationRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->avatarOrientationRate.increment(numBytes); + } } if (hasAvatarScale) { @@ -694,7 +305,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::AvatarScale); int numBytes = destinationBuffer - startSection; - //_avatarScaleRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->avatarScaleRate.increment(numBytes); + } } if (hasLookAtPosition) { @@ -707,7 +320,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::LookAtPosition); int numBytes = destinationBuffer - startSection; - //_lookAtPositionRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->lookAtPositionRate.increment(numBytes); + } } if (hasAudioLoudness) { @@ -717,7 +332,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::AudioLoudness); int numBytes = destinationBuffer - startSection; - //_audioLoudnessRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->audioLoudnessRate.increment(numBytes); + } } if (hasSensorToWorldMatrix) { @@ -733,7 +350,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::SensorToWorldMatrix); int numBytes = destinationBuffer - startSection; - //_sensorToWorldRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->sensorToWorldRate.increment(numBytes); + } } if (hasAdditionalFlags) { @@ -766,7 +385,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); int numBytes = destinationBuffer - startSection; - //_additionalFlagsRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->additionalFlagsRate.increment(numBytes); + } } if (hasParentInfo) { @@ -778,7 +399,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::ParentInfo); int numBytes = destinationBuffer - startSection; - //_parentInfoRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->parentInfoRate.increment(numBytes); + } } if (hasAvatarLocalPosition) { @@ -791,7 +414,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += sizeof(AvatarDataPacket::AvatarLocalPosition); int numBytes = destinationBuffer - startSection; - //_localPositionRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->localPositionRate.increment(numBytes); + } } // If it is connected, pack up the data @@ -811,7 +436,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent destinationBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); int numBytes = destinationBuffer - startSection; - //_faceTrackerRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->faceTrackerRate.increment(numBytes); + } } // If it is connected, pack up the data @@ -966,7 +593,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent #endif int numBytes = destinationBuffer - startSection; - //_jointDataRateOutbound.increment(numBytes); + if (outboundDataRateOut) { + outboundDataRateOut->jointDataRate.increment(numBytes); + } } int avatarDataSize = destinationBuffer - startPosition; @@ -1451,29 +1080,29 @@ float AvatarData::getDataRate(const QString& rateName) const { } else if (rateName == "jointData") { return _jointDataRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "globalPositionOutbound") { - return _globalPositionRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.globalPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "localPositionOutbound") { - return _localPositionRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.localPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "avatarBoundingBoxOutbound") { - return _avatarBoundingBoxRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.avatarBoundingBoxRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "avatarOrientationOutbound") { - return _avatarOrientationRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.avatarOrientationRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "avatarScaleOutbound") { - return _avatarScaleRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.avatarScaleRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "lookAtPositionOutbound") { - return _lookAtPositionRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.lookAtPositionRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "audioLoudnessOutbound") { - return _audioLoudnessRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.audioLoudnessRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "sensorToWorkMatrixOutbound") { - return _sensorToWorldRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.sensorToWorldRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "additionalFlagsOutbound") { - return _additionalFlagsRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.additionalFlagsRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "parentInfoOutbound") { - return _parentInfoRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.parentInfoRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "faceTrackerOutbound") { - return _faceTrackerRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.faceTrackerRate.rate() / BYTES_PER_KILOBIT; } else if (rateName == "jointDataOutbound") { - return _jointDataRateOutbound.rate() / BYTES_PER_KILOBIT; + return _outboundDataRate.jointDataRate.rate() / BYTES_PER_KILOBIT; } return 0.0f; } @@ -1807,7 +1436,7 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide } static const QUrl emptyURL(""); -const QUrl& AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) { +QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { // We don't put file urls on the wire, but instead convert to empty. return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; } @@ -1845,7 +1474,7 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC } } -QByteArray AvatarData::identityByteArray() { +QByteArray AvatarData::identityByteArray() const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); @@ -2008,13 +1637,7 @@ void AvatarData::sendAvatarDataPacket() { bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); auto dataDetail = cullSmallData ? SendAllData : CullSmallData; - QVector lastSentJointData; - { - QReadLocker readLock(&_jointDataLock); - _lastSentJointData.resize(_jointData.size()); - lastSentJointData = _lastSentJointData; - } - QByteArray avatarByteArray = toByteArray(dataDetail, 0, lastSentJointData); + QByteArray avatarByteArray = toByteArray(dataDetail); doneEncoding(cullSmallData); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 64c0e11577..14348b0622 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -288,6 +288,23 @@ class AttachmentData; class Transform; using TransformPointer = std::shared_ptr; +class AvatarDataRate { +public: + RateCounter<> globalPositionRate; + RateCounter<> localPositionRate; + RateCounter<> avatarBoundingBoxRate; + RateCounter<> avatarOrientationRate; + RateCounter<> avatarScaleRate; + RateCounter<> lookAtPositionRate; + RateCounter<> audioLoudnessRate; + RateCounter<> sensorToWorldRate; + RateCounter<> additionalFlagsRate; + RateCounter<> parentInfoRate; + RateCounter<> faceTrackerRate; + RateCounter<> jointDataRate; +}; + + class AvatarData : public QObject, public SpatiallyNestable { Q_OBJECT @@ -351,12 +368,12 @@ public: SendAllData } AvatarDataDetail; - virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - bool distanceAdjust = false, glm::vec3 viewerPosition = glm::vec3(0), QVector* sentJointDataOut = nullptr); + virtual QByteArray toByteArray(AvatarDataDetail dataDetail); // FIXME virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, - AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut) const; + AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, + QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const; virtual void doneEncoding(bool cullSmallChanges); @@ -497,7 +514,7 @@ public: // displayNameChanged returns true if displayName has changed, false otherwise. void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged); - QByteArray identityByteArray(); + QByteArray identityByteArray() const; const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } @@ -626,7 +643,7 @@ protected: QVector _attachmentData; QString _displayName; QString _sessionDisplayName { }; - const QUrl& cannonicalSkeletonModelURL(const QUrl& empty); + QUrl cannonicalSkeletonModelURL(const QUrl& empty) const; float _displayNameTargetAlpha; float _displayNameAlpha; @@ -697,18 +714,7 @@ protected: RateCounter<> _jointDataUpdateRate; // Some rate data for outgoing data - RateCounter<> _globalPositionRateOutbound; - RateCounter<> _localPositionRateOutbound; - RateCounter<> _avatarBoundingBoxRateOutbound; - RateCounter<> _avatarOrientationRateOutbound; - RateCounter<> _avatarScaleRateOutbound; - RateCounter<> _lookAtPositionRateOutbound; - RateCounter<> _audioLoudnessRateOutbound; - RateCounter<> _sensorToWorldRateOutbound; - RateCounter<> _additionalFlagsRateOutbound; - RateCounter<> _parentInfoRateOutbound; - RateCounter<> _faceTrackerRateOutbound; - RateCounter<> _jointDataRateOutbound; + AvatarDataRate _outboundDataRate; glm::vec3 _globalBoundingBoxDimensions; glm::vec3 _globalBoundingBoxOffset; From c2c843d841581d6cbc3af66ed2fa01e0d29eb12f Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 21:05:02 -0800 Subject: [PATCH 25/35] fix mac warnings --- assignment-client/src/avatars/AvatarMixer.cpp | 69 ++++++++----------- .../src/avatars/AvatarMixerSlave.cpp | 1 - 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 51f6067e6a..636ef3237f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -69,7 +69,6 @@ AvatarMixer::~AvatarMixer() { // An 80% chance of sending a identity packet within a 5 second interval. // assuming 60 htz update rate. -const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { QByteArray individualData = nodeData->getAvatar().identityByteArray(); @@ -142,6 +141,7 @@ void AvatarMixer::start() { // calculates last frame duration and sleeps for the remainder of the target amount auto frameDuration = timeFrame(frameTimestamp); + Q_UNUSED(frameDuration); int lockWait, nodeTransform, functor; @@ -248,16 +248,6 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { } } -static void avatarLoops(); - -// only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame -// Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times -// per second. -// This value should be a power of two for performance purposes, as the mixer performs a modulo operation every frame -// to determine whether the extra data should be sent. -static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; - - // FIXME -- this is dead code... it needs to be removed... // this "throttle" logic is the old approach. need to consider some // reasonable throttle approach in new multi-core design @@ -484,18 +474,19 @@ void AvatarMixer::sendStatsPacket() { int tightLoopFrames = _numTightLoopFrames; int tenTimesPerFrame = tightLoopFrames * 10; #define TIGHT_LOOP_STAT(x) (x > tenTimesPerFrame) ? x / tightLoopFrames : ((float)x / (float)tightLoopFrames); + #define TIGHT_LOOP_STAT_UINT64(x) (x > (quint64)tenTimesPerFrame) ? x / tightLoopFrames : ((float)x / (float)tightLoopFrames); QJsonObject singleCoreTasks; - singleCoreTasks["processEvents"] = TIGHT_LOOP_STAT(_processEventsElapsedTime); - singleCoreTasks["queueIncomingPacket"] = TIGHT_LOOP_STAT(_queueIncomingPacketElapsedTime); + singleCoreTasks["processEvents"] = TIGHT_LOOP_STAT_UINT64(_processEventsElapsedTime); + singleCoreTasks["queueIncomingPacket"] = TIGHT_LOOP_STAT_UINT64(_queueIncomingPacketElapsedTime); QJsonObject incomingPacketStats; - incomingPacketStats["handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT(_handleAvatarIdentityPacketElapsedTime); - incomingPacketStats["handleKillAvatarPacket"] = TIGHT_LOOP_STAT(_handleKillAvatarPacketElapsedTime); - incomingPacketStats["handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleNodeIgnoreRequestPacketElapsedTime); - incomingPacketStats["handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT(_handleRadiusIgnoreRequestPacketElapsedTime); - incomingPacketStats["handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT(_handleRequestsDomainListDataPacketElapsedTime); - incomingPacketStats["handleViewFrustumPacket"] = TIGHT_LOOP_STAT(_handleViewFrustumPacketElapsedTime); + incomingPacketStats["handleAvatarIdentityPacket"] = TIGHT_LOOP_STAT_UINT64(_handleAvatarIdentityPacketElapsedTime); + incomingPacketStats["handleKillAvatarPacket"] = TIGHT_LOOP_STAT_UINT64(_handleKillAvatarPacketElapsedTime); + incomingPacketStats["handleNodeIgnoreRequestPacket"] = TIGHT_LOOP_STAT_UINT64(_handleNodeIgnoreRequestPacketElapsedTime); + incomingPacketStats["handleRadiusIgnoreRequestPacket"] = TIGHT_LOOP_STAT_UINT64(_handleRadiusIgnoreRequestPacketElapsedTime); + incomingPacketStats["handleRequestsDomainListDataPacket"] = TIGHT_LOOP_STAT_UINT64(_handleRequestsDomainListDataPacketElapsedTime); + incomingPacketStats["handleViewFrustumPacket"] = TIGHT_LOOP_STAT_UINT64(_handleViewFrustumPacketElapsedTime); singleCoreTasks["incoming_packets"] = incomingPacketStats; singleCoreTasks["sendStats"] = (float)_sendStatsElapsedTime; @@ -505,22 +496,22 @@ void AvatarMixer::sendStatsPacket() { QJsonObject parallelTasks; QJsonObject processQueuedAvatarDataPacketsStats; - processQueuedAvatarDataPacketsStats["1_total"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsElapsedTime); - processQueuedAvatarDataPacketsStats["2_lockWait"] = TIGHT_LOOP_STAT(_processQueuedAvatarDataPacketsLockWaitElapsedTime); + processQueuedAvatarDataPacketsStats["1_total"] = TIGHT_LOOP_STAT_UINT64(_processQueuedAvatarDataPacketsElapsedTime); + processQueuedAvatarDataPacketsStats["2_lockWait"] = TIGHT_LOOP_STAT_UINT64(_processQueuedAvatarDataPacketsLockWaitElapsedTime); parallelTasks["processQueuedAvatarDataPackets"] = processQueuedAvatarDataPacketsStats; QJsonObject broadcastAvatarDataStats; - broadcastAvatarDataStats["1_total"] = TIGHT_LOOP_STAT(_broadcastAvatarDataElapsedTime); - broadcastAvatarDataStats["2_innner"] = TIGHT_LOOP_STAT(_broadcastAvatarDataInner); - broadcastAvatarDataStats["3_lockWait"] = TIGHT_LOOP_STAT(_broadcastAvatarDataLockWait); - broadcastAvatarDataStats["4_NodeTransform"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeTransform); - broadcastAvatarDataStats["5_Functor"] = TIGHT_LOOP_STAT(_broadcastAvatarDataNodeFunctor); + broadcastAvatarDataStats["1_total"] = TIGHT_LOOP_STAT_UINT64(_broadcastAvatarDataElapsedTime); + broadcastAvatarDataStats["2_innner"] = TIGHT_LOOP_STAT_UINT64(_broadcastAvatarDataInner); + broadcastAvatarDataStats["3_lockWait"] = TIGHT_LOOP_STAT_UINT64(_broadcastAvatarDataLockWait); + broadcastAvatarDataStats["4_NodeTransform"] = TIGHT_LOOP_STAT_UINT64(_broadcastAvatarDataNodeTransform); + broadcastAvatarDataStats["5_Functor"] = TIGHT_LOOP_STAT_UINT64(_broadcastAvatarDataNodeFunctor); parallelTasks["broadcastAvatarData"] = broadcastAvatarDataStats; QJsonObject displayNameManagementStats; - displayNameManagementStats["1_total"] = TIGHT_LOOP_STAT(_displayNameManagementElapsedTime); + displayNameManagementStats["1_total"] = TIGHT_LOOP_STAT_UINT64(_displayNameManagementElapsedTime); parallelTasks["displayNameManagement"] = displayNameManagementStats; statsObject["parallelTasks"] = parallelTasks; @@ -538,12 +529,12 @@ void AvatarMixer::sendStatsPacket() { slaveObject["numPacketsReceived"] = TIGHT_LOOP_STAT(stats.packetsProcessed); slaveObject["numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent); - slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(stats.processIncomingPacketsElapsedTime); - slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(stats.ignoreCalculationElapsedTime); - slaveObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT(stats.toByteArrayElapsedTime); - slaveObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT(stats.avatarDataPackingElapsedTime); - slaveObject["timing_5_packetSending"] = TIGHT_LOOP_STAT(stats.packetSendingElapsedTime); - slaveObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT(stats.jobElapsedTime); + slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(stats.processIncomingPacketsElapsedTime); + slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(stats.ignoreCalculationElapsedTime); + slaveObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT_UINT64(stats.toByteArrayElapsedTime); + slaveObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT_UINT64(stats.avatarDataPackingElapsedTime); + slaveObject["timing_5_packetSending"] = TIGHT_LOOP_STAT_UINT64(stats.packetSendingElapsedTime); + slaveObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(stats.jobElapsedTime); slavesObject[QString::number(slaveNumber)] = slaveObject; slaveNumber++; @@ -557,12 +548,12 @@ void AvatarMixer::sendStatsPacket() { slavesAggregatObject["numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); slavesAggregatObject["numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent); - slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT(aggregateStats.processIncomingPacketsElapsedTime); - slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT(aggregateStats.ignoreCalculationElapsedTime); - slavesAggregatObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT(aggregateStats.toByteArrayElapsedTime); - slavesAggregatObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT(aggregateStats.avatarDataPackingElapsedTime); - slavesAggregatObject["timing_5_packetSending"] = TIGHT_LOOP_STAT(aggregateStats.packetSendingElapsedTime); - slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT(aggregateStats.jobElapsedTime); + slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime); + slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime); + slavesAggregatObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.toByteArrayElapsedTime); + slavesAggregatObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.avatarDataPackingElapsedTime); + slavesAggregatObject["timing_5_packetSending"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.packetSendingElapsedTime); + slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.jobElapsedTime); statsObject["slaves_aggregate"] = slavesAggregatObject; statsObject["slaves_individual"] = slavesObject; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 2a7f9a4d8d..1ddefd8813 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -56,7 +56,6 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { #include static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; -static const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / (float)AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND) * 1000; // only send extra avatar data (avatars out of view, ignored) every Nth AvatarData frame // Extra avatar data will be sent (AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND/EXTRA_AVATAR_DATA_FRAME_RATIO) times From 4570b3439d4ad0c601c74bf2b162754a44d7246a Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 21:38:30 -0800 Subject: [PATCH 26/35] some cleanup --- assignment-client/src/avatars/AvatarMixer.cpp | 33 +++++++++-------- .../src/avatars/AvatarMixerSlave.cpp | 36 ++++++------------- .../src/avatars/AvatarMixerSlave.h | 4 +++ .../src/avatars/AvatarMixerSlavePool.cpp | 10 ++++-- .../src/avatars/AvatarMixerSlavePool.h | 3 +- 5 files changed, 42 insertions(+), 44 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 636ef3237f..6b5f84960b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -120,20 +120,25 @@ void AvatarMixer::start() { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // DO THIS FIRST!!!!!!!! + // WORK ITEMS... // // DONE --- 1) only sleep for remainder // DONE --- 2) clean up stats, add slave stats // DONE --- 3) out of view??? is it broken? - verified - it's working - // 4) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. // DONE --- 4a) hack to not send face data mostly seems to work... + // DONE --- 5) fix two different versions of toByteArray() + // DONE --- 7) audit the locking and side-effects to node, otherNode, and nodeData + // DONE --- 8) delete dead code from mixer (now that it's in slave) + // DONE --- 10) FIXME on sending identity packets + // DONE --- 12) FIXME _maxKbpsPerNode + // DONE --- 11) FIXME ++_sumListeners; + // + // 4) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. // 4b) some kind of a better approach to handling otherAvatar.toByteArray() for content that is larger than MTU - // 5) fix two different versions of toByteArray() - // 6) throttling?? - // 7) audit the locking and side-effects to node, otherNode, and nodeData - // 8) delete dead code from mixer (now that it's in slave) + // 6) CPU throttling?? // 9) better stats in the nodes: // how many avatars are actually "in view" for the avtar in question (even if they are over bandwidth budget) + // 13) FIXME -- otherNodeData->incrementNumOutOfOrderSends(); // // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -157,8 +162,6 @@ void AvatarMixer::start() { }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _processQueuedAvatarDataPacketsElapsedTime += (end - start); - - //qDebug() << "PROCESS PACKETS... " << "lockWait:" << lockWait << "nodeTransform:" << nodeTransform << "functor:" << functor; } // process pending display names... this doesn't currently run on multiple threads, because it @@ -168,23 +171,19 @@ void AvatarMixer::start() { nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { std::for_each(cbegin, cend, [&](const SharedNodePointer& node) { manageDisplayName(node); + ++_sumListeners; }); }, &lockWait, &nodeTransform, &functor); auto end = usecTimestampNow(); _displayNameManagementElapsedTime += (end - start); - - //qDebug() << "PROCESS PACKETS... " << "lockWait:" << lockWait << "nodeTransform:" << nodeTransform << "functor:" << functor; } // this is where we need to put the real work... { - // for now, call the single threaded version - //broadcastAvatarData(); - auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { auto start = usecTimestampNow(); - _slavePool.broadcastAvatarData(cbegin, cend); + _slavePool.broadcastAvatarData(cbegin, cend, _lastFrameTimestamp, _maxKbpsPerNode); auto end = usecTimestampNow(); _broadcastAvatarDataInner += (end - start); }, &lockWait, &nodeTransform, &functor); @@ -210,6 +209,9 @@ void AvatarMixer::start() { auto end = usecTimestampNow(); _processEventsElapsedTime += (end - start); } + + _lastFrameTimestamp = frameTimestamp; + } } @@ -461,7 +463,6 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; - //statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; //statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; statsObject["broadcast_loop_rate"] = _loopRate.rate(); @@ -476,6 +477,8 @@ void AvatarMixer::sendStatsPacket() { #define TIGHT_LOOP_STAT(x) (x > tenTimesPerFrame) ? x / tightLoopFrames : ((float)x / (float)tightLoopFrames); #define TIGHT_LOOP_STAT_UINT64(x) (x > (quint64)tenTimesPerFrame) ? x / tightLoopFrames : ((float)x / (float)tightLoopFrames); + statsObject["average_listeners_last_second"] = TIGHT_LOOP_STAT(_sumListeners); + QJsonObject singleCoreTasks; singleCoreTasks["processEvents"] = TIGHT_LOOP_STAT_UINT64(_processEventsElapsedTime); singleCoreTasks["queueIncomingPacket"] = TIGHT_LOOP_STAT_UINT64(_queueIncomingPacketElapsedTime); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 1ddefd8813..fb67cbf64e 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -35,6 +35,15 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _end = end; } +void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end, + p_high_resolution_clock::time_point lastFrameTimestamp, + float maxKbpsPerNode) { + _begin = begin; + _end = end; + _lastFrameTimestamp = lastFrameTimestamp; + _maxKbpsPerNode = maxKbpsPerNode; +} + void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) { stats = _stats; _stats.reset(); @@ -71,8 +80,6 @@ const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // FIXME... this is wrong void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 start = usecTimestampNow(); - //qDebug() << __FUNCTION__ << "node:" << node; - auto nodeList = DependencyManager::get(); // setup for distributed random floating point values @@ -82,13 +89,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) { AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); - //MutexTryLocker lock(nodeData->getMutex()); - - // FIXME???? - //if (!lock.isLocked()) { - //qDebug() << __FUNCTION__ << "unable to lock... node:" << node << " would BAIL???... line:" << __LINE__; - //return; - //} // FIXME -- mixer data // ++_sumListeners; @@ -143,8 +143,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // get the current full rate distance so we can work with it float currentFullRateDistance = nodeData->getFullRateDistance(); - // FIXME -- mixer data - float _maxKbpsPerNode = 5000.0f; if (avatarDataRateLastSecond > _maxKbpsPerNode) { // is the FRD greater than the farthest avatar? @@ -176,7 +174,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // this is an AGENT we have received head data from // send back a packet with other active node data to this node std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode; bool shouldConsider = false; quint64 startIgnoreCalculation = usecTimestampNow(); @@ -236,9 +233,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); } - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << "shouldConsider:" << shouldConsider; - - if (shouldConsider) { quint64 startAvatarDataPacking = usecTimestampNow(); @@ -247,24 +241,19 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); // make sure we send out identity packets to and from new arrivals. - // FIXME this is where our crash was on friday!! - // this is getting called on multiple threads... needs mutex of better solution bool forceSend = !nodeData->checkAndSetHasReceivedFirstPacketsFrom(otherNode->getUUID()); + // FIXME - this clause seems suspicious "... || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp ..." if (otherNodeData->getIdentityChangeTimestamp().time_since_epoch().count() > 0 && (forceSend - //|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp // FIXME - mixer data + || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp || distribution(generator) < IDENTITY_SEND_PROBABILITY)) { - // FIXME --- used to be.../ mixer data dependency - //sendIdentityPacket(otherNodeData, node); - QByteArray individualData = otherNodeData->getConstAvatarData()->identityByteArray(); auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNodeData->getNodeID().toRfc4122()); identityPacket->write(individualData); DependencyManager::get()->sendPacket(std::move(identityPacket), *node); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node << "otherNode:" << otherNode << " sending itentity packet for otherNode to node..."; } const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); @@ -285,7 +274,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; shouldConsider = false; } @@ -308,7 +296,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; shouldConsider = false; } 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 @@ -334,7 +321,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - //qDebug() << __FUNCTION__ << "inner loop, node:" << node->getUUID() << "otherNode:" << otherNode->getUUID() << " BAILING... line:" << __LINE__; shouldConsider = false; } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index b693356349..9382fcfeda 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -62,6 +62,7 @@ public: using ConstIter = NodeList::const_iterator; void configure(ConstIter begin, ConstIter end); + void configureBroadcast(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode); void processIncomingPackets(const SharedNodePointer& node); void broadcastAvatarData(const SharedNodePointer& node); @@ -73,6 +74,9 @@ private: ConstIter _begin; ConstIter _end; + p_high_resolution_clock::time_point _lastFrameTimestamp; + float _maxKbpsPerNode { 0.0f }; + AvatarMixerSlaveStats _stats; }; diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index f410bb566d..c0dcf9cbba 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -69,13 +69,17 @@ static AvatarMixerSlave slave; void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end) { _function = &AvatarMixerSlave::processIncomingPackets; - _configure = [&](AvatarMixerSlave& slave) { slave.configure(begin, end); }; + _configure = [&](AvatarMixerSlave& slave) { + slave.configure(begin, end); + }; run(begin, end); } -void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end) { +void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode) { _function = &AvatarMixerSlave::broadcastAvatarData; - _configure = [&](AvatarMixerSlave& slave) { slave.configure(begin, end); }; + _configure = [&](AvatarMixerSlave& slave) { + slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode); + }; run(begin, end); } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index 0a689b35ed..e54681401d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -65,7 +65,8 @@ public: // Jobs the slave pool can do... void processIncomingPackets(ConstIter begin, ConstIter end); - void broadcastAvatarData(ConstIter begin, ConstIter end); + void broadcastAvatarData(ConstIter begin, ConstIter end, + p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode); // iterate over all slaves void each(std::function functor); From d23343080926e04e402b56f0af70ba6d82083977 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 21:58:35 -0800 Subject: [PATCH 27/35] fix warnings --- assignment-client/src/Agent.cpp | 2 +- assignment-client/src/avatars/AvatarMixer.cpp | 1 + assignment-client/src/avatars/ScriptableAvatar.cpp | 4 ++-- assignment-client/src/avatars/ScriptableAvatar.h | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index a458719346..355e47be46 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -546,7 +546,7 @@ void Agent::processAgentAvatar() { auto scriptedAvatar = DependencyManager::get(); AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData; - QByteArray avatarByteArray = scriptedAvatar->toByteArray(dataDetail); + QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail); scriptedAvatar->doneEncoding(true); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 6b5f84960b..6c04ca8799 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -132,6 +132,7 @@ void AvatarMixer::start() { // DONE --- 10) FIXME on sending identity packets // DONE --- 12) FIXME _maxKbpsPerNode // DONE --- 11) FIXME ++_sumListeners; + // DONE --- 14) fix toByteArray() virtual hiding!!! // // 4) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. // 4b) some kind of a better approach to handling otherAvatar.toByteArray() for content that is larger than MTU diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index cdda070da2..516bf7a1e3 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -14,9 +14,9 @@ #include #include "ScriptableAvatar.h" -QByteArray ScriptableAvatar::toByteArray(AvatarDataDetail dataDetail) { +QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { _globalPosition = getPosition(); - return AvatarData::toByteArray(dataDetail); + return AvatarData::toByteArrayStateful(dataDetail); } diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index b6804da43d..1028912e55 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -28,7 +28,7 @@ public: Q_INVOKABLE AnimationDetails getAnimationDetails(); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; - virtual QByteArray toByteArray(AvatarDataDetail dataDetail) override; + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; private slots: From 42d916a719b41def5b237de556bd00714e19a763 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 21:58:48 -0800 Subject: [PATCH 28/35] fix warnings --- interface/src/avatar/MyAvatar.cpp | 6 +++--- interface/src/avatar/MyAvatar.h | 2 +- libraries/avatars/src/AvatarData.cpp | 4 ++-- libraries/avatars/src/AvatarData.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 21b75e4e71..fa3ddb6bbd 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -227,7 +227,7 @@ void MyAvatar::simulateAttachments(float deltaTime) { // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() } -QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail) { +QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) { CameraMode mode = qApp->getCamera()->getMode(); _globalPosition = getPosition(); _globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius(); @@ -238,12 +238,12 @@ QByteArray MyAvatar::toByteArray(AvatarDataDetail dataDetail) { // fake the avatar position that is sent up to the AvatarMixer glm::vec3 oldPosition = getPosition(); setPosition(getSkeletonPosition()); - QByteArray array = AvatarData::toByteArray(dataDetail); + QByteArray array = AvatarData::toByteArrayStateful(dataDetail); // copy the correct position back setPosition(oldPosition); return array; } - return AvatarData::toByteArray(dataDetail); + return AvatarData::toByteArrayStateful(dataDetail); } void MyAvatar::centerBody() { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 982f0505ff..d94d1088e2 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -338,7 +338,7 @@ private: glm::quat getWorldBodyOrientation() const; - virtual QByteArray toByteArray(AvatarDataDetail dataDetail) override; + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override; void simulate(float deltaTime); void updateFromTrackers(float deltaTime); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f48b31881a..2263964e8f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -169,7 +169,7 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio // we want to track outbound data in this case... -QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail) { +QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail) { AvatarDataPacket::HasFlags hasFlagsOut; auto lastSentTime = _lastToByteArray; _lastToByteArray = usecTimestampNow(); @@ -1637,7 +1637,7 @@ void AvatarData::sendAvatarDataPacket() { bool cullSmallData = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO); auto dataDetail = cullSmallData ? SendAllData : CullSmallData; - QByteArray avatarByteArray = toByteArray(dataDetail); + QByteArray avatarByteArray = toByteArrayStateful(dataDetail); doneEncoding(cullSmallData); static AvatarDataSequenceNumber sequenceNumber = 0; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 14348b0622..2e034073b3 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -368,7 +368,7 @@ public: SendAllData } AvatarDataDetail; - virtual QByteArray toByteArray(AvatarDataDetail dataDetail); + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail); // FIXME virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, From 71af81851efad3e8dc80b79ab7421d18ec4e9c73 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Fri, 17 Feb 2017 22:20:32 -0800 Subject: [PATCH 29/35] migrate to new style throttling --- assignment-client/src/avatars/AvatarMixer.cpp | 148 ++++++++---------- assignment-client/src/avatars/AvatarMixer.h | 9 +- 2 files changed, 66 insertions(+), 91 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 6c04ca8799..da0289a08f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -111,43 +111,43 @@ void AvatarMixer::start() { auto nodeList = DependencyManager::get(); + unsigned int frame = 1; auto frameTimestamp = p_high_resolution_clock::now(); while (!_isFinished) { - _numTightLoopFrames++; - _loopRate.increment(); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // WORK ITEMS... // - // DONE --- 1) only sleep for remainder - // DONE --- 2) clean up stats, add slave stats - // DONE --- 3) out of view??? is it broken? - verified - it's working - // DONE --- 4a) hack to not send face data mostly seems to work... - // DONE --- 5) fix two different versions of toByteArray() - // DONE --- 7) audit the locking and side-effects to node, otherNode, and nodeData - // DONE --- 8) delete dead code from mixer (now that it's in slave) - // DONE --- 10) FIXME on sending identity packets - // DONE --- 12) FIXME _maxKbpsPerNode - // DONE --- 11) FIXME ++_sumListeners; - // DONE --- 14) fix toByteArray() virtual hiding!!! + // DONE --- only sleep for remainder + // DONE --- clean up stats, add slave stats + // DONE --- out of view??? is it broken? - verified - it's working + // DONE --- hack to not send face data mostly seems to work... + // DONE --- fix two different versions of toByteArray() + // DONE --- audit the locking and side-effects to node, otherNode, and nodeData + // DONE --- delete dead code from mixer (now that it's in slave) + // DONE --- FIXME on sending identity packets + // DONE --- FIXME _maxKbpsPerNode + // DONE --- FIXME ++_sumListeners; + // DONE --- fix toByteArray() virtual hiding!!! // - // 4) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. - // 4b) some kind of a better approach to handling otherAvatar.toByteArray() for content that is larger than MTU - // 6) CPU throttling?? - // 9) better stats in the nodes: + // 1) CPU throttling - now we're calculating it (like audio mixer, how to use it???) + // + // 2) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. + // 2b) some kind of a better approach to handling otherAvatar.toByteArray() for content that is larger than MTU + // 3) better stats in the nodes: // how many avatars are actually "in view" for the avtar in question (even if they are over bandwidth budget) - // 13) FIXME -- otherNodeData->incrementNumOutOfOrderSends(); - // + // 4) FIXME -- otherNodeData->incrementNumOutOfOrderSends(); + // 5) average_identity_packets_per_frame??? // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // calculates last frame duration and sleeps for the remainder of the target amount auto frameDuration = timeFrame(frameTimestamp); - Q_UNUSED(frameDuration); + throttle(frameDuration, frame); int lockWait, nodeTransform, functor; @@ -196,6 +196,9 @@ void AvatarMixer::start() { _broadcastAvatarDataNodeFunctor += functor; } + ++frame; + ++_numTightLoopFrames; + _loopRate.increment(); // play nice with qt event-looping { @@ -251,81 +254,52 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { } } -// FIXME -- this is dead code... it needs to be removed... -// this "throttle" logic is the old approach. need to consider some -// reasonable throttle approach in new multi-core design -void AvatarMixer::broadcastAvatarData() { - int idleTime = AVATAR_DATA_SEND_INTERVAL_MSECS; +void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { + // throttle using a modified proportional-integral controller + const float FRAME_TIME = USECS_PER_SECOND / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; + float mixRatio = duration.count() / FRAME_TIME; - if (_lastFrameTimestamp.time_since_epoch().count() > 0) { - auto idleDuration = p_high_resolution_clock::now() - _lastFrameTimestamp; - idleTime = std::chrono::duration_cast(idleDuration).count(); - } + // constants are determined based on a "regular" 16-CPU EC2 server - const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; - const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; + // target different mix and backoff ratios (they also have different backoff rates) + // this is to prevent oscillation, and encourage throttling to find a steady state + const float TARGET = 0.9f; + // on a "regular" machine with 100 avatars, this is the largest value where + // - overthrottling can be recovered + // - oscillations will not occur after the recovery + const float BACKOFF_TARGET = 0.44f; - const float RATIO_BACK_OFF = 0.02f; + // the mixer is known to struggle at about 80 on a "regular" machine + // so throttle 2/80 the streams to ensure smooth audio (throttling is linear) + const float THROTTLE_RATE = 2 / 80.0f; + const float BACKOFF_RATE = THROTTLE_RATE / 4; - const int TRAILING_AVERAGE_FRAMES = 100; - int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; + // recovery should be bounded so that large changes in user count is a tolerable experience + // throttling is linear, so most cases will not need a full recovery + const int RECOVERY_TIME = 180; - const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; + // weight more recent frames to determine if throttling is necessary, + const int TRAILING_FRAMES = (int)(100 * RECOVERY_TIME * BACKOFF_RATE); + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; + _trailingMixRatio = PREVIOUS_FRAMES_RATIO * _trailingMixRatio + CURRENT_FRAME_RATIO * mixRatio; - // NOTE: The following code calculates the _performanceThrottlingRatio based on how much the avatar-mixer was - // able to sleep. This will eventually be used to ask for an additional avatar-mixer to help out. Currently the value - // is unused as it is assumed this should not be hit before the avatar-mixer hits the desired bandwidth limit per client. - // It is reported in the domain-server stats for the avatar-mixer. - - _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) - + (idleTime * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_MSECS); - - float lastCutoffRatio = _performanceThrottlingRatio; - bool hasRatioChanged = false; - - if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { - if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { - // we're struggling - change our performance throttling ratio - _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); - - qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" - << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; - hasRatioChanged = true; - } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { - // we've recovered and can back off the performance throttling - _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; - - if (_performanceThrottlingRatio < 0) { - _performanceThrottlingRatio = 0; - } - - qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" - << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; - hasRatioChanged = true; + if (frame % TRAILING_FRAMES == 0) { + if (_trailingMixRatio > TARGET) { + int proportionalTerm = 1 + (_trailingMixRatio - TARGET) / 0.1f; + _throttlingRatio += THROTTLE_RATE * proportionalTerm; + _throttlingRatio = std::min(_throttlingRatio, 1.0f); + qDebug("avatar-mixer is struggling (%f mix/sleep) - throttling %f of streams", + (double)_trailingMixRatio, (double)_throttlingRatio); } - - if (hasRatioChanged) { - framesSinceCutoffEvent = 0; + else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) { + int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f; + _throttlingRatio -= BACKOFF_RATE * proportionalTerm; + _throttlingRatio = std::max(_throttlingRatio, 0.0f); + qDebug("avatar-mixer is recovering (%f mix/sleep) - throttling %f of streams", + (double)_trailingMixRatio, (double)_throttlingRatio); } } - - if (!hasRatioChanged) { - ++framesSinceCutoffEvent; - } - - _lastFrameTimestamp = p_high_resolution_clock::now(); - -#ifdef WANT_DEBUG - auto sinceLastDebug = p_high_resolution_clock::now() - _lastDebugMessage; - auto sinceLastDebugUsecs = std::chrono::duration_cast(sinceLastDebug).count(); - quint64 DEBUG_INTERVAL = USECS_PER_SECOND * 5; - - if (sinceLastDebugUsecs > DEBUG_INTERVAL) { - qDebug() << "broadcast rate:" << _broadcastRate.rate() << "hz"; - _lastDebugMessage = p_high_resolution_clock::now(); - } -#endif } void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { @@ -469,8 +443,8 @@ void AvatarMixer::sendStatsPacket() { statsObject["broadcast_loop_rate"] = _loopRate.rate(); statsObject["threads"] = _slavePool.numThreads(); - statsObject["throttling_1_trailing_sleep_percentage"] = _trailingSleepRatio * 100; - statsObject["throttling_2_performance_ratio"] = _performanceThrottlingRatio; + statsObject["trailing_mix_ratio"] = _trailingMixRatio; + statsObject["throttling_ratio"] = _throttlingRatio; // this things all occur on the frequency of the tight loop int tightLoopFrames = _numTightLoopFrames; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 7a79ec1d57..f03a47dbd8 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -53,9 +53,8 @@ private slots: private: AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node); std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp); + void throttle(std::chrono::microseconds duration, int frame); - - void broadcastAvatarData(); void parseDomainServerSettings(const QJsonObject& domainSettings); void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); @@ -63,8 +62,10 @@ private: p_high_resolution_clock::time_point _lastFrameTimestamp; - float _trailingSleepRatio { 1.0f }; - float _performanceThrottlingRatio { 0.0f }; + // FIXME - new throttling - use these values somehow + float _trailingMixRatio { 0.0f }; + float _throttlingRatio { 0.0f }; + int _sumListeners { 0 }; int _numStatFrames { 0 }; From 66a6666b5298738c2d1b6e6c1af8301725db1c58 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 18 Feb 2017 12:29:01 -0800 Subject: [PATCH 30/35] more cleanup, improved stats, port throttling --- assignment-client/src/avatars/AvatarMixer.cpp | 83 ++++++++++--------- assignment-client/src/avatars/AvatarMixer.h | 1 + .../src/avatars/AvatarMixerClientData.cpp | 1 - .../src/avatars/AvatarMixerSlave.cpp | 57 +++++++++---- .../src/avatars/AvatarMixerSlave.h | 25 +++++- .../src/avatars/AvatarMixerSlavePool.cpp | 6 +- .../src/avatars/AvatarMixerSlavePool.h | 2 +- libraries/avatars/src/AvatarData.h | 1 - 8 files changed, 114 insertions(+), 62 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index da0289a08f..8ba7181e9c 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -9,6 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WORK ITEMS... +// +// 1) FIXME in AvatarMixerSlave.cpp -- otherNodeData->incrementNumOutOfOrderSends(); +// This code appears to be determining if a node sent out of order packets, that logic should not be in +// the broadcast method, but would make more sense in the incoming packet processing section +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + #include #include #include @@ -116,38 +125,8 @@ void AvatarMixer::start() { while (!_isFinished) { - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // WORK ITEMS... - // - // DONE --- only sleep for remainder - // DONE --- clean up stats, add slave stats - // DONE --- out of view??? is it broken? - verified - it's working - // DONE --- hack to not send face data mostly seems to work... - // DONE --- fix two different versions of toByteArray() - // DONE --- audit the locking and side-effects to node, otherNode, and nodeData - // DONE --- delete dead code from mixer (now that it's in slave) - // DONE --- FIXME on sending identity packets - // DONE --- FIXME _maxKbpsPerNode - // DONE --- FIXME ++_sumListeners; - // DONE --- fix toByteArray() virtual hiding!!! - // - // 1) CPU throttling - now we're calculating it (like audio mixer, how to use it???) - // - // 2) Error in PacketList::writeData - attempted to write a segment to an unordered packet that is larger than the payload size. - // 2b) some kind of a better approach to handling otherAvatar.toByteArray() for content that is larger than MTU - // 3) better stats in the nodes: - // how many avatars are actually "in view" for the avtar in question (even if they are over bandwidth budget) - // 4) FIXME -- otherNodeData->incrementNumOutOfOrderSends(); - // 5) average_identity_packets_per_frame??? - // - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - // calculates last frame duration and sleeps for the remainder of the target amount - auto frameDuration = timeFrame(frameTimestamp); - throttle(frameDuration, frame); + auto frameDuration = timeFrame(frameTimestamp); // calculates last frame duration and sleeps remainder of target amount + throttle(frameDuration, frame); // determines _throttlingRatio for upcoming mix frame int lockWait, nodeTransform, functor; @@ -184,7 +163,7 @@ void AvatarMixer::start() { auto start = usecTimestampNow(); nodeList->nestedEach([&](NodeList::const_iterator cbegin, NodeList::const_iterator cend) { auto start = usecTimestampNow(); - _slavePool.broadcastAvatarData(cbegin, cend, _lastFrameTimestamp, _maxKbpsPerNode); + _slavePool.broadcastAvatarData(cbegin, cend, _lastFrameTimestamp, _maxKbpsPerNode, _throttlingRatio); auto end = usecTimestampNow(); _broadcastAvatarDataInner += (end - start); }, &lockWait, &nodeTransform, &functor); @@ -497,15 +476,28 @@ void AvatarMixer::sendStatsPacket() { AvatarMixerSlaveStats aggregateStats; QJsonObject slavesObject; + + float secondsSinceLastStats = (float)(start - _lastStatsTime) / (float)USECS_PER_SECOND; // gather stats int slaveNumber = 1; _slavePool.each([&](AvatarMixerSlave& slave) { QJsonObject slaveObject; AvatarMixerSlaveStats stats; slave.harvestStats(stats); - slaveObject["nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed); - slaveObject["numPacketsReceived"] = TIGHT_LOOP_STAT(stats.packetsProcessed); - slaveObject["numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent); + slaveObject["recevied_1_nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed); + slaveObject["received_2_numPacketsReceived"] = TIGHT_LOOP_STAT(stats.packetsProcessed); + + slaveObject["sent_1_nodesBroadcastedTo"] = TIGHT_LOOP_STAT(stats.nodesBroadcastedTo); + slaveObject["sent_2_numBytesSent"] = TIGHT_LOOP_STAT(stats.numBytesSent); + slaveObject["sent_3_numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent); + slaveObject["sent_4_numIdentityPackets"] = TIGHT_LOOP_STAT(stats.numIdentityPackets); + + float averageNodes = ((float)stats.nodesBroadcastedTo / (float)tightLoopFrames); + float averageOutboundAvatarKbps = averageNodes ? ((stats.numBytesSent / secondsSinceLastStats) / BYTES_PER_KILOBIT) / averageNodes : 0.0f; + slaveObject["sent_5_averageOutboundAvatarKbps"] = averageOutboundAvatarKbps; + + float averageOthersIncluded = averageNodes ? stats.numOthersIncluded / averageNodes : 0.0f; + slaveObject["sent_6_averageOthersIncluded"] = TIGHT_LOOP_STAT(averageOthersIncluded); slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(stats.processIncomingPacketsElapsedTime); slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(stats.ignoreCalculationElapsedTime); @@ -522,10 +514,21 @@ void AvatarMixer::sendStatsPacket() { QJsonObject slavesAggregatObject; - slavesAggregatObject["nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); - slavesAggregatObject["numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); - slavesAggregatObject["numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent); + slavesAggregatObject["recevied_1_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed); + slavesAggregatObject["received_2_numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed); + slavesAggregatObject["sent_1_nodesBroadcastedTo"] = TIGHT_LOOP_STAT(aggregateStats.nodesBroadcastedTo); + slavesAggregatObject["sent_2_numBytesSent"] = TIGHT_LOOP_STAT(aggregateStats.numBytesSent); + slavesAggregatObject["sent_3_numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent); + slavesAggregatObject["sent_4_numIdentityPackets"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityPackets); + + float averageNodes = ((float)aggregateStats.nodesBroadcastedTo / (float)tightLoopFrames); + float averageOutboundAvatarKbps = averageNodes ? ((aggregateStats.numBytesSent / secondsSinceLastStats) / BYTES_PER_KILOBIT) / averageNodes : 0.0f; + slavesAggregatObject["sent_5_averageOutboundAvatarKbps"] = averageOutboundAvatarKbps; + + float averageOthersIncluded = averageNodes ? aggregateStats.numOthersIncluded / averageNodes : 0.0f; + slavesAggregatObject["sent_6_averageOthersIncluded"] = TIGHT_LOOP_STAT(averageOthersIncluded); + slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime); slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime); slavesAggregatObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.toByteArrayElapsedTime); @@ -600,6 +603,8 @@ void AvatarMixer::sendStatsPacket() { auto end = usecTimestampNow(); _sendStatsElapsedTime = (end - start); + _lastStatsTime = start; + } void AvatarMixer::run() { diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index f03a47dbd8..32b0ffed69 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -104,6 +104,7 @@ private: quint64 _processEventsElapsedTime { 0 }; quint64 _sendStatsElapsedTime { 0 }; quint64 _queueIncomingPacketElapsedTime { 0 }; + quint64 _lastStatsTime { usecTimestampNow() }; RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 6fdd2fd23e..b5d4e390bb 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -58,7 +58,6 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); } -// FIXME -- this needs a mutex in new model. bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) { if (_hasReceivedFirstPacketsFrom.find(uuid) == _hasReceivedFirstPacketsFrom.end()) { _hasReceivedFirstPacketsFrom.insert(uuid); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index fb67cbf64e..3ef46eef9c 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -10,6 +10,7 @@ // #include +#include #include #include @@ -37,11 +38,12 @@ void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { void AvatarMixerSlave::configureBroadcast(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, - float maxKbpsPerNode) { + float maxKbpsPerNode, float throttlingRatio) { _begin = begin; _end = end; _lastFrameTimestamp = lastFrameTimestamp; _maxKbpsPerNode = maxKbpsPerNode; + _throttlingRatio = throttlingRatio; } void AvatarMixerSlave::harvestStats(AvatarMixerSlaveStats& stats) { @@ -61,8 +63,15 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { _stats.processIncomingPacketsElapsedTime += (end - start); } -#include -#include + +void AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { + QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); + auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); + individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); + identityPacket->write(individualData); + DependencyManager::get()->sendPacket(std::move(identityPacket), *destinationNode); + _stats.numIdentityPackets++; +} static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; @@ -88,10 +97,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { std::uniform_real_distribution distribution; if (node->getLinkedData() && (node->getType() == NodeType::Agent) && node->getActiveSocket()) { - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + _stats.nodesBroadcastedTo++; - // FIXME -- mixer data - // ++_sumListeners; + AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); nodeData->resetInViewStats(); @@ -249,11 +257,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp || distribution(generator) < IDENTITY_SEND_PROBABILITY)) { - QByteArray individualData = otherNodeData->getConstAvatarData()->identityByteArray(); - auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size()); - individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNodeData->getNodeID().toRfc4122()); - identityPacket->write(individualData); - DependencyManager::get()->sendPacket(std::move(identityPacket), *node); + sendIdentityPacket(otherNodeData, node); } const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); @@ -339,13 +343,13 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } { - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); + bool includeThisAvatar = true; auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); bool distanceAdjust = true; glm::vec3 viewerPosition = myPosition; AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray - bool dropFaceTracking = true; // this is a hack for now... always drop face tracking + bool dropFaceTracking = false; quint64 start = usecTimestampNow(); QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, @@ -353,10 +357,30 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 end = usecTimestampNow(); _stats.toByteArrayElapsedTime += (end - start); - if (bytes.size() > 1400) { - qDebug() << "WARNING: otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size(); - } else { + static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID); + if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { + qDebug() << "WARNING: otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data"; + + dropFaceTracking = true; // first try dropping the facial data + bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + + if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { + qDebug() << "WARNING: otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; + bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + } + + if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { + qDebug() << "WARNING: otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; + includeThisAvatar = false; + } + } + + if (includeThisAvatar) { + numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); + _stats.numOthersIncluded++; } } @@ -376,6 +400,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { avatarPacketList->closeCurrentPacket(true); _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); + _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList //qDebug() << "about to call nodeList->sendPacketList() for node:" << node; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 9382fcfeda..00948746ec 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -12,13 +12,19 @@ #ifndef hifi_AvatarMixerSlave_h #define hifi_AvatarMixerSlave_h +class AvatarMixerClientData; + class AvatarMixerSlaveStats { public: int nodesProcessed { 0 }; int packetsProcessed { 0 }; quint64 processIncomingPacketsElapsedTime { 0 }; + int nodesBroadcastedTo { 0 }; int numPacketsSent { 0 }; + int numBytesSent { 0 }; + int numIdentityPackets { 0 }; + int numOthersIncluded { 0 }; quint64 ignoreCalculationElapsedTime { 0 }; quint64 avatarDataPackingElapsedTime { 0 }; quint64 packetSendingElapsedTime { 0 }; @@ -29,16 +35,22 @@ public: // receiving job stats nodesProcessed = 0; packetsProcessed = 0; - numPacketsSent = 0; processIncomingPacketsElapsedTime = 0; // sending job stats + nodesBroadcastedTo = 0; numPacketsSent = 0; + numBytesSent = 0; + numIdentityPackets = 0; + numOthersIncluded = 0; ignoreCalculationElapsedTime = 0; avatarDataPackingElapsedTime = 0; packetSendingElapsedTime = 0; toByteArrayElapsedTime = 0; jobElapsedTime = 0; + + //qDebug() << "reset!!! " << "_stats.numBytesSent:" << numBytesSent << "_stats.nodesBroadcastedTo:" << nodesBroadcastedTo; + } AvatarMixerSlaveStats& operator+=(const AvatarMixerSlaveStats& rhs) { @@ -46,7 +58,11 @@ public: packetsProcessed += rhs.packetsProcessed; processIncomingPacketsElapsedTime += rhs.processIncomingPacketsElapsedTime; + nodesBroadcastedTo += rhs.nodesBroadcastedTo; numPacketsSent += rhs.numPacketsSent; + numBytesSent += rhs.numBytesSent; + numIdentityPackets += rhs.numIdentityPackets; + numOthersIncluded += rhs.numOthersIncluded; ignoreCalculationElapsedTime += rhs.ignoreCalculationElapsedTime; avatarDataPackingElapsedTime += rhs.avatarDataPackingElapsedTime; packetSendingElapsedTime += rhs.packetSendingElapsedTime; @@ -62,7 +78,9 @@ public: using ConstIter = NodeList::const_iterator; void configure(ConstIter begin, ConstIter end); - void configureBroadcast(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode); + void configureBroadcast(ConstIter begin, ConstIter end, + p_high_resolution_clock::time_point lastFrameTimestamp, + float maxKbpsPerNode, float throttlingRatio); void processIncomingPackets(const SharedNodePointer& node); void broadcastAvatarData(const SharedNodePointer& node); @@ -70,12 +88,15 @@ public: void harvestStats(AvatarMixerSlaveStats& stats); private: + void sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode); + // frame state ConstIter _begin; ConstIter _end; p_high_resolution_clock::time_point _lastFrameTimestamp; float _maxKbpsPerNode { 0.0f }; + float _throttlingRatio { 0.0f }; AvatarMixerSlaveStats _stats; }; diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index c0dcf9cbba..07d4fa8851 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -75,10 +75,12 @@ void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end run(begin, end); } -void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end, p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode) { +void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end, + p_high_resolution_clock::time_point lastFrameTimestamp, + float maxKbpsPerNode, float throttlingRatio) { _function = &AvatarMixerSlave::broadcastAvatarData; _configure = [&](AvatarMixerSlave& slave) { - slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode); + slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio); }; run(begin, end); } diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.h b/assignment-client/src/avatars/AvatarMixerSlavePool.h index e54681401d..6bef0515bb 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.h +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.h @@ -66,7 +66,7 @@ public: // Jobs the slave pool can do... void processIncomingPackets(ConstIter begin, ConstIter end); void broadcastAvatarData(ConstIter begin, ConstIter end, - p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode); + p_high_resolution_clock::time_point lastFrameTimestamp, float maxKbpsPerNode, float throttlingRatio); // iterate over all slaves void each(std::function functor); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2e034073b3..264da75de2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -370,7 +370,6 @@ public: virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail); - // FIXME virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector& lastSentJointData, AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, QVector* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const; From e95e7f663ce3933414a65fcfb222a6bd92c5c532 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 18 Feb 2017 13:04:00 -0800 Subject: [PATCH 31/35] more cleanup --- assignment-client/src/avatars/AvatarMixer.cpp | 28 ++++--------- .../src/avatars/AvatarMixerClientData.cpp | 10 ++++- .../src/avatars/AvatarMixerSlave.cpp | 42 +++++++++++-------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 8ba7181e9c..3978768907 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -9,18 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// WORK ITEMS... -// -// 1) FIXME in AvatarMixerSlave.cpp -- otherNodeData->incrementNumOutOfOrderSends(); -// This code appears to be determining if a node sent out of order packets, that logic should not be in -// the broadcast method, but would make more sense in the incoming packet processing section -// -////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - #include -#include +#include #include +#include +#include #include #include @@ -93,9 +86,6 @@ void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const Shar ++_sumIdentityPackets; } -#include -#include - std::chrono::microseconds AvatarMixer::timeFrame(p_high_resolution_clock::time_point& timestamp) { // advance the next frame auto nextTimestamp = timestamp + std::chrono::microseconds((int)((float)USECS_PER_SECOND / (float)AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND)); @@ -199,7 +189,7 @@ void AvatarMixer::start() { } -// NOTE: nodeData->getAvatar() might be side effected, most be called when access to node/nodeData +// NOTE: nodeData->getAvatar() might be side effected, must be called when access to node/nodeData // is guarenteed to not be accessed by other thread void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); @@ -248,9 +238,10 @@ void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) { // - oscillations will not occur after the recovery const float BACKOFF_TARGET = 0.44f; - // the mixer is known to struggle at about 80 on a "regular" machine - // so throttle 2/80 the streams to ensure smooth audio (throttling is linear) - const float THROTTLE_RATE = 2 / 80.0f; + // the mixer is known to struggle at about 150 on a "regular" machine + // so throttle 2/150 the streams to ensure smooth mixing (throttling is linear) + const float STRUGGLES_AT = 150.0f; + const float THROTTLE_RATE = 2 / STRUGGLES_AT; const float BACKOFF_RATE = THROTTLE_RATE / 4; // recovery should be bounded so that large changes in user count is a tolerable experience @@ -417,10 +408,7 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; - //statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; - statsObject["broadcast_loop_rate"] = _loopRate.rate(); - statsObject["threads"] = _slavePool.numThreads(); statsObject["trailing_mix_ratio"] = _trailingMixRatio; statsObject["throttling_ratio"] = _throttlingRatio; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index b5d4e390bb..df6a64e874 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -51,9 +51,17 @@ int AvatarMixerClientData::processPackets() { } int AvatarMixerClientData::parseData(ReceivedMessage& message) { + // pull the sequence number from the data first - message.readPrimitive(&_lastReceivedSequenceNumber); + uint16_t sequenceNumber; + + message.readPrimitive(&sequenceNumber); + if (sequenceNumber < _lastReceivedSequenceNumber && _lastReceivedSequenceNumber != UINT16_MAX) { + incrementNumOutOfOrderSends(); + } + _lastReceivedSequenceNumber = sequenceNumber; + // compute the offset to the data payload return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 3ef46eef9c..bc5ed355f9 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -198,7 +198,9 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { } else { const AvatarMixerClientData* otherData = reinterpret_cast(otherNode->getLinkedData()); - //AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + + shouldConsider = true; // assume we will consider... + // Check to see if the space bubble is enabled if (node->isIgnoreRadiusEnabled() || otherNode->isIgnoreRadiusEnabled()) { // Define the minimum bubble size @@ -235,8 +237,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { nodeData->removeFromRadiusIgnoringSet(node, otherNode->getUUID()); } - shouldConsider = true; - quint64 endIgnoreCalculation = usecTimestampNow(); _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); } @@ -271,7 +271,11 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // potentially update the max full rate distance for this frame maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); - // FIXME-- understand this code... WHAT IS IT DOING!!! + // This code handles the random dropping of avatar data based on the ratio of + // "getFullRateDistance" to actual distance. + // + // NOTE: If the recieving node is in "PAL mode" then it's asked to get things even that + // are out of view, this also appears to disable this random distribution. if (distanceToAvatar != 0.0f && !getsOutOfView && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { @@ -285,14 +289,14 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); - /*** - // FIXME this does not belong here... it should be in the packet processing - if (lastSeqToReceiver > lastSeqFromSender && lastSeqToReceiver != UINT16_MAX) { - // we got out out of order packets from the sender, track it - otherNodeData->incrementNumOutOfOrderSends(); - } - ***/ - + // FIXME - This code does appear to be working. But it seems brittle. + // It supports determining if the frame of data for this "other" + // avatar has already been sent to the reciever. This has been + // verified to work on a desktop display that renders at 60hz and + // therefore sends to mixer at 30hz. Each second you'd expect to + // have 15 (45hz-30hz) duplicate frames. In this case, the stat + // avg_other_av_skips_per_second does report 15. + // // make sure we haven't already sent this data from this sender to this receiver // or that somehow we haven't sent if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { @@ -308,12 +312,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // we're going to send this avatar if (shouldConsider) { - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); // FIXME - this seems weird... - - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); // determine if avatar is in view, to determine how much data to include... glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; @@ -381,6 +379,14 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(bytes); _stats.numOthersIncluded++; + + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + } } From 2b79602220344811de6cf530f3f571460a6af052 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 18 Feb 2017 13:14:14 -0800 Subject: [PATCH 32/35] debug cleanup --- assignment-client/src/avatars/AvatarMixer.cpp | 21 ++++++++++--------- .../src/avatars/AvatarMixerSlave.cpp | 9 ++++---- .../src/avatars/AvatarMixerSlave.h | 3 --- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3978768907..93ec0d0e8b 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -219,7 +220,7 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { nodeData->flagIdentityChange(); nodeData->setAvatarSessionDisplayNameMustChange(false); sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. - qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); + qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } } @@ -345,7 +346,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointerreadPrimitive(&isRequesting); nodeData->setRequestsDomainListData(isRequesting); - qDebug() << "node" << nodeData->getNodeID() << "requestsDomainListData" << isRequesting; + qCDebug(avatars) << "node" << nodeData->getNodeID() << "requestsDomainListData" << isRequesting; } } auto end = usecTimestampNow(); @@ -596,7 +597,7 @@ void AvatarMixer::sendStatsPacket() { } void AvatarMixer::run() { - qDebug() << "Waiting for connection to domain to request settings from domain-server."; + qCDebug(avatars) << "Waiting for connection to domain to request settings from domain-server."; // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -656,11 +657,11 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const float DEFAULT_NODE_SEND_BANDWIDTH = 5.0f; QJsonValue nodeBandwidthValue = avatarMixerGroupObject[NODE_SEND_BANDWIDTH_KEY]; if (!nodeBandwidthValue.isDouble()) { - qDebug() << NODE_SEND_BANDWIDTH_KEY << "is not a double - will continue with default value"; + qCDebug(avatars) << NODE_SEND_BANDWIDTH_KEY << "is not a double - will continue with default value"; } _maxKbpsPerNode = nodeBandwidthValue.toDouble(DEFAULT_NODE_SEND_BANDWIDTH) * KILO_PER_MEGA; - qDebug() << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps."; + qCDebug(avatars) << "The maximum send bandwidth per node is" << _maxKbpsPerNode << "kbps."; const QString AUTO_THREADS = "auto_threads"; bool autoThreads = avatarMixerGroupObject[AUTO_THREADS].toBool(); @@ -669,13 +670,13 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString NUM_THREADS = "num_threads"; int numThreads = avatarMixerGroupObject[NUM_THREADS].toString().toInt(&ok); if (!ok) { - qWarning() << "Avatar mixer: Error reading thread count. Using 1 thread."; + qCWarning(avatars) << "Avatar mixer: Error reading thread count. Using 1 thread."; numThreads = 1; } - qDebug() << "Avatar mixer will use specified number of threads:" << numThreads; + qCDebug(avatars) << "Avatar mixer will use specified number of threads:" << numThreads; _slavePool.setNumThreads(numThreads); } else { - qDebug() << "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 AVATARS_SETTINGS_KEY = "avatars"; @@ -693,7 +694,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { std::swap(_domainMinimumScale, _domainMaximumScale); } - qDebug() << "This domain requires a minimum avatar scale of" << _domainMinimumScale - << "and a maximum avatar scale of" << _domainMaximumScale; + qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale + << "and a maximum avatar scale of" << _domainMaximumScale; } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index bc5ed355f9..9c58b1be55 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include + #include "AvatarMixer.h" #include "AvatarMixerClientData.h" #include "AvatarMixerSlave.h" @@ -357,20 +359,20 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID); if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qDebug() << "WARNING: otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data"; + qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data"; dropFaceTracking = true; // first try dropping the facial data bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qDebug() << "WARNING: otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; + qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); } if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qDebug() << "WARNING: otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; + qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; includeThisAvatar = false; } } @@ -409,7 +411,6 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { _stats.numBytesSent += numAvatarDataBytes; // send the avatar data PacketList - //qDebug() << "about to call nodeList->sendPacketList() for node:" << node; nodeList->sendPacketList(std::move(avatarPacketList), *node); // record the bytes sent for other avatar data in the AvatarMixerClientData diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 00948746ec..b1a5b56c4f 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -48,9 +48,6 @@ public: packetSendingElapsedTime = 0; toByteArrayElapsedTime = 0; jobElapsedTime = 0; - - //qDebug() << "reset!!! " << "_stats.numBytesSent:" << numBytesSent << "_stats.nodesBroadcastedTo:" << nodesBroadcastedTo; - } AvatarMixerSlaveStats& operator+=(const AvatarMixerSlaveStats& rhs) { From bc858f82fcd29808b42ad67424d9b3f63b59ed22 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 18 Feb 2017 14:07:20 -0800 Subject: [PATCH 33/35] fixed a FIXME comment --- assignment-client/src/avatars/AvatarMixerSlave.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 9c58b1be55..46133a221f 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -84,9 +84,15 @@ static const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45; // to determine whether the extra data should be sent. static const int EXTRA_AVATAR_DATA_FRAME_RATIO = 16; -// An 80% chance of sending a identity packet within a 5 second interval. -// assuming 60 htz update rate. -const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // FIXME... this is wrong for 45hz +// FIXME - There is some old logic (unchanged as of 2/17/17) that randomly decides to send an identity +// packet. That logic had the following comment about the constants it uses... +// +// An 80% chance of sending a identity packet within a 5 second interval. +// assuming 60 htz update rate. +// +// Assuming the calculation of the constant is in fact correct for 80% and 60hz and 5 seconds (an assumption +// that I have not verified) then the constant is definitely wrong now, since we send at 45hz. +const float IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { quint64 start = usecTimestampNow(); From 73d64120dfd48c45fd8ebf05720567fc4ae14f8f Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 18 Feb 2017 14:08:03 -0800 Subject: [PATCH 34/35] removed dead comment --- assignment-client/src/avatars/AvatarMixer.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 93ec0d0e8b..3b46c96406 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -70,9 +70,6 @@ void AvatarMixer::queueIncomingPacket(QSharedPointer message, S AvatarMixer::~AvatarMixer() { } -// An 80% chance of sending a identity packet within a 5 second interval. -// assuming 60 htz update rate. - void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { QByteArray individualData = nodeData->getAvatar().identityByteArray(); From 942c5689fcc0975edd24ab4550fbe2582aba3265 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Sat, 18 Feb 2017 14:09:47 -0800 Subject: [PATCH 35/35] more comment cleanup --- assignment-client/src/avatars/AvatarMixer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 3b46c96406..3a8aa0182c 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -216,7 +216,7 @@ void AvatarMixer::manageDisplayName(const SharedNodePointer& node) { soFar.second++; // refcount nodeData->flagIdentityChange(); nodeData->setAvatarSessionDisplayNameMustChange(false); - sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. + sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } }