Merge branch 'master' of https://github.com/highfidelity/hifi into removeLogSpam

Conflicts:
	libraries/networking/src/NodeList.cpp
This commit is contained in:
Brad Hefta-Gaub 2016-12-20 10:46:57 -08:00
commit 9b11c7cc9c
14 changed files with 111 additions and 29 deletions

View file

@ -16,6 +16,7 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QDateTime>
#include <QtCore/QJsonObject>
#include <QtCore/QRegularExpression>
#include <QtCore/QTimer>
#include <QtCore/QThread>
@ -27,7 +28,6 @@
#include <UUID.h>
#include <TryLocker.h>
#include "AvatarMixerClientData.h"
#include "AvatarMixer.h"
const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
@ -67,6 +67,20 @@ AvatarMixer::~AvatarMixer() {
// 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();
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122());
identityPacket->write(individualData);
DependencyManager::get<NodeList>()->sendPacket(std::move(identityPacket), *destinationNode);
++_sumIdentityPackets;
}
// 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.
@ -227,6 +241,27 @@ void AvatarMixer::broadcastAvatarData() {
// setup a PacketList for the avatarPackets
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
if (avatar.getSessionDisplayName().isEmpty() && // We haven't set it yet...
nodeData->getReceivedIdentity()) { // ... but we have processed identity (with possible displayName).
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<int, int>& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want.
int& highWater = soFar.first;
nodeData->setBaseDisplayName(baseName);
avatar.setSessionDisplayName((highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName);
highWater++;
soFar.second++; // refcount
nodeData->flagIdentityChange();
sendIdentityPacket(nodeData, node); // Tell new node about its sessionUUID. Others will find out below.
}
// this is an AGENT we have received head data from
// send back a packet with other active node data to this node
nodeList->eachMatchingNode(
@ -295,17 +330,7 @@ void AvatarMixer::broadcastAvatarData() {
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|| distribution(generator) < IDENTITY_SEND_PROBABILITY)) {
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
identityPacket->write(individualData);
nodeList->sendPacket(std::move(identityPacket), *node);
++_sumIdentityPackets;
sendIdentityPacket(otherNodeData, node);
}
AvatarData& otherAvatar = otherNodeData->getAvatar();
@ -416,6 +441,16 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
&& killedNode->getLinkedData()) {
auto nodeList = DependencyManager::get<NodeList>();
{ // decrement sessionDisplayNames table and possibly remove
QMutexLocker nodeDataLocker(&killedNode->getLinkedData()->getMutex());
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(killedNode->getLinkedData());
const QString& baseDisplayName = nodeData->getBaseDisplayName();
// No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case.
if (--_sessionDisplayNames[baseDisplayName].second <= 0) {
_sessionDisplayNames.remove(baseDisplayName);
}
}
// this was an avatar we were sending to other people
// send a kill packet for it to our other nodes
auto killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
@ -468,6 +503,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
if (avatar.processAvatarIdentity(identity)) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->flagIdentityChange();
nodeData->setReceivedIdentity();
}
}
}
@ -564,7 +600,7 @@ void AvatarMixer::domainSettingsRequestComplete() {
float domainMaximumScale = _domainMaximumScale;
nodeList->linkedDataCreateCallback = [domainMinimumScale, domainMaximumScale] (Node* node) {
auto clientData = std::unique_ptr<AvatarMixerClientData> { new AvatarMixerClientData };
auto clientData = std::unique_ptr<AvatarMixerClientData> { new AvatarMixerClientData(node->getUUID()) };
clientData->getAvatar().setDomainMinimumScale(domainMinimumScale);
clientData->getAvatar().setDomainMaximumScale(domainMaximumScale);

View file

@ -18,6 +18,7 @@
#include <PortableHighResolutionClock.h>
#include <ThreadedAssignment.h>
#include "AvatarMixerClientData.h"
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
class AvatarMixer : public ThreadedAssignment {
@ -46,6 +47,7 @@ private slots:
private:
void broadcastAvatarData();
void parseDomainServerSettings(const QJsonObject& domainSettings);
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
QThread _broadcastThread;
@ -64,6 +66,8 @@ private:
float _domainMaximumScale { MAX_AVATAR_SCALE };
QTimer* _broadcastTimer = nullptr;
QHash<QString, QPair<int, int>> _sessionDisplayNames;
};
#endif // hifi_AvatarMixer_h

View file

@ -34,6 +34,8 @@ const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
class AvatarMixerClientData : public NodeData {
Q_OBJECT
public:
AvatarMixerClientData(const QUuid& nodeID = QUuid()) : NodeData(nodeID) {}
virtual ~AvatarMixerClientData() {}
using HRCTime = p_high_resolution_clock::time_point;
int parseData(ReceivedMessage& message) override;
@ -50,6 +52,8 @@ public:
HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); }
bool getReceivedIdentity() const { return _gotIdentity; }
void setReceivedIdentity() { _gotIdentity = true; }
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
float getFullRateDistance() const { return _fullRateDistance; }
@ -87,6 +91,9 @@ public:
void removeFromRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.erase(other); }
void ignoreOther(SharedNodePointer self, SharedNodePointer other);
const QString& getBaseDisplayName() { return _baseDisplayName; }
void setBaseDisplayName(const QString& baseDisplayName) { _baseDisplayName = baseDisplayName; }
private:
AvatarSharedPointer _avatar { new AvatarData() };
@ -95,6 +102,7 @@ private:
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
HRCTime _identityChangeTimestamp;
bool _gotIdentity { false };
float _fullRateDistance = FLT_MAX;
float _maxAvatarDistance = FLT_MAX;
@ -108,6 +116,8 @@ private:
SimpleMovingAverage _avgOtherAvatarDataRate;
std::unordered_set<QUuid> _radiusIgnoredOthers;
QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary.
};
#endif // hifi_AvatarMixerClientData_h

