diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index f534be9346..4cc24e2110 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -447,11 +447,6 @@ void Agent::executeScript() { auto avatarHashMap = DependencyManager::set(); _scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data()); - auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket"); - // register ourselves to the script engine _scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this)); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index cf30aca1a7..f279d76450 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -109,17 +109,10 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) { message.readPrimitive(&traitSize); if (packetTraitVersion > _receivedSimpleTraitVersions[traitType]) { - if (traitType == AvatarTraits::SkeletonModelURL) { - // get the URL from the binary data - auto skeletonModelURL = QUrl::fromEncoded(message.read(traitSize)); - _avatar->setSkeletonModelURL(skeletonModelURL); - - qDebug() << "Set skeleton URL to" << skeletonModelURL << "for trait packet version" << packetTraitVersion; - - _receivedSimpleTraitVersions[traitType] = packetTraitVersion; - - anyTraitsChanged = true; - } + _avatar->processTrait(traitType, message.readWithoutCopy(traitSize)); + _receivedSimpleTraitVersions[traitType] = packetTraitVersion; + + anyTraitsChanged = true; } else { message.seek(message.getPosition() + traitSize); } diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 0e1126cebe..05002828f5 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -81,9 +81,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); auto avatarHashMap = DependencyManager::set(); - packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket"); packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket"); packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket"); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 09fa6dc573..8569aaf05a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -72,10 +72,6 @@ AvatarManager::AvatarManager(QObject* parent) : qRegisterMetaType >("NodeWeakPointer"); auto nodeList = DependencyManager::get(); - auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); // when we hear that the user has ignored an avatar by session UUID // immediately remove that avatar instead of waiting for the absence of packets from avatar mixer diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 53ad8c0a0f..df9afeb92d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1705,8 +1705,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (previousSkeletonModelURL != _skeletonModelURL) { _clientTraitsHandler.markTraitChanged(AvatarTraits::SkeletonModelURL); - } else { - qDebug() << "Not marking skeleton model URL trait changed since the new value matches the previous"; } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 62c7a7053f..ddfeb4df24 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -42,6 +42,7 @@ #include #include "AvatarLogging.h" +#include "AvatarTraits.h" //#define WANT_DEBUG @@ -1756,7 +1757,7 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { } void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, - bool& displayNameChanged, bool& skeletonModelUrlChanged) { + bool& displayNameChanged) { QDataStream packetStream(identityData); @@ -1777,7 +1778,7 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide if (incomingSequenceNumber > _identitySequenceNumber) { Identity identity; - packetStream >> identity.skeletonModelURL + packetStream >> identity.attachmentData >> identity.displayName >> identity.sessionDisplayName @@ -1789,16 +1790,6 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; - if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { - setSkeletonModelURL(identity.skeletonModelURL); - identityChanged = true; - skeletonModelUrlChanged = true; - if (_firstSkeletonCheck) { - displayNameChanged = true; - } - _firstSkeletonCheck = false; - } - if (identity.displayName != _displayName) { _displayName = identity.displayName; identityChanged = true; @@ -1834,7 +1825,6 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ << "identity.uuid:" << identity.uuid - << "identity.skeletonModelURL:" << identity.skeletonModelURL << "identity.displayName:" << identity.displayName << "identity.sessionDisplayName:" << identity.sessionDisplayName; } else { @@ -1846,10 +1836,18 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide } } +void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { + if (traitType == AvatarTraits::SkeletonModelURL) { + // get the URL from the binary data + auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData); + qDebug() << "Setting skeleton model URL from trait packet to" << skeletonModelURL; + setSkeletonModelURL(skeletonModelURL); + } +} + QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); - const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL // when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received // whereas agents send a fresh outgoing sequence number when identity data has changed @@ -1857,7 +1855,6 @@ QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { _avatarEntitiesLock.withReadLock([&] { identityStream << getSessionUUID() << (udt::SequenceNumber::Type) _identitySequenceNumber - << urlToSend << _attachmentData << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index fcc63fdc98..12e8370b86 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -51,6 +51,7 @@ #include #include "AABox.h" +#include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" @@ -955,8 +956,9 @@ public: // identityChanged returns true if identity has changed, false otherwise. // identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange. - void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, - bool& displayNameChanged, bool& skeletonModelUrlChanged); + void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); + + void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData); QByteArray identityByteArray(bool setIsReplicated = false) const; @@ -1327,7 +1329,6 @@ protected: mutable HeadData* _headData { nullptr }; QUrl _skeletonModelURL; - bool _firstSkeletonCheck { true }; QUrl _skeletonFBXURL; QVector _attachmentData; QVector _oldAttachmentData; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 174e81bb31..407e88e27c 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -19,10 +19,17 @@ #include #include "AvatarLogging.h" +#include "AvatarTraits.h" AvatarHashMap::AvatarHashMap() { auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); + packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); + packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); + packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); + connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } @@ -182,9 +189,74 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); bool identityChanged = false; bool displayNameChanged = false; - bool skeletonModelUrlChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged); + avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); + } +} + +bool AvatarHashMap::checkLastProcessedTraitVersion(QUuid avatarID, + AvatarTraits::TraitType traitType, AvatarTraits::TraitVersion newVersion) { + auto it = _processedSimpleTraitVersions.find(avatarID); + if (it == _processedSimpleTraitVersions.end()) { + auto pair = _processedSimpleTraitVersions.insert({ + avatarID, + { AvatarTraits::TotalTraitTypes, AvatarTraits::DEFAULT_TRAIT_VERSION } + }); + + it = pair.first; + }; + + if (it->second[traitType] < newVersion) { + it->second[traitType] = newVersion; + return true; + } else { + return false; + } +} + +void AvatarHashMap::processBulkAvatarTraits(QSharedPointer message, SharedNodePointer sendingNode) { + while (message->getBytesLeftToRead()) { + // read the avatar ID to figure out which avatar this is for + auto avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + // grab the avatar so we can ask it to process trait data + AvatarSharedPointer avatar; + + QReadLocker locker(&_hashLock); + auto it = _avatarHash.find(avatarID); + if (it != _avatarHash.end()) { + avatar = *it; + } + locker.unlock(); + + // read the first trait type for this avatar + AvatarTraits::TraitType traitType; + message->readPrimitive(&traitType); + + while (traitType != AvatarTraits::NullTrait) { + AvatarTraits::TraitVersion traitVersion; + message->readPrimitive(&traitVersion); + + AvatarTraits::TraitWireSize traitBinarySize; + message->readPrimitive(&traitBinarySize); + + if (avatar) { + // check if this trait version is newer than what we already have for this avatar + bool traitIsNewer = checkLastProcessedTraitVersion(avatarID, traitType, traitVersion); + if (traitIsNewer) { + avatar->processTrait(traitType, message->readWithoutCopy(traitBinarySize)); + } else { + message->seek(message->getPosition() + traitBinarySize); + } + } else { + // though we have no avatar pointer, we still hop through the packet in case there are + // traits for avatars we do have later in the packet + message->seek(message->getPosition() + traitBinarySize); + } + + // read the next trait type, which is null if there are no more traits for this avatar + message->readPrimitive(&traitType); + } } } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index fd2cd76fbf..ed8440eb89 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -30,6 +30,7 @@ #include "ScriptAvatarData.h" #include "AvatarData.h" +#include "AvatarTraits.h" /**jsdoc * Note: An AvatarList API is also provided for Interface and client entity scripts: it is a @@ -133,6 +134,8 @@ protected slots: */ void processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode); + void processBulkAvatarTraits(QSharedPointer message, SharedNodePointer sendingNode); + /**jsdoc * @function AvatarList.processKillAvatar * @param {} message @@ -153,6 +156,9 @@ protected: virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); + bool checkLastProcessedTraitVersion(QUuid avatarID, + AvatarTraits::TraitType traitType, AvatarTraits::TraitVersion newVersion); + AvatarHash _avatarHash; struct PendingAvatar { std::chrono::steady_clock::time_point creationTime; @@ -163,6 +169,7 @@ protected: AvatarPendingHash _pendingAvatars; mutable QReadWriteLock _hashLock; + std::unordered_map _processedSimpleTraitVersions; private: QUuid _lastOwnerSessionUUID; }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 8b50e37699..94137786cd 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::FarGrabJoints); + return static_cast(AvatarMixerPacketVersion::MigrateSkeletonURLToTraits); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 616694c8da..31724ab5dc 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -134,7 +134,7 @@ public: EntityClone, EntityQueryInitialResultsComplete, BulkAvatarTraits, - + NUM_PACKET_TYPE }; @@ -291,6 +291,7 @@ enum class AvatarMixerPacketVersion : PacketVersion { FixMannequinDefaultAvatarFeet, ProceduralFaceMovementFlagsAndBlendshapes, FarGrabJoints + MigrateSkeletonURLToTraits }; enum class DomainConnectRequestVersion : PacketVersion {