From 954391ae3e832090f1ac4643482f759d08ad4473 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 11 Nov 2015 18:33:26 -0800 Subject: [PATCH 01/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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); }