// // AvatarHashMap.cpp // libraries/avatars/src // // Created by Andrew Meadows on 1/28/2014. // Copyright 2014 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 "AvatarHashMap.h" #include #include #include #include #include #include "AvatarLogging.h" #include "AvatarTraits.h" #include "Profile.h" void AvatarReplicas::addReplica(const QUuid& parentID, AvatarSharedPointer replica) { if (parentID == QUuid()) { return; } if (_replicasMap.find(parentID) == _replicasMap.end()) { std::vector emptyReplicas = std::vector(); _replicasMap.insert(std::pair>(parentID, emptyReplicas)); } auto &replicas = _replicasMap[parentID]; replica->setReplicaIndex((int)replicas.size() + 1); replicas.push_back(replica); } std::vector AvatarReplicas::getReplicaIDs(const QUuid& parentID) { std::vector ids; if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (int i = 0; i < (int)replicas.size(); i++) { ids.push_back(replicas[i]->getID()); } } else if (_replicaCount > 0) { for (int i = 0; i < _replicaCount; i++) { ids.push_back(QUuid::createUuid()); } } return ids; } void AvatarReplicas::parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { avatar->parseDataFromBuffer(buffer); } } } void AvatarReplicas::removeReplicas(const QUuid& parentID) { if (_replicasMap.find(parentID) != _replicasMap.end()) { _replicasMap.erase(parentID); } } std::vector AvatarReplicas::takeReplicas(const QUuid& parentID) { std::vector replicas; auto it = _replicasMap.find(parentID); if (it != _replicasMap.end()) { // take a copy of the replica shared pointers for this parent replicas = it->second; // erase the replicas for this parent from our map _replicasMap.erase(it); } return replicas; } void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged); } } } void AvatarReplicas::processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { avatar->processTrait(traitType, traitBinaryData); } } } void AvatarReplicas::processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { avatar->processDeletedTraitInstance(traitType, instanceID); } } } void AvatarReplicas::processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; for (auto avatar : replicas) { avatar->processTraitInstance(traitType, instanceID, traitBinaryData); } } } 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); connect(nodeList.data(), &NodeList::nodeKilled, this, [this](SharedNodePointer killedNode){ if (killedNode->getType() == NodeType::AvatarMixer) { clearOtherAvatars(); } }); } QVector AvatarHashMap::getAvatarIdentifiers() { QReadLocker locker(&_hashLock); return _avatarHash.keys().toVector(); } QVector AvatarHashMap::getAvatarsInRange(const glm::vec3& position, float rangeMeters) const { auto hashCopy = getHashCopy(); QVector avatarsInRange; auto rangeMetersSquared = rangeMeters * rangeMeters; for (const AvatarSharedPointer& sharedAvatar : hashCopy) { glm::vec3 avatarPosition = sharedAvatar->getWorldPosition(); auto distanceSquared = glm::distance2(avatarPosition, position); if (distanceSquared < rangeMetersSquared) { avatarsInRange.push_back(sharedAvatar->getSessionUUID()); } } return avatarsInRange; } bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range) { auto hashCopy = getHashCopy(); foreach(const AvatarSharedPointer& sharedAvatar, hashCopy) { glm::vec3 avatarPosition = sharedAvatar->getWorldPosition(); float distance = glm::distance(avatarPosition, position); if (distance < range) { return true; } } return false; } void AvatarHashMap::setReplicaCount(int count) { _replicas.setReplicaCount(count); auto avatars = getAvatarIdentifiers(); for (int i = 0; i < avatars.size(); i++) { KillAvatarReason reason = KillAvatarReason::NoReason; if (avatars[i] != QUuid()) { removeAvatar(avatars[i], reason); auto replicaIDs = _replicas.getReplicaIDs(avatars[i]); for (auto id : replicaIDs) { removeAvatar(id, reason); } } } } int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) { auto hashCopy = getHashCopy(); auto rangeMeters2 = rangeMeters * rangeMeters; int count = 0; for (const AvatarSharedPointer& sharedAvatar : hashCopy) { glm::vec3 avatarPosition = sharedAvatar->getWorldPosition(); auto distance2 = glm::distance2(avatarPosition, position); if (distance2 < rangeMeters2) { ++count; } } return count; } AvatarSharedPointer AvatarHashMap::newSharedAvatar() { return std::make_shared(); } AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; auto avatar = newSharedAvatar(); avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); // addAvatar is only called from newOrExistingAvatar, which already locks _hashLock _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); return avatar; } AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer, bool& isNew) { QWriteLocker locker(&_hashLock); auto avatar = _avatarHash.value(sessionUUID); if (!avatar) { avatar = addAvatar(sessionUUID, mixerWeakPointer); isNew = true; } else { isNew = false; } return avatar; } AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const { QReadLocker locker(&_hashLock); if (_avatarHash.contains(sessionUUID)) { return _avatarHash.value(sessionUUID); } return nullptr; } void AvatarHashMap::processAvatarDataPacket(QSharedPointer message, SharedNodePointer sendingNode) { DETAILED_PROFILE_RANGE(network, __FUNCTION__); 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()) { parseAvatarData(message, sendingNode); } } AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode) { QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); int positionBeforeRead = message->getPosition(); QByteArray byteArray = message->readWithoutCopy(message->getBytesLeftToRead()); // make sure this isn't our own avatar data or for a previously ignored node auto nodeList = DependencyManager::get(); bool isNewAvatar; if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) { auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar); if (isNewAvatar) { QWriteLocker locker(&_hashLock); _pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar }); auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); for (auto replicaID : replicaIDs) { auto replicaAvatar = addAvatar(replicaID, sendingNode); _replicas.addReplica(sessionUUID, replicaAvatar); } } // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); _replicas.parseDataFromBuffer(sessionUUID, byteArray); return avatar; } else { // create a dummy AvatarData class to throw this data on the ground AvatarData dummyData; int bytesRead = dummyData.parseDataFromBuffer(byteArray); message->seek(positionBeforeRead + bytesRead); return std::make_shared(); } } void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer message, SharedNodePointer sendingNode) { // peek the avatar UUID from the incoming packet QUuid identityUUID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID)); if (identityUUID.isNull()) { qCDebug(avatars) << "Refusing to process identity packet for null avatar ID"; return; } // make sure this isn't for an ignored avatar auto nodeList = DependencyManager::get(); static auto EMPTY = QUuid(); { QReadLocker locker(&_hashLock); _pendingAvatars.remove(identityUUID); auto me = _avatarHash.find(EMPTY); if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), // we make things match here. identityUUID = EMPTY; } } if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { // mesh URL for a UUID, find avatar in our list bool isNewAvatar; auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); bool identityChanged = false; bool displayNameChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); _replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged); } } 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 bool isNewAvatar; auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar); // read the first trait type for this avatar AvatarTraits::TraitType traitType; message->readPrimitive(&traitType); // grab the last trait versions for this avatar auto& lastProcessedVersions = _processedTraitVersions[avatarID]; while (traitType != AvatarTraits::NullTrait) { AvatarTraits::TraitVersion packetTraitVersion; message->readPrimitive(&packetTraitVersion); AvatarTraits::TraitWireSize traitBinarySize; bool skipBinaryTrait = false; if (AvatarTraits::isSimpleTrait(traitType)) { message->readPrimitive(&traitBinarySize); // check if this trait version is newer than what we already have for this avatar if (packetTraitVersion > lastProcessedVersions[traitType]) { auto traitData = message->read(traitBinarySize); avatar->processTrait(traitType, traitData); _replicas.processTrait(avatarID, traitType, traitData); lastProcessedVersions[traitType] = packetTraitVersion; } else { skipBinaryTrait = true; } } else { AvatarTraits::TraitInstanceID traitInstanceID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); message->readPrimitive(&traitBinarySize); auto& processedInstanceVersion = lastProcessedVersions.getInstanceValueRef(traitType, traitInstanceID); if (packetTraitVersion > processedInstanceVersion) { if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) { avatar->processDeletedTraitInstance(traitType, traitInstanceID); _replicas.processDeletedTraitInstance(avatarID, traitType, traitInstanceID); } else { auto traitData = message->read(traitBinarySize); avatar->processTraitInstance(traitType, traitInstanceID, traitData); _replicas.processTraitInstance(avatarID, traitType, traitInstanceID, traitData); } processedInstanceVersion = packetTraitVersion; } else { skipBinaryTrait = true; } } if (skipBinaryTrait) { // we didn't read this trait because it was older or because we didn't have an avatar to process it for 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); } } } void AvatarHashMap::processKillAvatar(QSharedPointer message, SharedNodePointer sendingNode) { // read the node id QUuid sessionUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); KillAvatarReason reason; message->readPrimitive(&reason); removeAvatar(sessionUUID, reason); auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); for (auto id : replicaIDs) { removeAvatar(id, reason); } } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { std::vector removedAvatars; { QWriteLocker locker(&_hashLock); auto replicas = _replicas.takeReplicas(sessionUUID); for (auto& replica : replicas) { auto removedReplica = _avatarHash.take(replica->getID()); if (removedReplica) { removedAvatars.push_back(removedReplica); } } _pendingAvatars.remove(sessionUUID); auto removedAvatar = _avatarHash.take(sessionUUID); if (removedAvatar) { removedAvatars.push_back(removedAvatar); } } for (auto& removedAvatar: removedAvatars) { handleRemovedAvatar(removedAvatar, removalReason); } } void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { // remove any information about processed traits for this avatar _processedTraitVersions.erase(removedAvatar->getID()); qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID()) << "from AvatarHashMap" << removalReason; emit avatarRemovedEvent(removedAvatar->getSessionUUID()); } void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID) { _lastOwnerSessionUUID = oldUUID; emit avatarSessionChangedEvent(sessionUUID, oldUUID); } void AvatarHashMap::clearOtherAvatars() { QList removedAvatars; { QWriteLocker locker(&_hashLock); // grab a copy of the current avatars so we can call handleRemoveAvatar for them removedAvatars = _avatarHash.values(); _avatarHash.clear(); } for (auto& av : removedAvatars) { handleRemovedAvatar(av); } }