diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 4336870618..c8ab489311 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -544,7 +544,7 @@ void Agent::setIsAvatar(bool isAvatar) { connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket); // start the timers - _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); + _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets // tell the avatarAudioTimer to start ticking emit startAvatarAudioTimer(); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index f3da74ce5e..05dbfee912 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -71,15 +71,10 @@ AvatarMixer::~AvatarMixer() { void AvatarMixer::sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { QByteArray individualData = nodeData->getAvatar().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); - + auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); + identityPackets->write(individualData); + DependencyManager::get()->sendPacketList(std::move(identityPackets), *destinationNode); ++_sumIdentityPackets; } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 6e3dd150a4..2ad8bb58ed 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -263,16 +263,8 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { // 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) { - // don't ignore this avatar if we haven't sent any update for a long while - // in an effort to prevent other interfaces from deleting a stale avatar instance - uint64_t lastBroadcastTime = nodeData->getLastBroadcastTime(avatarNode->getUUID()); - const AvatarMixerClientData* otherNodeData = reinterpret_cast(avatarNode->getLinkedData()); - const uint64_t AVATAR_UPDATE_STALE = AVATAR_UPDATE_TIMEOUT - USECS_PER_SECOND; - if (lastBroadcastTime > otherNodeData->getIdentityChangeTimestamp() && - lastBroadcastTime + AVATAR_UPDATE_STALE > startIgnoreCalculation) { - ++numAvatarsHeldBack; - shouldIgnore = true; - } + ++numAvatarsHeldBack; + shouldIgnore = true; } 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; @@ -285,7 +277,7 @@ void AvatarMixerSlave::broadcastAvatarData(const SharedNodePointer& node) { int avatarRank = 0; // this is overly conservative, because it includes some avatars we might not consider - int remainingAvatars = (int)sortedAvatars.size(); + int remainingAvatars = (int)sortedAvatars.size(); while (!sortedAvatars.empty()) { AvatarPriority sortData = sortedAvatars.top(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 44c2918f9d..39a4b8ee7c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5216,11 +5216,7 @@ void Application::resettingDomain() { } void Application::nodeAdded(SharedNodePointer node) const { - if (node->getType() == NodeType::AvatarMixer) { - // new avatar mixer, send off our identity packet right away - getMyAvatar()->sendIdentityPacket(); - getMyAvatar()->resetLastSent(); - } + // nothing to do here } void Application::nodeActivated(SharedNodePointer node) { @@ -5256,6 +5252,13 @@ void Application::nodeActivated(SharedNodePointer node) { if (node->getType() == NodeType::AudioMixer) { DependencyManager::get()->negotiateAudioFormat(); } + + if (node->getType() == NodeType::AvatarMixer) { + // new avatar mixer, send off our identity packet right away + getMyAvatar()->markIdentityDataChanged(); + getMyAvatar()->sendIdentityPacket(); + getMyAvatar()->resetLastSent(); + } } void Application::nodeKilled(SharedNodePointer node) { diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f29efb8c32..ce8ec44f6c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -115,8 +115,6 @@ Avatar::Avatar(QThread* thread, RigPointer rig) : } Avatar::~Avatar() { - assert(isDead()); // mark dead before calling the dtor - auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { @@ -510,12 +508,13 @@ static TextRenderer3D* textRenderer(TextRendererType type) { void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& scene, render::Transaction& transaction) { auto avatarPayload = new render::Payload(self); auto avatarPayloadPointer = Avatar::PayloadPointer(avatarPayload); - _renderItemID = scene->allocateID(); - transaction.resetItem(_renderItemID, avatarPayloadPointer); - _skeletonModel->addToScene(scene, transaction); + if (_skeletonModel->addToScene(scene, transaction)) { + _renderItemID = scene->allocateID(); + transaction.resetItem(_renderItemID, avatarPayloadPointer); - for (auto& attachmentModel : _attachmentModels) { - attachmentModel->addToScene(scene, transaction); + for (auto& attachmentModel : _attachmentModels) { + attachmentModel->addToScene(scene, transaction); + } } } @@ -1123,11 +1122,20 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setModelURLFinished(bool success) { if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { - qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL; - // call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that - // we don't redo this every time we receive an identity packet from the avatar with the bad url. - QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", - Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl())); + const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts + if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || + _skeletonModel->getResourceDownloadAttempts() > MAX_SKELETON_DOWNLOAD_ATTEMPTS) { + qCWarning(interfaceapp) << "Using default after failing to load Avatar model: " << _skeletonModelURL + << "after" << _skeletonModel->getResourceDownloadAttempts() << "attempts."; + // call _skeletonModel.setURL, but leave our copy of _skeletonModelURL alone. This is so that + // we don't redo this every time we receive an identity packet from the avatar with the bad url. + QMetaObject::invokeMethod(_skeletonModel.get(), "setURL", + Qt::QueuedConnection, Q_ARG(QUrl, AvatarData::defaultFullAvatarModelUrl())); + } else { + qCWarning(interfaceapp) << "Avatar model: " << _skeletonModelURL + << "failed to load... attempts:" << _skeletonModel->getResourceDownloadAttempts() + << "out of:" << MAX_SKELETON_DOWNLOAD_ATTEMPTS; + } } } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 585776b395..c4bcb67a16 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -213,10 +213,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } } avatar->animateScaleChanges(deltaTime); - if (avatar->shouldDie()) { - avatar->die(); - removeAvatar(avatar->getID()); - } const float OUT_OF_VIEW_THRESHOLD = 0.5f * AvatarData::OUT_OF_VIEW_PENALTY; uint64_t now = usecTimestampNow(); @@ -330,44 +326,12 @@ AvatarSharedPointer AvatarManager::newSharedAvatar() { return std::make_shared(qApp->thread(), std::make_shared()); } -void AvatarManager::processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) { - PerformanceTimer perfTimer("receiveAvatar"); - // enumerate over all of the avatars in this packet - // only add them if mixerWeakPointer points to something (meaning that mixer is still around) - while (message->getBytesLeftToRead()) { - AvatarSharedPointer avatarData = parseAvatarData(message, sendingNode); - if (avatarData) { - auto avatar = std::static_pointer_cast(avatarData); - if (avatar->isInScene()) { - if (!_shouldRender) { - // rare transition so we process the transaction immediately - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - avatar->removeFromScene(avatar, scene, transaction); - if (scene) { - scene->enqueueTransaction(transaction); - } - } - } else if (_shouldRender) { - // very rare transition so we process the transaction immediately - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - avatar->addToScene(avatar, scene, transaction); - if (scene) { - scene->enqueueTransaction(transaction); - } - } - } - } -} - void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { AvatarHashMap::handleRemovedAvatar(removedAvatar, removalReason); // removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar // class in this context so we can call methods that don't exist at the base class. - Avatar* avatar = static_cast(removedAvatar.get()); - avatar->die(); + auto avatar = std::static_pointer_cast(removedAvatar); AvatarMotionState* motionState = avatar->getMotionState(); if (motionState) { @@ -403,14 +367,11 @@ void AvatarManager::clearOtherAvatars() { if (avatar->isInScene()) { avatar->removeFromScene(avatar, scene, transaction); } - AvatarMotionState* motionState = avatar->getMotionState(); - if (motionState) { - _motionStatesThatMightUpdate.remove(motionState); - _motionStatesToAddToPhysics.remove(motionState); - _motionStatesToRemoveFromPhysics.push_back(motionState); - } + handleRemovedAvatar(avatar); + avatarIterator = _avatarHash.erase(avatarIterator); + } else { + ++avatarIterator; } - ++avatarIterator; } scene->enqueueTransaction(transaction); _myAvatar->clearLookAtTargetAvatar(); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 6eabbd081f..45f1a597eb 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -98,9 +98,6 @@ public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); -protected slots: - void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) override; - private: explicit AvatarManager(QObject* parent = 0); explicit AvatarManager(const AvatarManager& other); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f4f078c9e5..7bc961c654 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -412,9 +412,7 @@ void MyAvatar::update(float deltaTime) { Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)), Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f))); - uint64_t now = usecTimestampNow(); - if (now > _identityPacketExpiry || _avatarEntityDataLocallyEdited) { - _identityPacketExpiry = now + AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS; + if (getIdentityDataChanged()) { sendIdentityPacket(); } @@ -1258,7 +1256,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN setSkeletonModelURL(fullAvatarURL); UserActivityLogger::getInstance().changedModel("skeleton", urlString); } - _identityPacketExpiry = 0; // triggers an identity packet next update() + markIdentityDataChanged(); } void MyAvatar::setAttachmentData(const QVector& attachmentData) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 74af44c99a..a201ee54c9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -701,8 +701,6 @@ private: std::mutex _holdActionsMutex; std::vector _holdActions; - uint64_t _identityPacketExpiry { 0 }; - float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; float AUDIO_ENERGY_CONSTANT { 0.000001f }; float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f }; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 39b37e3d19..99dbb1a28e 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -79,6 +79,25 @@ int main(int argc, const char* argv[]) { instanceMightBeRunning = false; } + QCommandLineParser parser; + QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications"); + QCommandLineOption runServerOption("runServer", "Whether to run the server"); + QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); + QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run"); + parser.addOption(checkMinSpecOption); + parser.addOption(runServerOption); + parser.addOption(serverContentPathOption); + parser.addOption(allowMultipleInstancesOption); + parser.parse(arguments); + bool runServer = parser.isSet(runServerOption); + bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); + QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); + bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption); + + if (allowMultipleInstances) { + instanceMightBeRunning = false; + } + if (instanceMightBeRunning) { // Try to connect and send message to existing interface instance QLocalSocket socket; @@ -137,18 +156,6 @@ int main(int argc, const char* argv[]) { } } - QCommandLineParser parser; - QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications"); - QCommandLineOption runServerOption("runServer", "Whether to run the server"); - QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath"); - parser.addOption(checkMinSpecOption); - parser.addOption(runServerOption); - parser.addOption(serverContentPathOption); - parser.parse(arguments); - bool runServer = parser.isSet(runServerOption); - bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); - QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); - QElapsedTimer startupTime; startupTime.start(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 90f2fb5342..ad65ba4410 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1473,7 +1473,22 @@ QStringList AvatarData::getJointNames() const { void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { QDataStream packetStream(data); - packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.sessionDisplayName >> identityOut.avatarEntityData; + packetStream >> identityOut.uuid + >> identityOut.skeletonModelURL + >> identityOut.attachmentData + >> identityOut.displayName + >> identityOut.sessionDisplayName + >> identityOut.avatarEntityData + >> identityOut.updatedAt; + +#ifdef WANT_DEBUG + qCDebug(avatars) << __FUNCTION__ + << "identityOut.uuid:" << identityOut.uuid + << "identityOut.skeletonModelURL:" << identityOut.skeletonModelURL + << "identityOut.displayName:" << identityOut.displayName + << "identityOut.sessionDisplayName:" << identityOut.sessionDisplayName; +#endif + } static const QUrl emptyURL(""); @@ -1484,6 +1499,12 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) { + if (identity.updatedAt < _identityUpdatedAt) { + qCDebug(avatars) << "Ignoring late identity packet for avatar " << getSessionUUID() + << "identity.updatedAt:" << identity.updatedAt << "_identityUpdatedAt:" << _identityUpdatedAt; + return; + } + if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { setSkeletonModelURL(identity.skeletonModelURL); identityChanged = true; @@ -1513,24 +1534,35 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC setAvatarEntityData(identity.avatarEntityData); identityChanged = true; } - // flag this avatar as non-stale by updating _averageBytesReceived - const int BOGUS_NUM_BYTES = 1; - _averageBytesReceived.updateAverage(BOGUS_NUM_BYTES); + + // use the timestamp from this identity, since we want to honor the updated times in "server clock" + // this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt + _identityUpdatedAt = identity.updatedAt; } QByteArray AvatarData::identityByteArray() const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); - const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); + const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL _avatarEntitiesLock.withReadLock([&] { - identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << getSessionDisplayNameForTransport() << _avatarEntityData; + identityStream << getSessionUUID() + << urlToSend + << _attachmentData + << _displayName + << getSessionDisplayNameForTransport() // depends on _sessionDisplayName + << _avatarEntityData + << _identityUpdatedAt; }); return identityData; } void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { + if (skeletonModelURL.isEmpty()) { + qCDebug(avatars) << __FUNCTION__ << "caller called with empty URL."; + } + const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; if (expanded == _skeletonModelURL) { return; @@ -1539,6 +1571,7 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); updateJointMappings(); + markIdentityDataChanged(); } void AvatarData::setDisplayName(const QString& displayName) { @@ -1548,6 +1581,7 @@ void AvatarData::setDisplayName(const QString& displayName) { sendIdentityPacket(); qCDebug(avatars) << "Changing display name for avatar to" << displayName; + markIdentityDataChanged(); } QVector AvatarData::getAttachmentData() const { @@ -1566,6 +1600,7 @@ void AvatarData::setAttachmentData(const QVector& attachmentData return; } _attachmentData = attachmentData; + markIdentityDataChanged(); } void AvatarData::attach(const QString& modelURL, const QString& jointName, @@ -1695,7 +1730,6 @@ void AvatarData::sendAvatarDataPacket() { void AvatarData::sendIdentityPacket() { auto nodeList = DependencyManager::get(); - QByteArray identityData = identityByteArray(); auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); @@ -1709,6 +1743,7 @@ void AvatarData::sendIdentityPacket() { }); _avatarEntityDataLocallyEdited = false; + _identityDataChanged = false; } void AvatarData::updateJointMappings() { @@ -2245,10 +2280,12 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { _avatarEntityData.insert(entityID, entityData); _avatarEntityDataLocallyEdited = true; + markIdentityDataChanged(); } } else { itr.value() = entityData; _avatarEntityDataLocallyEdited = true; + markIdentityDataChanged(); } }); } @@ -2262,6 +2299,7 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID) { _avatarEntitiesLock.withWriteLock([&] { _avatarEntityData.remove(entityID); _avatarEntityDataLocallyEdited = true; + markIdentityDataChanged(); }); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e05bdce162..545a5f1f8c 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -110,9 +110,7 @@ const char LEFT_HAND_POINTING_FLAG = 1; const char RIGHT_HAND_POINTING_FLAG = 2; const char IS_FINGER_POINTING_FLAG = 4; -const qint64 AVATAR_UPDATE_TIMEOUT = 5 * USECS_PER_SECOND; - -// AvatarData state flags - we store the details about the packet encoding in the first byte, +// AvatarData state flags - we store the details about the packet encoding in the first byte, // before the "header" structure const char AVATARDATA_FLAGS_MINIMUM = 0; @@ -531,6 +529,7 @@ public: QString displayName; QString sessionDisplayName; AvatarEntityMap avatarEntityData; + quint64 updatedAt; }; static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); @@ -547,7 +546,10 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); - virtual void setSessionDisplayName(const QString& sessionDisplayName) { _sessionDisplayName = sessionDisplayName; }; + virtual void setSessionDisplayName(const QString& sessionDisplayName) { + _sessionDisplayName = sessionDisplayName; + markIdentityDataChanged(); + } Q_INVOKABLE QVector getAttachmentData() const; Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData); @@ -565,7 +567,6 @@ public: void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } - int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); } int getAverageBytesReceivedPerSecond() const; int getReceiveRate() const; @@ -601,9 +602,6 @@ public: return _lastSentJointData; } - - bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_UPDATE_TIMEOUT; } - static const float OUT_OF_VIEW_PENALTY; static void sortAvatars( @@ -620,11 +618,15 @@ public: static float _avatarSortCoefficientCenter; static float _avatarSortCoefficientAge; - + bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called + void markIdentityDataChanged() { + _identityDataChanged = true; + _identityUpdatedAt = usecTimestampNow(); + } signals: void displayNameChanged(); - + public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); @@ -779,6 +781,9 @@ protected: quint64 _audioLoudnessChanged { 0 }; float _audioAverageLoudness { 0.0f }; + bool _identityDataChanged { false }; + quint64 _identityUpdatedAt { 0 }; + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index e944c7c887..21ea8081c7 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -57,7 +57,7 @@ public slots: protected slots: void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID); - virtual void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode); void processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode); void processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode); void processExitingSpaceBubble(QSharedPointer message, SharedNodePointer sendingNode); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index f23cba04a0..6a1cc4c466 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -112,6 +112,8 @@ public: void setResource(GeometryResource::Pointer resource); QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } + int getResourceDownloadAttempts() { return _resource ? _resource->getDownloadAttempts() : 0; } + int getResourceDownloadAttemptsRemaining() { return _resource ? _resource->getDownloadAttemptsRemaining() : 0; } private: void startWatching(); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 033f4bbaa8..60227eeaa1 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -56,7 +56,6 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), _connectionSecret(connectionSecret), - _isAlive(true), _pingMs(-1), // "Uninitialized" _clockSkewUsec(0), _mutex(), diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 28afb8b943..d1bbffd817 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -54,9 +54,6 @@ public: NodeData* getLinkedData() const { return _linkedData.get(); } void setLinkedData(std::unique_ptr linkedData) { _linkedData = std::move(linkedData); } - bool isAlive() const { return _isAlive; } - void setAlive(bool isAlive) { _isAlive = isAlive; } - int getPingMs() const { return _pingMs; } void setPingMs(int pingMs) { _pingMs = pingMs; } @@ -92,7 +89,6 @@ private: QUuid _connectionSecret; std::unique_ptr _linkedData; - bool _isAlive; int _pingMs; qint64 _clockSkewUsec; QMutex _mutex; diff --git a/libraries/networking/src/ReceivedMessage.cpp b/libraries/networking/src/ReceivedMessage.cpp index 02cb58fb2d..2c5a11334b 100644 --- a/libraries/networking/src/ReceivedMessage.cpp +++ b/libraries/networking/src/ReceivedMessage.cpp @@ -26,8 +26,7 @@ ReceivedMessage::ReceivedMessage(const NLPacketList& packetList) _sourceID(packetList.getSourceID()), _packetType(packetList.getType()), _packetVersion(packetList.getVersion()), - _senderSockAddr(packetList.getSenderSockAddr()), - _isComplete(true) + _senderSockAddr(packetList.getSenderSockAddr()) { } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 7ae75b9538..f9694f1cd9 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -627,8 +627,6 @@ void Resource::init() { } } -const int MAX_ATTEMPTS = 8; - void Resource::attemptRequest() { _startedLoading = true; @@ -746,14 +744,17 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { // Fall through to other cases } case ResourceRequest::Result::ServerUnavailable: { + _attempts++; + _attemptsRemaining--; + + qCDebug(networking) << "Retriable error while loading" << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining; + // retry with increasing delays const int BASE_DELAY_MS = 1000; - if (_attempts++ < MAX_ATTEMPTS) { + if (_attempts < MAX_ATTEMPTS) { auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts); - qCDebug(networking).noquote() << "Server unavailable for" << _url << "- may retry in" << waitTime << "ms" << "if resource is still needed"; - QTimer::singleShot(waitTime, this, &Resource::attemptRequest); willRetry = true; break; @@ -761,9 +762,10 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { // fall through to final failure } default: { - qCDebug(networking) << "Error loading " << _url; + _attemptsRemaining = 0; + qCDebug(networking) << "Error loading " << _url << "attempt:" << _attempts << "attemptsRemaining:" << _attemptsRemaining; auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError - : QNetworkReply::UnknownNetworkError; + : QNetworkReply::UnknownNetworkError; emit failed(error); willRetry = false; finishedLoading(false); diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 3a28c6c313..d4c7d63ee5 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -395,6 +395,9 @@ public: const QUrl& getURL() const { return _url; } + unsigned int getDownloadAttempts() { return _attempts; } + unsigned int getDownloadAttemptsRemaining() { return _attemptsRemaining; } + signals: /// Fired when the resource begins downloading. void loading(); @@ -483,7 +486,9 @@ private: int _lruKey{ 0 }; QTimer* _replyTimer{ nullptr }; - int _attempts{ 0 }; + unsigned int _attempts{ 0 }; + static const int MAX_ATTEMPTS = 8; + unsigned int _attemptsRemaining { MAX_ATTEMPTS }; bool _isInScript{ false }; }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 863f1bfda6..bd30cdd29c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::StickAndBallDefaultAvatar); + return static_cast(AvatarMixerPacketVersion::IdentityPacketsIncludeUpdateTime); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 87af3513b5..d312427ca7 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -231,7 +231,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { ImmediateSessionDisplayNameUpdates, VariableAvatarData, AvatarAsChildFixes, - StickAndBallDefaultAvatar + StickAndBallDefaultAvatar, + IdentityPacketsIncludeUpdateTime }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 62e045a3c1..5899ccf6b5 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -252,6 +252,8 @@ public: void renderDebugMeshBoxes(gpu::Batch& batch); + int getResourceDownloadAttempts() { return _renderWatcher.getResourceDownloadAttempts(); } + int getResourceDownloadAttemptsRemaining() { return _renderWatcher.getResourceDownloadAttemptsRemaining(); } public slots: void loadURLFinished(bool success);