From 245150333b949200a11bb40e8b1d840fe8e66353 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 13 Nov 2015 18:08:18 -0800 Subject: [PATCH] Fix for missing avatars on entry This should fix the issue where a persons avatar was occasionally not visible to others for a long period of time. This was due to several factors: 1) When a new avatar was added to the simulation that identity packet was not broadcast to other avatars on the server. This would rely on random logic to send the identity eventually. This is fixed in this PR, by sending identity packets to all other clients when a new clients arrive. 2) The random identity logic was not being executed if the sequence number check caused an update to be skipped. This means the probability of sending a random packet was reduced significantly, especially for clients that were loading geometry on entry. This was fixed by doing the random check before sequence number check. 3) The 1/300 probably used in the check was too low, this was only a 63% chance of sending a identity packet within 5 seconds. This was fixed by changing the probability to 1/187, which is a 80% chance to send an identity packet within 5 seconds. 4) The randFloat() implementation slightly reduced the identity packet probability due to quantization errors. This has been replaced by a C++ std random number generator. --- assignment-client/src/avatars/AvatarMixer.cpp | 101 +++++++++--------- .../src/avatars/AvatarMixerClientData.cpp | 14 ++- .../src/avatars/AvatarMixerClientData.h | 25 +++-- 3 files changed, 76 insertions(+), 64 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0a455891f9..74641e9387 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -63,7 +63,9 @@ AvatarMixer::~AvatarMixer() { _broadcastThread.wait(); } -const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f; +// An 80% chance of sending a identity packet within a 5 second interval. +// assuming 60 htz update rate. +const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f; // 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 @@ -243,6 +245,47 @@ void AvatarMixer::broadcastAvatarData() { return; } + // if an avatar has just connected make sure we send out the mesh and billboard + bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets() + || !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID()); + + // we will also force a send of billboard or identity packet + // if either has changed in the last frame + if (otherNodeData->getBillboardChangeTimestamp() > 0 + && (forceSend + || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp + || distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { + + QByteArray rfcUUID = otherNode->getUUID().toRfc4122(); + QByteArray billboard = otherNodeData->getAvatar().getBillboard(); + + auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size()); + billboardPacket->write(rfcUUID); + billboardPacket->write(billboard); + + nodeList->sendPacket(std::move(billboardPacket), *node); + + ++_sumBillboardPackets; + } + + if (otherNodeData->getIdentityChangeTimestamp() > 0 + && (forceSend + || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp + || distribution(generator) < BILLBOARD_AND_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; + } + AvatarData& otherAvatar = otherNodeData->getAvatar(); // Decide whether to send this avatar's data based on it's distance from us @@ -254,10 +297,10 @@ void AvatarMixer::broadcastAvatarData() { // potentially update the max full rate distance for this frame maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); - if (distanceToAvatar != 0.0f + if (distanceToAvatar != 0.0f && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { - return; - } + return; + } AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); @@ -291,53 +334,11 @@ void AvatarMixer::broadcastAvatarData() { numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += - avatarPacketList->write(otherAvatar.toByteArray(false, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO)); + avatarPacketList->write(otherAvatar.toByteArray(false, distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO)); avatarPacketList->endSegment(); - - // if the receiving avatar has just connected make sure we send out the mesh and billboard - // for this avatar (assuming they exist) - bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets(); - - // we will also force a send of billboard or identity packet - // if either has changed in the last frame - - if (otherNodeData->getBillboardChangeTimestamp() > 0 - && (forceSend - || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp - || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { - - QByteArray rfcUUID = otherNode->getUUID().toRfc4122(); - QByteArray billboard = otherNodeData->getAvatar().getBillboard(); - - auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size()); - billboardPacket->write(rfcUUID); - billboardPacket->write(billboard); - - nodeList->sendPacket(std::move(billboardPacket), *node); - - ++_sumBillboardPackets; - } - - if (otherNodeData->getIdentityChangeTimestamp() > 0 - && (forceSend - || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp - || randFloat() < BILLBOARD_AND_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; - } }); - + // close the current packet so that we're always sending something avatarPacketList->closeCurrentPacket(true); @@ -484,7 +485,7 @@ void AvatarMixer::sendStatsPacket() { // add the key to ask the domain-server for a username replacement, if it has it avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID()); - + avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth(); avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundBandwidth(); @@ -537,7 +538,7 @@ void AvatarMixer::run() { qDebug() << "Waiting for domain settings from domain-server."; // block until we get the settingsRequestComplete signal - + QEventLoop loop; connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 0a9be20691..bfa7b99b68 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,7 +16,7 @@ int AvatarMixerClientData::parseData(NLPacket& packet) { // pull the sequence number from the data first packet.readPrimitive(&_lastReceivedSequenceNumber); - + // compute the offset to the data payload return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); } @@ -27,6 +27,14 @@ bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() { return oldValue; } +bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) { + if (_hasReceivedFirstPacketsFrom.find(uuid) == _hasReceivedFirstPacketsFrom.end()) { + _hasReceivedFirstPacketsFrom.insert(uuid); + return false; + } + return true; +} + uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); @@ -45,9 +53,9 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["avg_other_av_starves_per_second"] = getAvgNumOtherAvatarStarvesPerSecond(); jsonObject["avg_other_av_skips_per_second"] = getAvgNumOtherAvatarSkipsPerSecond(); jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; - + jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; - + jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate(); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index e68a54c265..42a2c1d4e4 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -34,25 +35,26 @@ class AvatarMixerClientData : public NodeData { public: int parseData(NLPacket& packet); AvatarData& getAvatar() { return _avatar; } - + bool checkAndSetHasReceivedFirstPackets(); + bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid); uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } - + uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; } void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; } - + quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; } - + void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; } float getFullRateDistance() const { return _fullRateDistance; } - + void setMaxAvatarDistance(float maxAvatarDistance) { _maxAvatarDistance = maxAvatarDistance; } float getMaxAvatarDistance() const { return _maxAvatarDistance; } @@ -73,31 +75,32 @@ public: void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; } void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); } - + float getOutboundAvatarDataKbps() const { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } - + void loadJSONStats(QJsonObject& jsonObject) const; private: AvatarData _avatar; - + uint16_t _lastReceivedSequenceNumber { 0 }; std::unordered_map _lastBroadcastSequenceNumbers; + std::unordered_set _hasReceivedFirstPacketsFrom; bool _hasReceivedFirstPackets = false; quint64 _billboardChangeTimestamp = 0; quint64 _identityChangeTimestamp = 0; - + float _fullRateDistance = FLT_MAX; float _maxAvatarDistance = FLT_MAX; - + int _numAvatarsSentLastFrame = 0; int _numFramesSinceAdjustment = 0; SimpleMovingAverage _otherAvatarStarves; SimpleMovingAverage _otherAvatarSkips; int _numOutOfOrderSends = 0; - + SimpleMovingAverage _avgOtherAvatarDataRate; };