View file

@ -800,7 +800,12 @@ void DomainServerSettingsManager::processUsernameFromIDRequestPacket(QSharedPoin
usernameFromIDReplyPacket->write(nodeUUID.toRfc4122());
usernameFromIDReplyPacket->writeString(verifiedUsername);
qDebug() << "Sending username" << verifiedUsername << "associated with node" << nodeUUID;
// now put in the machine fingerprint
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
QUuid machineFingerprint = nodeData ? nodeData->getMachineFingerprint() : QUuid();
usernameFromIDReplyPacket->write(machineFingerprint.toRfc4122());
qDebug() << "Sending username" << verifiedUsername << "and machine fingerprint" << machineFingerprint << "associated with node" << nodeUUID;
// Ship it!
limitedNodeList->sendPacket(std::move(usernameFromIDReplyPacket), *sendingNode);

View file

@ -119,6 +119,7 @@ public:
virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData) override;
void setShowDisplayName(bool showDisplayName);
virtual void setSessionDisplayName(const QString& sessionDisplayName) override { }; // no-op
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
@ -189,6 +190,10 @@ public slots:
protected:
friend class AvatarManager;
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
QString _empty{};
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
void setMotionState(AvatarMotionState* motionState);
SkeletonModelPointer _skeletonModel;

View file

@ -984,7 +984,7 @@ 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.avatarEntityData;
packetStream >> identityOut.uuid >> identityOut.skeletonModelURL >> identityOut.attachmentData >> identityOut.displayName >> identityOut.sessionDisplayName >> identityOut.avatarEntityData;
}
static const QUrl emptyURL("");
@ -1006,6 +1006,7 @@ bool AvatarData::processAvatarIdentity(const Identity& identity) {
setDisplayName(identity.displayName);
hasIdentityChanged = true;
}
maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName);
if (identity.attachmentData != _attachmentData) {
setAttachmentData(identity.attachmentData);
@ -1030,7 +1031,7 @@ QByteArray AvatarData::identityByteArray() {
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL);
_avatarEntitiesLock.withReadLock([&] {
identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << _avatarEntityData;
identityStream << getSessionUUID() << urlToSend << _attachmentData << _displayName << getSessionDisplayNameForTransport() << _avatarEntityData;
});
return identityData;
@ -1385,6 +1386,7 @@ static const QString JSON_AVATAR_HEAD = QStringLiteral("head");
static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel");
static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel");
static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName");
// It isn't meaningful to persist sessionDisplayName.
static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments");
static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
static const QString JSON_AVATAR_SCALE = QStringLiteral("scale");

View file

@ -171,6 +171,9 @@ class AvatarData : public QObject, public SpatiallyNestable {
Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness)
Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName)
// sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients.
// The result is unique among all avatars present at the time.
Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName)
Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript)
Q_PROPERTY(QVector<AttachmentData> attachmentData READ getAttachmentData WRITE setAttachmentData)
@ -313,6 +316,7 @@ public:
QUrl skeletonModelURL;
QVector<AttachmentData> attachmentData;
QString displayName;
QString sessionDisplayName;
AvatarEntityMap avatarEntityData;
};
@ -325,9 +329,11 @@ public:
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
const QString& getDisplayName() const { return _displayName; }
const QString& getSessionDisplayName() const { return _sessionDisplayName; }
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
virtual void setDisplayName(const QString& displayName);
virtual void setSessionDisplayName(const QString& sessionDisplayName) { _sessionDisplayName = sessionDisplayName; };
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
Q_INVOKABLE virtual void setAttachmentData(const QVector<AttachmentData>& attachmentData);
@ -390,6 +396,8 @@ public slots:
protected:
glm::vec3 _handPosition;
virtual const QString& getSessionDisplayNameForTransport() const { return _sessionDisplayName; }
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) { } // No-op in AvatarMixer
// Body scale
float _targetScale;
@ -417,6 +425,7 @@ protected:
QUrl _skeletonFBXURL;
QVector<AttachmentData> _attachmentData;
QString _displayName;
QString _sessionDisplayName { };
const QUrl& cannonicalSkeletonModelURL(const QUrl& empty);
float _displayNameTargetAlpha;

