From 954391ae3e832090f1ac4643482f759d08ad4473 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 11 Nov 2015 18:33:26 -0800 Subject: [PATCH 01/11] Fix models resizing When models are MUCH bigger one one axis than the others and we modify the scale only of one of those small axis, it wouldn't propagate because the change was relatively too small conpared to the size of the model --- libraries/render-utils/src/Model.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 5b9bfdca3d..e201893209 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -89,11 +89,7 @@ void Model::setScale(const glm::vec3& scale) { } void Model::setScaleInternal(const glm::vec3& scale) { - float scaleLength = glm::length(_scale); - float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; - - const float ONE_PERCENT = 0.01f; - if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) { + if (glm::distance(_scale, scale) > METERS_PER_MILLIMETER) { _scale = scale; initJointTransforms(); } From 83e7eb58b9a0877450ac1fd867e94f11cb4ddd09 Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 12 Nov 2015 16:36:06 -0800 Subject: [PATCH 02/11] add special packets and nack packets to outbound stats --- .../src/octree/OctreeInboundPacketProcessor.cpp | 6 +++++- assignment-client/src/octree/OctreeSendThread.cpp | 12 +++++++++++- assignment-client/src/octree/OctreeSendThread.h | 3 +++ assignment-client/src/octree/OctreeServer.cpp | 10 ++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 92c08152f7..0cdc7f9921 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -240,6 +240,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() { auto nodeList = DependencyManager::get(); int packetsSent = 0; + int totalBytesSent = 0; NodeToSenderStatsMapIterator i = _singleSenderStats.begin(); while (i != _singleSenderStats.end()) { @@ -291,12 +292,15 @@ int OctreeInboundPacketProcessor::sendNackPackets() { packetsSent += nackPacketList->getNumPackets(); // send the list of nack packets - nodeList->sendPacketList(std::move(nackPacketList), *destinationNode); + totalBytesSent += nodeList->sendPacketList(std::move(nackPacketList), *destinationNode); } ++i; } + OctreeSendThread::_totalPackets += packetsSent; + OctreeSendThread::_totalBytes += totalBytesSent; + return packetsSent; } diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 1906cb7979..1d92ed917f 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -118,6 +118,10 @@ AtomicUIntStat OctreeSendThread::_totalBytes { 0 }; AtomicUIntStat OctreeSendThread::_totalWastedBytes { 0 }; AtomicUIntStat OctreeSendThread::_totalPackets { 0 }; +AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 }; +AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 }; + + int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { OctreeServer::didHandlePacketSend(this); @@ -580,11 +584,17 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets if (_myServer->hasSpecialPacketsToSend(_node) && !nodeData->isShuttingDown()) { - int specialPacketsSent; + int specialPacketsSent = 0; trueBytesSent += _myServer->sendSpecialPackets(_node, nodeData, specialPacketsSent); nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed truePacketsSent += specialPacketsSent; packetsSentThisInterval += specialPacketsSent; + + _totalPackets += specialPacketsSent; + _totalBytes += trueBytesSent; + + _totalSpecialPackets += specialPacketsSent; + _totalSpecialBytes += trueBytesSent; } // Re-send packets that were nacked by the client diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 69ea0be567..6e640942e7 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -38,6 +38,9 @@ public: static AtomicUIntStat _totalWastedBytes; static AtomicUIntStat _totalPackets; + static AtomicUIntStat _totalSpecialBytes; + static AtomicUIntStat _totalSpecialPackets; + static AtomicUIntStat _usleepTime; static AtomicUIntStat _usleepCalls; diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 818d54ee97..ad3df11474 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -415,6 +415,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks(); quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor(); + quint64 totalOutboundSpecialPackets = OctreeSendThread::_totalSpecialPackets; + quint64 totalOutboundSpecialBytes = OctreeSendThread::_totalSpecialBytes; + statsString += QString(" Total Clients Connected: %1 clients\r\n") .arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' ')); @@ -606,6 +609,13 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url .arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Total Outbound Bytes: %1 bytes\r\n") .arg(locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' ')); + + statsString += QString(" Total Outbound Special Packets: %1 packets\r\n") + .arg(locale.toString((uint)totalOutboundSpecialPackets).rightJustified(COLUMN_WIDTH, ' ')); + statsString += QString(" Total Outbound Special Bytes: %1 bytes\r\n") + .arg(locale.toString((uint)totalOutboundSpecialBytes).rightJustified(COLUMN_WIDTH, ' ')); + + statsString += QString(" Total Wasted Bytes: %1 bytes\r\n") .arg(locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString().sprintf(" Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n", From 1fd37b51a2bced38a435128d327e0aa4269388b7 Mon Sep 17 00:00:00 2001 From: samcake Date: Thu, 12 Nov 2015 17:54:35 -0800 Subject: [PATCH 03/11] trying to get somewhere.... --- examples/utilities/record/recorder.js | 2 + interface/src/avatar/MyAvatar.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 67 +++++++++++++++++++-------- libraries/avatars/src/AvatarData.h | 3 ++ 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index 6d49030a28..13ea1e0c72 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -176,6 +176,8 @@ function formatTime(time) { var SEC_PER_MIN = 60; var MSEC_PER_SEC = 1000; + time = time * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); + var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR)); time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5e14c66ff1..4d62946a5f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -608,7 +608,7 @@ float MyAvatar::recorderElapsed() { if (!_recorder) { return 0; } - return (float)_recorder->position() / MSECS_PER_SECOND; + return (float)_recorder->position(); } QMetaObject::Connection _audioClientRecorderConnection; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index adf471c50e..e2d2c05ef6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -804,12 +804,12 @@ float AvatarData::playerElapsed() { return 0; } if (QThread::currentThread() != thread()) { - qint64 result; + float result; QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(qint64, result)); + Q_RETURN_ARG(float, result)); return result; } - return (float)_player->position() / MSECS_PER_SECOND; + return (float)_player->position(); } float AvatarData::playerLength() { @@ -817,12 +817,12 @@ float AvatarData::playerLength() { return 0; } if (QThread::currentThread() != thread()) { - qint64 result; + float result; QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection, - Q_RETURN_ARG(qint64, result)); + Q_RETURN_ARG(float, result)); return result; } - return _player->length() / MSECS_PER_SECOND; + return _player->length(); } void AvatarData::loadRecording(const QString& filename) { @@ -870,7 +870,7 @@ void AvatarData::setPlayerTime(float time) { return; } - _player->seek(time * MSECS_PER_SECOND); + _player->seek(time); } void AvatarData::setPlayFromCurrentLocation(bool playFromCurrentLocation) { @@ -1532,7 +1532,7 @@ Transform AvatarData::getTransform() const { static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform"); static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform"); -static const QString JSON_AVATAR_JOINT_ROTATIONS = QStringLiteral("jointRotations"); +static const QString JSON_AVATAR_JOINT_ARRAY = QStringLiteral("jointArray"); static const QString JSON_AVATAR_HEAD = QStringLiteral("head"); static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation"); static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes"); @@ -1544,6 +1544,24 @@ static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); +QJsonValue toJsonValue(const JointData& joint) { + QJsonArray result; + result.push_back(toJsonValue(joint.rotation)); + result.push_back(toJsonValue(joint.translation)); + return result; +} + +JointData jointDataFromJsonValue(const QJsonValue& json) { + JointData result; + if (json.isArray()) { + QJsonArray array = json.toArray(); + result.rotation = quatFromJsonValue(array[0]); + result.rotationSet = true; + result.translation = vec3FromJsonValue(array[1]); + result.translationSet = false; + } + return result; +} // Every frame will store both a basis for the recording and a relative transform // This allows the application to decide whether playback should be relative to an avatar's @@ -1575,13 +1593,16 @@ QByteArray avatarStateToFrame(const AvatarData* _avatar) { root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform); root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); } + } else { + root[JSON_AVATAR_RELATIVE] = Transform::toJson(_avatar->getTransform()); } - QJsonArray jointRotations; - for (const auto& jointRotation : _avatar->getJointRotations()) { - jointRotations.push_back(toJsonValue(jointRotation)); + // Skeleton pose + QJsonArray jointArray; + for (const auto& joint : _avatar->getRawJointData()) { + jointArray.push_back(toJsonValue(joint)); } - root[JSON_AVATAR_JOINT_ROTATIONS] = jointRotations; + root[JSON_AVATAR_JOINT_ARRAY] = jointArray; const HeadData* head = _avatar->getHeadData(); if (head) { @@ -1646,21 +1667,29 @@ void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar) { _avatar->setTargetScale(worldTransform.getScale().x); } -#if 0 + if (root.contains(JSON_AVATAR_ATTACHEMENTS)) { // FIXME de-serialize attachment data } // Joint rotations are relative to the avatar, so they require no basis correction - if (root.contains(JSON_AVATAR_JOINT_ROTATIONS)) { - QVector jointRotations; - QJsonArray jointRotationsJson = root[JSON_AVATAR_JOINT_ROTATIONS].toArray(); - jointRotations.reserve(jointRotationsJson.size()); - for (const auto& jointRotationJson : jointRotationsJson) { - jointRotations.push_back(quatFromJsonValue(jointRotationJson)); + if (root.contains(JSON_AVATAR_JOINT_ARRAY)) { + QVector jointArray; + QJsonArray jointArrayJson = root[JSON_AVATAR_JOINT_ARRAY].toArray(); + jointArray.reserve(jointArrayJson.size()); + for (const auto& jointJson : jointArrayJson) { + jointArray.push_back(jointDataFromJsonValue(jointJson)); } + + QVector jointRotations; + jointRotations.reserve(jointArray.size()); + for (const auto& joint : jointArray) { + jointRotations.push_back(joint.rotation); + } + _avatar->setJointRotations(jointRotations); } +#if 0 // Most head data is relative to the avatar, and needs no basis correction, // but the lookat vector does need correction HeadData* head = _avatar->_headData; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 7716a16a1c..26bc9d83ff 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -457,6 +457,9 @@ public: bool translationSet = false; }; +QJsonValue toJsonValue(const JointData& joint); +JointData jointDataFromJsonValue(const QJsonValue& q); + class AttachmentData { public: QUrl modelURL; From ecaa50c0ff82de10c2714b26cf0fac8c1a51cb9b Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 12 Nov 2015 18:25:18 -0800 Subject: [PATCH 04/11] fix for infinite loop in erase entities special packet --- .../src/entities/EntityServer.cpp | 114 ++++++++++++++++-- libraries/entities/src/EntityTree.cpp | 89 +------------- libraries/entities/src/EntityTree.h | 17 ++- 3 files changed, 125 insertions(+), 95 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 493a16fea4..4448a3c5c3 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -101,24 +101,121 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN EntityNodeData* nodeData = static_cast(node->getLinkedData()); if (nodeData) { + quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); + quint64 considerEntitiesSince = EntityTree::getAdjustedConsiderSince(deletedEntitiesSentAt); + quint64 deletePacketSentAt = usecTimestampNow(); EntityTreePointer tree = std::static_pointer_cast(_tree); + auto recentlyDeleted = tree->getRecentlyDeletedEntityIDs(); bool hasMoreToSend = true; packetsSent = 0; - while (hasMoreToSend) { - auto specialPacket = tree->encodeEntitiesDeletedSince(queryNode->getSequenceNumber(), deletedEntitiesSentAt, - hasMoreToSend); + // create a new special packet + std::unique_ptr deletesPacket = NLPacket::create(PacketType::EntityErase); - queryNode->packetSent(*specialPacket); + // pack in flags + OCTREE_PACKET_FLAGS flags = 0; + deletesPacket->writePrimitive(flags); - totalBytes += specialPacket->getDataSize(); - packetsSent++; + // pack in sequence number + auto sequenceNumber = queryNode->getSequenceNumber(); + deletesPacket->writePrimitive(sequenceNumber); - DependencyManager::get()->sendPacket(std::move(specialPacket), *node); - } + // pack in timestamp + OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); + deletesPacket->writePrimitive(now); + + // figure out where we are now and pack a temporary number of IDs + uint16_t numberOfIDs = 0; + qint64 numberOfIDsPos = deletesPacket->pos(); + deletesPacket->writePrimitive(numberOfIDs); + + // we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been + // deleted since we last sent to this node + auto it = recentlyDeleted.constBegin(); + while (it != recentlyDeleted.constEnd()) { + + // if the timestamp is more recent then out last sent time, include it + if (it.key() > considerEntitiesSince) { + + // get all the IDs for this timestamp + const auto& entityIDsFromTime = recentlyDeleted.values(it.key()); + + for (const auto& entityID : entityIDsFromTime) { + + // check to make sure we have room for one more ID, if we don't have more + // room, then send out this packet and create another one + if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) { + + // replace the count for the number of included IDs + deletesPacket->seek(numberOfIDsPos); + deletesPacket->writePrimitive(numberOfIDs); + + // Send the current packet + queryNode->packetSent(*deletesPacket); + auto thisPacketSize = deletesPacket->getDataSize(); + totalBytes += thisPacketSize; + packetsSent++; + DependencyManager::get()->sendPacket(std::move(deletesPacket), *node); + + #ifdef EXTRA_ERASE_DEBUGGING + qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize; + #endif + + + // create another packet + deletesPacket = NLPacket::create(PacketType::EntityErase); + + // pack in flags + deletesPacket->writePrimitive(flags); + + // pack in sequence number + sequenceNumber = queryNode->getSequenceNumber(); + deletesPacket->writePrimitive(sequenceNumber); + + // pack in timestamp + deletesPacket->writePrimitive(now); + + // figure out where we are now and pack a temporary number of IDs + numberOfIDs = 0; + numberOfIDsPos = deletesPacket->pos(); + deletesPacket->writePrimitive(numberOfIDs); + } + + // FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server + // to the client. These were causing "lost" entities like flashlights and laser pointers + // now that we keep around some additional history of the erased entities and resend that + // history for a longer time window, these entities are not "lost". But we haven't yet + // found/fixed the underlying issue that caused bad UUIDs to be sent to some users. + deletesPacket->write(entityID.toRfc4122()); + ++numberOfIDs; + + #ifdef EXTRA_ERASE_DEBUGGING + qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID; + #endif + } // end for (ids) + + } // end if (it.val > sinceLast) + + + ++it; + } // end while + + // replace the count for the number of included IDs + deletesPacket->seek(numberOfIDsPos); + deletesPacket->writePrimitive(numberOfIDs); + + // Send the current packet + queryNode->packetSent(*deletesPacket); + auto thisPacketSize = deletesPacket->getDataSize(); + totalBytes += thisPacketSize; + packetsSent++; + DependencyManager::get()->sendPacket(std::move(deletesPacket), *node); + #ifdef EXTRA_ERASE_DEBUGGING + qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize; + #endif nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt); } @@ -134,6 +231,7 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN return totalBytes; } + void EntityServer::pruneDeletedEntities() { EntityTreePointer tree = std::static_pointer_cast(_tree); if (tree->hasAnyDeletedEntities()) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0174dbe39b..c2f635c95f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -801,8 +801,13 @@ void EntityTree::update() { } } +quint64 EntityTree::getAdjustedConsiderSince(quint64 sinceTime) { + return (sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER); +} + + bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { - quint64 considerEntitiesSince = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER; + quint64 considerEntitiesSince = getAdjustedConsiderSince(sinceTime); // we can probably leverage the ordered nature of QMultiMap to do this quickly... bool hasSomethingNewer = false; @@ -829,88 +834,6 @@ bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { return hasSomethingNewer; } -// sinceTime is an in/out parameter - it will be side effected with the last time sent out -std::unique_ptr EntityTree::encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, - bool& hasMore) { - quint64 considerEntitiesSince = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER; - auto deletesPacket = NLPacket::create(PacketType::EntityErase); - - // pack in flags - OCTREE_PACKET_FLAGS flags = 0; - deletesPacket->writePrimitive(flags); - - // pack in sequence number - deletesPacket->writePrimitive(sequenceNumber); - - // pack in timestamp - OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); - deletesPacket->writePrimitive(now); - - // figure out where we are now and pack a temporary number of IDs - uint16_t numberOfIDs = 0; - qint64 numberOfIDsPos = deletesPacket->pos(); - deletesPacket->writePrimitive(numberOfIDs); - - // we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been - // deleted since we last sent to this node - { - QReadLocker locker(&_recentlyDeletedEntitiesLock); - - bool hasFilledPacket = false; - - auto it = _recentlyDeletedEntityItemIDs.constBegin(); - while (it != _recentlyDeletedEntityItemIDs.constEnd()) { - QList values = _recentlyDeletedEntityItemIDs.values(it.key()); - for (int valueItem = 0; valueItem < values.size(); ++valueItem) { - - // if the timestamp is more recent then out last sent time, include it - if (it.key() > considerEntitiesSince) { - QUuid entityID = values.at(valueItem); - - // FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server - // to the client. These were causing "lost" entities like flashlights and laser pointers - // now that we keep around some additional history of the erased entities and resend that - // history for a longer time window, these entities are not "lost". But we haven't yet - // found/fixed the underlying issue that caused bad UUIDs to be sent to some users. - deletesPacket->write(entityID.toRfc4122()); - ++numberOfIDs; - - #ifdef EXTRA_ERASE_DEBUGGING - qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID; - #endif - - // check to make sure we have room for one more ID - if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) { - hasFilledPacket = true; - break; - } - } - } - - // check to see if we're about to return - if (hasFilledPacket) { - // let our caller know how far we got - sinceTime = it.key(); - break; - } - - ++it; - } - - // if we got to the end, then we're done sending - if (it == _recentlyDeletedEntityItemIDs.constEnd()) { - hasMore = false; - } - } - - // replace the count for the number of included IDs - deletesPacket->seek(numberOfIDsPos); - deletesPacket->writePrimitive(numberOfIDs); - - return deletesPacket; -} - - // called by the server when it knows all nodes have been sent deleted packets void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) { quint64 considerSinceTime = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index c177840199..645e3f4f76 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -147,10 +147,19 @@ public: void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); - bool hasAnyDeletedEntities() const { return _recentlyDeletedEntityItemIDs.size() > 0; } + bool hasAnyDeletedEntities() const { + QReadLocker locker(&_recentlyDeletedEntitiesLock); + return _recentlyDeletedEntityItemIDs.size() > 0; + } + bool hasEntitiesDeletedSince(quint64 sinceTime); - std::unique_ptr encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, - bool& hasMore); + static quint64 getAdjustedConsiderSince(quint64 sinceTime); + + QMultiMap getRecentlyDeletedEntityIDs() const { + QReadLocker locker(&_recentlyDeletedEntitiesLock); + return _recentlyDeletedEntityItemIDs; + } + void forgetEntitiesDeletedBefore(quint64 sinceTime); int processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode); @@ -243,7 +252,7 @@ private: QReadWriteLock _newlyCreatedHooksLock; QVector _newlyCreatedHooks; - QReadWriteLock _recentlyDeletedEntitiesLock; + mutable QReadWriteLock _recentlyDeletedEntitiesLock; QMultiMap _recentlyDeletedEntityItemIDs; EntityItemFBXService* _fbxService; From b6b5a5b1c2411c4ffac6b89ab54a7eb1ed828c5c Mon Sep 17 00:00:00 2001 From: Brad Hefta-Gaub Date: Thu, 12 Nov 2015 18:28:08 -0800 Subject: [PATCH 05/11] add comment --- assignment-client/src/entities/EntityServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 4448a3c5c3..5754a9e057 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -96,6 +96,10 @@ bool EntityServer::hasSpecialPacketsToSend(const SharedNodePointer& node) { return shouldSendDeletedEntities; } +// FIXME - most of the old code for this was encapsulated in EntityTree, I liked that design from a data +// hiding and object oriented perspective. But that didn't really allow us to handle the case of lots +// of entities being deleted at the same time. I'd like to look to move this back into EntityTree but +// for now this works and addresses the bug. int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { int totalBytes = 0; From 89d120ab3d42ca26f7536961d861b29e009847c2 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Thu, 12 Nov 2015 22:24:11 -0800 Subject: [PATCH 06/11] Few fixes to make the recorder.js run --- examples/utilities/record/recorder.js | 2 +- libraries/avatars/src/AvatarData.cpp | 6 +++--- libraries/recording/src/recording/Deck.cpp | 2 ++ libraries/recording/src/recording/Frame.h | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index 13ea1e0c72..b44cb23aa8 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -176,7 +176,7 @@ function formatTime(time) { var SEC_PER_MIN = 60; var MSEC_PER_SEC = 1000; - time = time * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); + time = time * (MSEC_PER_SEC); var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR)); time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e2d2c05ef6..e44219977b 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -809,7 +809,7 @@ float AvatarData::playerElapsed() { Q_RETURN_ARG(float, result)); return result; } - return (float)_player->position(); + return (float)_player->position() / (float) MSECS_PER_SECOND; } float AvatarData::playerLength() { @@ -822,7 +822,7 @@ float AvatarData::playerLength() { Q_RETURN_ARG(float, result)); return result; } - return _player->length(); + return (float)_player->length() / (float) MSECS_PER_SECOND; } void AvatarData::loadRecording(const QString& filename) { @@ -870,7 +870,7 @@ void AvatarData::setPlayerTime(float time) { return; } - _player->seek(time); + _player->seek(time * MSECS_PER_SECOND); } void AvatarData::setPlayFromCurrentLocation(bool playFromCurrentLocation) { diff --git a/libraries/recording/src/recording/Deck.cpp b/libraries/recording/src/recording/Deck.cpp index 4349a39732..10209c26d7 100644 --- a/libraries/recording/src/recording/Deck.cpp +++ b/libraries/recording/src/recording/Deck.cpp @@ -25,6 +25,8 @@ void Deck::queueClip(ClipPointer clip, Time timeOffset) { // FIXME if the time offset is not zero, wrap the clip in a OffsetClip wrapper _clips.push_back(clip); + + _length = std::max(_length, clip->duration()); } void Deck::play() { diff --git a/libraries/recording/src/recording/Frame.h b/libraries/recording/src/recording/Frame.h index 85f5246a4e..f0f53ce144 100644 --- a/libraries/recording/src/recording/Frame.h +++ b/libraries/recording/src/recording/Frame.h @@ -27,7 +27,7 @@ public: static const FrameType TYPE_INVALID = 0xFFFF; static const FrameType TYPE_HEADER = 0x0; FrameType type { TYPE_INVALID }; - Time timeOffset { 0 }; + Time timeOffset { 0 }; // milliseconds QByteArray data; Frame() {} From eb0d91fc7eca8405b17006bb7ff760a88ac5a60b Mon Sep 17 00:00:00 2001 From: EdgarPironti Date: Thu, 12 Nov 2015 18:25:51 -0800 Subject: [PATCH 07/11] Fixes to Master recording for ACs --- examples/acScripts/ControlACs.js | 57 +++++++++++++++--------------- examples/acScripts/ControlledAC.js | 54 ++++++++++++---------------- 2 files changed, 51 insertions(+), 60 deletions(-) diff --git a/examples/acScripts/ControlACs.js b/examples/acScripts/ControlACs.js index 60b72446bb..c0d5043e26 100644 --- a/examples/acScripts/ControlACs.js +++ b/examples/acScripts/ControlACs.js @@ -33,15 +33,6 @@ var SHOW = 4; var HIDE = 5; var LOAD = 6; -var COLORS = []; -COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 }; -COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 }; -COLORS[STOP] = { red: STOP, green: 0, blue: 0 }; -COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 }; -COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 }; -COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 }; - - var windowDimensions = Controller.getViewportDimensions(); var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/"; @@ -138,6 +129,7 @@ function setupToolBars() { } function sendCommand(id, action) { + if (action === SHOW) { toolBars[id].selectTool(onOffIcon[id], false); toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); @@ -154,24 +146,29 @@ function sendCommand(id, action) { return; } - if (id === (toolBars.length - 1)) { - for (i = 0; i < NUM_AC; i++) { - sendCommand(i, action); - } - return; - } + if (id === (toolBars.length - 1)) + id = -1; - var position = { x: controlEntityPosition.x + id * controlEntitySize, - y: controlEntityPosition.y, z: controlEntityPosition.z }; - Entities.addEntity({ - name: "Actor Controller", - userData: clip_url, + var controlEntity = Entities.addEntity({ + name: 'New Actor Controller', type: "Box", - position: position, - dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize }, - color: COLORS[action], - lifetime: 5 - }); + color: { red: 0, green: 0, blue: 0 }, + position: controlEntityPosition, + dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize }, + visible: false, + lifetime: 10, + userData: JSON.stringify({ + idKey: { + uD_id: id + }, + actionKey: { + uD_action: action + }, + clipKey: { + uD_url: clip_url + } + }) + }); } function mousePressEvent(event) { @@ -191,8 +188,12 @@ function mousePressEvent(event) { sendCommand(i, PLAY_LOOP); } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { sendCommand(i, STOP); - } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { - sendCommand(i, LOAD); + } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { + input_text = Window.prompt("Insert the url of the clip: ",""); + if(!(input_text === "" || input_text === null)) { + clip_url = input_text; + sendCommand(i, LOAD); + } } else { // Check individual controls for (i = 0; i < NUM_AC; i++) { @@ -210,7 +211,7 @@ function mousePressEvent(event) { sendCommand(i, STOP); } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { input_text = Window.prompt("Insert the url of the clip: ",""); - if(!(input_text === "" || input_text === null)){ + if(!(input_text === "" || input_text === null)) { clip_url = input_text; sendCommand(i, LOAD); } diff --git a/examples/acScripts/ControlledAC.js b/examples/acScripts/ControlledAC.js index 4eecc11136..de31f66df7 100644 --- a/examples/acScripts/ControlledAC.js +++ b/examples/acScripts/ControlledAC.js @@ -38,18 +38,6 @@ var SHOW = 4; var HIDE = 5; var LOAD = 6; -var COLORS = []; -COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 }; -COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 }; -COLORS[STOP] = { red: STOP, green: 0, blue: 0 }; -COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 }; -COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 }; -COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 }; - -controlEntityPosition.x += id * controlEntitySize; - -Avatar.loadRecording(clip_url); - Avatar.setPlayFromCurrentLocation(playFromCurrentLocation); Avatar.setPlayerUseDisplayName(useDisplayName); Avatar.setPlayerUseAttachments(useAttachments); @@ -67,27 +55,29 @@ function setupEntityViewer() { EntityViewer.queryOctree(); } -function getAction(controlEntity) { - clip_url = controlEntity.userData; +function getAction(controlEntity) { + if (controlEntity === null) { + return DO_NOTHING; + } + + var userData = JSON.parse(Entities.getEntityProperties(controlEntity, ["userData"]).userData); - if (controlEntity === null || - controlEntity.position.x !== controlEntityPosition.x || - controlEntity.position.y !== controlEntityPosition.y || - controlEntity.position.z !== controlEntityPosition.z || - controlEntity.dimensions.x !== controlEntitySize) { + var uD_id = userData.idKey.uD_id; + var uD_action = userData.actionKey.uD_action; + var uD_url = userData.clipKey.uD_url; + + print("Sono " + id + " e ho ricevuto un comando da " + uD_id); + + Entities.deleteEntity((Entities.getEntityProperties(controlEntity)).id); + + if(uD_id === id || uD_id === -1) { + if(uD_action === 6) + clip_url = uD_url; + + return uD_action; + } else { return DO_NOTHING; - } - - for (i in COLORS) { - if (controlEntity.color.red === COLORS[i].red && - controlEntity.color.green === COLORS[i].green && - controlEntity.color.blue === COLORS[i].blue) { - Entities.deleteEntity(controlEntity.id); - return parseInt(i); - } - } - - return DO_NOTHING; + } } count = 100; // This is necessary to wait for the audio mixer to connect @@ -100,7 +90,7 @@ function update(event) { var controlEntity = Entities.findClosestEntity(controlEntityPosition, controlEntitySize); - var action = getAction(Entities.getEntityProperties(controlEntity)); + var action = getAction(controlEntity); switch(action) { case PLAY: From a80871a7a85b2c70f3152db238fd06a74eea78c1 Mon Sep 17 00:00:00 2001 From: EdgarPironti Date: Fri, 13 Nov 2015 14:58:59 -0800 Subject: [PATCH 08/11] Fixes --- examples/acScripts/ControlACs.js | 4 ++-- examples/acScripts/ControlledAC.js | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/acScripts/ControlACs.js b/examples/acScripts/ControlACs.js index c0d5043e26..ba066d9750 100644 --- a/examples/acScripts/ControlACs.js +++ b/examples/acScripts/ControlACs.js @@ -190,7 +190,7 @@ function mousePressEvent(event) { sendCommand(i, STOP); } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { input_text = Window.prompt("Insert the url of the clip: ",""); - if(!(input_text === "" || input_text === null)) { + if (!(input_text === "" || input_text === null)) { clip_url = input_text; sendCommand(i, LOAD); } @@ -211,7 +211,7 @@ function mousePressEvent(event) { sendCommand(i, STOP); } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { input_text = Window.prompt("Insert the url of the clip: ",""); - if(!(input_text === "" || input_text === null)) { + if (!(input_text === "" || input_text === null)) { clip_url = input_text; sendCommand(i, LOAD); } diff --git a/examples/acScripts/ControlledAC.js b/examples/acScripts/ControlledAC.js index de31f66df7..41a8a2b257 100644 --- a/examples/acScripts/ControlledAC.js +++ b/examples/acScripts/ControlledAC.js @@ -66,12 +66,10 @@ function getAction(controlEntity) { var uD_action = userData.actionKey.uD_action; var uD_url = userData.clipKey.uD_url; - print("Sono " + id + " e ho ricevuto un comando da " + uD_id); - Entities.deleteEntity((Entities.getEntityProperties(controlEntity)).id); - if(uD_id === id || uD_id === -1) { - if(uD_action === 6) + if (uD_id === id || uD_id === -1) { + if (uD_action === 6) clip_url = uD_url; return uD_action; From 8c21ac144ee63642461e457bdcafcf0556ddf6db Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 13 Nov 2015 16:45:19 -0800 Subject: [PATCH 09/11] Fixing review comments --- interface/src/avatar/MyAvatar.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4d62946a5f..278cc5ff81 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -608,7 +608,7 @@ float MyAvatar::recorderElapsed() { if (!_recorder) { return 0; } - return (float)_recorder->position(); + return (float)_recorder->position() / (float) MSECS_PER_SECOND; } QMetaObject::Connection _audioClientRecorderConnection; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index e44219977b..8537f37f84 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1664,7 +1664,7 @@ void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar) { auto worldTransform = currentBasis->worldTransform(relativeTransform); _avatar->setPosition(worldTransform.getTranslation()); _avatar->setOrientation(worldTransform.getRotation()); - _avatar->setTargetScale(worldTransform.getScale().x); + // _avatar->setTargetScale(worldTransform.getScale().x); } From 89e5db11a016005e6b402cdd297ada002256d287 Mon Sep 17 00:00:00 2001 From: samcake Date: Fri, 13 Nov 2015 17:41:41 -0800 Subject: [PATCH 10/11] More fixes --- examples/utilities/record/recorder.js | 13 +++++-------- libraries/avatars/src/AvatarData.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index b44cb23aa8..40476626e8 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -176,16 +176,13 @@ function formatTime(time) { var SEC_PER_MIN = 60; var MSEC_PER_SEC = 1000; - time = time * (MSEC_PER_SEC); + var hours = Math.floor(time / (SEC_PER_MIN * MIN_PER_HOUR)); + time -= hours * (SEC_PER_MIN * MIN_PER_HOUR); - var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR)); - time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); + var minutes = Math.floor(time / (SEC_PER_MIN)); + time -= minutes * (SEC_PER_MIN); - var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN)); - time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN); - - var seconds = Math.floor(time / MSEC_PER_SEC); - seconds = time / MSEC_PER_SEC; + var seconds = time; var text = ""; text += (hours > 0) ? hours + ":" : diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 8537f37f84..a47d5f663e 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1513,7 +1513,8 @@ void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { recordingBasis = std::make_shared(); recordingBasis->setRotation(getOrientation()); recordingBasis->setTranslation(getPosition()); - recordingBasis->setScale(getTargetScale()); + // TODO: find a different way to record/playback the Scale of the avatar + //recordingBasis->setScale(getTargetScale()); } _recordingBasis = recordingBasis; } @@ -1664,7 +1665,9 @@ void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar) { auto worldTransform = currentBasis->worldTransform(relativeTransform); _avatar->setPosition(worldTransform.getTranslation()); _avatar->setOrientation(worldTransform.getRotation()); - // _avatar->setTargetScale(worldTransform.getScale().x); + + // TODO: find a way to record/playback the Scale of the avatar + //_avatar->setTargetScale(worldTransform.getScale().x); } From 245150333b949200a11bb40e8b1d840fe8e66353 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 13 Nov 2015 18:08:18 -0800 Subject: [PATCH 11/11] 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; };