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; }; diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 493a16fea4..5754a9e057 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -96,29 +96,130 @@ 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; 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 +235,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/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 f2dbf56206..7c8d8f0e01 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -119,6 +119,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); @@ -579,11 +583,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", diff --git a/examples/acScripts/ControlACs.js b/examples/acScripts/ControlACs.js index 60b72446bb..ba066d9750 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..41a8a2b257 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,27 @@ 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; + + 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 +88,7 @@ function update(event) { var controlEntity = Entities.findClosestEntity(controlEntityPosition, controlEntitySize); - var action = getAction(Entities.getEntityProperties(controlEntity)); + var action = getAction(controlEntity); switch(action) { case PLAY: diff --git a/examples/utilities/record/recorder.js b/examples/utilities/record/recorder.js index 6d49030a28..40476626e8 100644 --- a/examples/utilities/record/recorder.js +++ b/examples/utilities/record/recorder.js @@ -176,14 +176,13 @@ function formatTime(time) { var SEC_PER_MIN = 60; var MSEC_PER_SEC = 1000; - 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 hours = Math.floor(time / (SEC_PER_MIN * MIN_PER_HOUR)); + time -= hours * (SEC_PER_MIN * MIN_PER_HOUR); - var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN)); - time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN); + var minutes = Math.floor(time / (SEC_PER_MIN)); + time -= minutes * (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/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5e14c66ff1..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() / MSECS_PER_SECOND; + 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 adf471c50e..a47d5f663e 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) MSECS_PER_SECOND; } 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 (float)_player->length() / (float) MSECS_PER_SECOND; } void AvatarData::loadRecording(const QString& filename) { @@ -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; } @@ -1532,7 +1533,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 +1545,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 +1594,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) { @@ -1643,24 +1665,34 @@ 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); } -#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; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0e2b713b65..027972549a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -887,8 +887,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; @@ -915,88 +920,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; 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() {} diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 420bb3e54d..5b81c68e99 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(); }