View file

@ -133,6 +133,13 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
// make sure this isn't for an ignored avatar
auto nodeList = DependencyManager::get<NodeList>();
static auto EMPTY = QUuid();
if (identity.uuid == _avatarHash.value(EMPTY)->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.
identity.uuid = EMPTY;
}
if (!nodeList->isIgnoringNode(identity.uuid)) {
// mesh URL for a UUID, find avatar in our list
auto avatar = newOrExistingAvatar(identity.uuid, sendingNode);

View file

@ -923,8 +923,10 @@ void NodeList::processUsernameFromIDReply(QSharedPointer<ReceivedMessage> messag
QString nodeUUIDString = (QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID))).toString();
// read the username from the packet
QString username = message->readString();
// read the machine fingerprint from the packet
QString machineFingerprintString = (QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID))).toString();
qCDebug(networking) << "Got username" << username << "for node" << nodeUUIDString;
qCDebug(networking) << "Got username" << username << "and machine fingerprint" << machineFingerprintString << "for node" << nodeUUIDString;
emit usernameFromIDReply(nodeUUIDString, username);
emit usernameFromIDReply(nodeUUIDString, username, machineFingerprintString);
}

View file

@ -111,7 +111,7 @@ signals:
void receivedDomainServerList();
void ignoredNode(const QUuid& nodeID);
void ignoreRadiusEnabledChanged(bool isIgnored);
void usernameFromIDReply(const QString& nodeID, const QString& username);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
private slots:
void stopKeepalivePingTimer();

View file

@ -44,7 +44,7 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
PacketVersion versionForPacketType(PacketType packetType) {
switch (packetType) {
case PacketType::DomainList:
return static_cast<PacketVersion>(DomainListVersion::GetUsernameFromUUIDSupport);
return static_cast<PacketVersion>(DomainListVersion::GetMachineFingerprintFromUUIDSupport);
case PacketType::EntityAdd:
case PacketType::EntityEdit:
case PacketType::EntityData:
@ -53,7 +53,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::HasKillAvatarReason);
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SessionDisplayName);
case PacketType::ICEServerHeartbeat:
return 18; // ICE Server Heartbeat signing
case PacketType::AssetGetInfo:

View file

@ -205,7 +205,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
AbsoluteSixByteRotations,
SensorToWorldMat,
HandControllerJoints,
HasKillAvatarReason
HasKillAvatarReason,
SessionDisplayName
};
enum class DomainConnectRequestVersion : PacketVersion {
@ -230,7 +231,8 @@ enum class DomainServerAddedNodeVersion : PacketVersion {
enum class DomainListVersion : PacketVersion {
PrePermissionsGrid = 18,
PermissionsGrid,
GetUsernameFromUUIDSupport
GetUsernameFromUUIDSupport,
GetMachineFingerprintFromUUIDSupport
};
enum class AudioVersion : PacketVersion {

View file

@ -101,10 +101,10 @@ signals:
void enteredIgnoreRadius();
/**jsdoc
* Notifies scripts of the username associated with a UUID.
* @function Users.enteredIgnoreRadius
* Notifies scripts of the username and machine fingerprint associated with a UUID.
* @function Users.usernameFromIDReply
*/
void usernameFromIDReply(const QString& nodeID, const QString& username);
void usernameFromIDReply(const QString& nodeID, const QString& username, const QString& machineFingerprint);
};

View file

@ -117,7 +117,7 @@ function populateUserList() {
AvatarList.getAvatarIdentifiers().sort().forEach(function (id) { // sorting the identifiers is just an aid for debugging
var avatar = AvatarList.getAvatar(id);
var avatarPalDatum = {
displayName: avatar.displayName || ('anonymous ' + counter++),
displayName: avatar.sessionDisplayName,
userName: '',
sessionId: id || ''
};
@ -137,7 +137,7 @@ function populateUserList() {
}
// The function that handles the reply from the server
function usernameFromIDReply(id, username) {
function usernameFromIDReply(id, username, machineFingerprint) {
var data;
// If the ID we've received is our ID...
if (AvatarList.getAvatar('').sessionUUID === id) {
@ -145,7 +145,7 @@ function usernameFromIDReply(id, username) {
data = ['', username + ' (hidden)']
} else {
// Set the data to contain the ID and the username+ID concat string.
data = [id, username + '/' + id];
data = [id, username + '/' + machineFingerprint];
}
print('Username Data:', JSON.stringify(data));
// Ship the data off to QML