diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 7db06f12c0..868b377ced 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -81,7 +81,6 @@ bool OctreeSendThread::process() { // don't do any send processing until the initial load of the octree is complete... if (_myServer->isInitialLoadComplete()) { if (auto node = _node.lock()) { - _nodeMissingCount = 0; OctreeQueryNode* nodeData = static_cast(node->getLinkedData()); // Sometimes the node data has not yet been linked, in which case we can't really do anything @@ -129,8 +128,7 @@ AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 }; AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 }; -int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, - int& truePacketsSent, bool dontSuppressDuplicate) { +int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate) { OctreeServer::didHandlePacketSend(this); // if we're shutting down, then exit early @@ -141,15 +139,14 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* bool debug = _myServer->wantsDebugSending(); quint64 now = usecTimestampNow(); - bool packetSent = false; // did we send a packet? - int packetsSent = 0; + int numPackets = 0; // Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently // obscure the packet and not send it. This allows the callers and upper level logic to not need to know about // this rate control savings. if (!dontSuppressDuplicate && nodeData->shouldSuppressDuplicatePacket()) { nodeData->resetOctreePacket(); // we still need to reset it though! - return packetsSent; // without sending... + return numPackets; // without sending... } // If we've got a stats message ready to send, then see if we can piggyback them together @@ -163,12 +160,15 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // copy octree message to back of stats message statsPacket.write(nodeData->getPacket().getData(), nodeData->getPacket().getDataSize()); - // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since + int numBytes = statsPacket.getDataSize(); + _totalBytes += numBytes; + _totalPackets++; + // since a stats message is only included on end of scene, don't consider any of these bytes "wasted" // there was nothing else to send. int thisWastedBytes = 0; - _totalWastedBytes += thisWastedBytes; - _totalBytes += statsPacket.getDataSize(); - _totalPackets++; + //_totalWastedBytes += 0; + _trueBytesSent += numBytes; + numPackets++; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -191,18 +191,22 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // actually send it OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(statsPacket, *node); - packetSent = true; } else { // not enough room in the packet, send two packets + + // first packet OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(statsPacket, *node); - // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since + int numBytes = statsPacket.getDataSize(); + _totalBytes += numBytes; + _totalPackets++; + // since a stats message is only included on end of scene, don't consider any of these bytes "wasted" // there was nothing else to send. int thisWastedBytes = 0; - _totalWastedBytes += thisWastedBytes; - _totalBytes += statsPacket.getDataSize(); - _totalPackets++; + //_totalWastedBytes += 0; + _trueBytesSent += numBytes; + numPackets++; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -221,19 +225,18 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } - trueBytesSent += statsPacket.getDataSize(); - truePacketsSent++; - packetsSent++; - + // second packet OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(nodeData->getPacket(), *node); - packetSent = true; - int packetSizeWithHeader = nodeData->getPacket().getDataSize(); - thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader; - _totalWastedBytes += thisWastedBytes; - _totalBytes += nodeData->getPacket().getDataSize(); + numBytes = nodeData->getPacket().getDataSize(); + _totalBytes += numBytes; _totalPackets++; + // we count wasted bytes here because we were unable to fit the stats packet + thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes; + _totalWastedBytes += thisWastedBytes; + _trueBytesSent += numBytes; + numPackets++; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -259,13 +262,14 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* // just send the octree packet OctreeServer::didCallWriteDatagram(this); DependencyManager::get()->sendUnreliablePacket(nodeData->getPacket(), *node); - packetSent = true; - int packetSizeWithHeader = nodeData->getPacket().getDataSize(); - int thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader; - _totalWastedBytes += thisWastedBytes; - _totalBytes += packetSizeWithHeader; + int numBytes = nodeData->getPacket().getDataSize(); + _totalBytes += numBytes; _totalPackets++; + int thisWastedBytes = udt::MAX_PACKET_SIZE - numBytes; + _totalWastedBytes += thisWastedBytes; + numPackets++; + _trueBytesSent += numBytes; if (debug) { NLPacket& sentPacket = nodeData->getPacket(); @@ -280,23 +284,21 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << " timestamp: " << timestamp << - " size: " << packetSizeWithHeader << " [" << _totalBytes << + " size: " << numBytes << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } } } // remember to track our stats - if (packetSent) { + if (numPackets > 0) { nodeData->stats.packetSent(nodeData->getPacket().getPayloadSize()); - trueBytesSent += nodeData->getPacket().getPayloadSize(); - truePacketsSent++; - packetsSent++; nodeData->octreePacketSent(); nodeData->resetOctreePacket(); } - return packetsSent; + _truePacketsSent += numPackets; + return numPackets; } /// Version of octree element distributor that sends the deepest LOD level at once @@ -315,13 +317,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* preDistributionProcessing(); } - // calculate max number of packets that can be sent during this interval - int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); - int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); - - int truePacketsSent = 0; - int trueBytesSent = 0; - int packetsSentThisInterval = 0; + _truePacketsSent = 0; + _trueBytesSent = 0; + _packetsSentThisInterval = 0; bool isFullScene = nodeData->shouldForceFullScene(); if (isFullScene) { @@ -334,17 +332,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* && ((!viewFrustumChanged && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged())); } - bool somethingToSend = true; // assume we have something - - // If our packet already has content in it, then we must use the color choice of the waiting packet. - // If we're starting a fresh packet, then... - // If we're moving, and the client asked for low res, then we force monochrome, otherwise, use - // the clients requested color state. - - // If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color - // then let's just send that waiting packet. if (nodeData->isPacketWaiting()) { - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + // send the waiting packet + _packetsSentThisInterval += handlePacketSend(node, nodeData); } else { nodeData->resetOctreePacket(); } @@ -375,8 +365,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* //unsigned long encodeTime = nodeData->stats.getTotalEncodeTime(); //unsigned long elapsedTime = nodeData->stats.getElapsedTime(); - int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent, isFullScene); - packetsSentThisInterval += packetsJustSent; + _packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene); // If we're starting a full scene, then definitely we want to empty the elementBag if (isFullScene) { @@ -404,185 +393,44 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // If we have something in our elementBag, then turn them into packets and send them out... if (!nodeData->elementBag.isEmpty()) { - int bytesWritten = 0; quint64 start = usecTimestampNow(); - // TODO: add these to stats page - //quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000; - //quint64 startCompressCalls = OctreePacketData::getCompressContentCalls(); - - int extraPackingAttempts = 0; - bool completedScene = false; - - while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { - float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; - float encodeElapsedUsec = OctreeServer::SKIP_TIME; - float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; - float packetSendingElapsedUsec = OctreeServer::SKIP_TIME; - - quint64 startInside = usecTimestampNow(); - - bool lastNodeDidntFit = false; // assume each node fits - if (!nodeData->elementBag.isEmpty()) { - - quint64 lockWaitStart = usecTimestampNow(); - _myServer->getOctree()->withReadLock([&]{ - quint64 lockWaitEnd = usecTimestampNow(); - lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); - quint64 encodeStart = usecTimestampNow(); - - OctreeElementPointer subTree = nodeData->elementBag.extract(); - if (!subTree) { - return; - } - - float octreeSizeScale = nodeData->getOctreeSizeScale(); - int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - - int boundaryLevelAdjust = boundaryLevelAdjustClient + - (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - - EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, - viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, - isFullScene, _myServer->getJurisdiction(), nodeData); - nodeData->copyCurrentViewFrustum(params.viewFrustum); - if (viewFrustumChanged) { - nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); - } - - // Our trackSend() function is implemented by the server subclass, and will be called back - // during the encodeTreeBitstream() as new entities/data elements are sent - params.trackSend = [this, node](const QUuid& dataID, quint64 dataEdited) { - _myServer->trackSend(dataID, dataEdited, node->getUUID()); - }; - - // TODO: should this include the lock time or not? This stat is sent down to the client, - // it seems like it may be a good idea to include the lock time as part of the encode time - // are reported to client. Since you can encode without the lock - nodeData->stats.encodeStarted(); - - bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); - - quint64 encodeEnd = usecTimestampNow(); - encodeElapsedUsec = (float)(encodeEnd - encodeStart); - - // If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've - // sent the entire scene. We want to know this below so we'll actually write this content into - // the packet and send it - completedScene = nodeData->elementBag.isEmpty(); - - if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { - lastNodeDidntFit = true; - extraPackingAttempts++; - } - - nodeData->stats.encodeStopped(); - }); - } else { - // If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0 - bytesWritten = 0; - somethingToSend = false; // this will cause us to drop out of the loop... - } - - // If the last node didn't fit, but we're in compressed mode, then we actually want to see if we can fit a - // little bit more in this packet. To do this we write into the packet, but don't send it yet, we'll - // keep attempting to write in compressed mode to add more compressed segments - - // We only consider sending anything if there is something in the _packetData to send... But - // if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases - // mean we should send the previous packet contents and reset it. - if (completedScene || lastNodeDidntFit) { - - if (_packetData.hasContent()) { - quint64 compressAndWriteStart = usecTimestampNow(); - - // if for some reason the finalized size is greater than our available size, then probably the "compressed" - // form actually inflated beyond our padding, and in this case we will send the current packet, then - // write to out new packet... - unsigned int writtenSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - - if (writtenSize > nodeData->getAvailable()) { - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); - } - - nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); - quint64 compressAndWriteEnd = usecTimestampNow(); - compressAndWriteElapsedUsec = (float)(compressAndWriteEnd - compressAndWriteStart); - } - - // If we're not running compressed, then we know we can just send now. Or if we're running compressed, but - // the packet doesn't have enough space to bother attempting to pack more... - bool sendNow = true; - - if (!completedScene && (nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING && - extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS)) { - sendNow = false; // try to pack more - } - - int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; - if (sendNow) { - quint64 packetSendingStart = usecTimestampNow(); - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); - quint64 packetSendingEnd = usecTimestampNow(); - packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); - - targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); - extraPackingAttempts = 0; - } else { - // If we're in compressed mode, then we want to see if we have room for more in this wire packet. - // but we've finalized the _packetData, so we want to start a new section, we will do that by - // resetting the packet settings with the max uncompressed size of our current available space - // in the wire packet. We also include room for our section header, and a little bit of padding - // to account for the fact that whenc compressing small amounts of data, we sometimes end up with - // a larger compressed size then uncompressed size - targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING; - } - _packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed - - } - OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec); - OctreeServer::trackEncodeTime(encodeElapsedUsec); - OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec); - OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec); - - quint64 endInside = usecTimestampNow(); - quint64 elapsedInsideUsecs = endInside - startInside; - OctreeServer::trackInsideTime((float)elapsedInsideUsecs); - } - - if (somethingToSend && _myServer->wantsVerboseDebug()) { - qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << packetsSentThisInterval - << " maxPacketsPerInterval = " << maxPacketsPerInterval - << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; - } + traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); // Here's where we can/should allow the server to send other data... // 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 = 0; - trueBytesSent += _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); + int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed - truePacketsSent += specialPacketsSent; - packetsSentThisInterval += specialPacketsSent; + _truePacketsSent += specialPacketsSent; + _trueBytesSent += specialBytesSent; + _packetsSentThisInterval += specialPacketsSent; _totalPackets += specialPacketsSent; - _totalBytes += trueBytesSent; + _totalBytes += specialBytesSent; _totalSpecialPackets += specialPacketsSent; - _totalSpecialBytes += trueBytesSent; + _totalSpecialBytes += specialBytesSent; } + // calculate max number of packets that can be sent during this interval + int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + // Re-send packets that were nacked by the client - while (nodeData->hasNextNackedPacket() && packetsSentThisInterval < maxPacketsPerInterval) { + while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { const NLPacket* packet = nodeData->getNextNackedPacket(); if (packet) { DependencyManager::get()->sendUnreliablePacket(*packet, *node); - truePacketsSent++; - packetsSentThisInterval++; + int numBytes = packet->getDataSize(); + _truePacketsSent++; + _trueBytesSent += numBytes; + _packetsSentThisInterval++; - _totalBytes += packet->getDataSize(); _totalPackets++; + _totalBytes += numBytes; _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); } } @@ -591,12 +439,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* int elapsedmsec = (end - start) / USECS_PER_MSEC; OctreeServer::trackLoopTime(elapsedmsec); - // TODO: add these to stats page - //quint64 endCompressCalls = OctreePacketData::getCompressContentCalls(); - //int elapsedCompressCalls = endCompressCalls - startCompressCalls; - //quint64 endCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000; - //int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs; - // if after sending packets we've emptied our bag, then we want to remember that we've sent all // the octree elements from the current view frustum if (nodeData->elementBag.isEmpty()) { @@ -606,17 +448,147 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // If this was a full scene then make sure we really send out a stats packet at this point so that // the clients will know the scene is stable if (isFullScene) { - int thisTrueBytesSent = 0; - int thisTruePacketsSent = 0; nodeData->stats.sceneCompleted(); - int packetsJustSent = handlePacketSend(node, nodeData, thisTrueBytesSent, thisTruePacketsSent, true); - _totalBytes += thisTrueBytesSent; - _totalPackets += thisTruePacketsSent; - truePacketsSent += packetsJustSent; + handlePacketSend(node, nodeData, true); } } } // end if bag wasn't empty, and so we sent stuff... - return truePacketsSent; + return _truePacketsSent; +} + +void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { + // calculate max number of packets that can be sent during this interval + int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + + int extraPackingAttempts = 0; + bool completedScene = false; + + bool somethingToSend = true; // assume we have something + while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { + float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; + float encodeElapsedUsec = OctreeServer::SKIP_TIME; + float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; + float packetSendingElapsedUsec = OctreeServer::SKIP_TIME; + + quint64 startInside = usecTimestampNow(); + + bool lastNodeDidntFit = false; // assume each node fits + if (!nodeData->elementBag.isEmpty()) { + + quint64 lockWaitStart = usecTimestampNow(); + _myServer->getOctree()->withReadLock([&]{ + quint64 lockWaitEnd = usecTimestampNow(); + lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); + quint64 encodeStart = usecTimestampNow(); + + OctreeElementPointer subTree = nodeData->elementBag.extract(); + if (!subTree) { + return; + } + + float octreeSizeScale = nodeData->getOctreeSizeScale(); + int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); + + int boundaryLevelAdjust = boundaryLevelAdjustClient + + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + + EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, + viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, + isFullScene, _myServer->getJurisdiction(), nodeData); + nodeData->copyCurrentViewFrustum(params.viewFrustum); + if (viewFrustumChanged) { + nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); + } + + // Our trackSend() function is implemented by the server subclass, and will be called back + // during the encodeTreeBitstream() as new entities/data elements are sent + params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { + _myServer->trackSend(dataID, dataEdited, _nodeUuid); + }; + + // TODO: should this include the lock time or not? This stat is sent down to the client, + // it seems like it may be a good idea to include the lock time as part of the encode time + // are reported to client. Since you can encode without the lock + nodeData->stats.encodeStarted(); + + // NOTE: this is where the tree "contents" are actaully packed + _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); + + quint64 encodeEnd = usecTimestampNow(); + encodeElapsedUsec = (float)(encodeEnd - encodeStart); + + // If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've + // sent the entire scene. We want to know this below so we'll actually write this content into + // the packet and send it + completedScene = nodeData->elementBag.isEmpty(); + + if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { + lastNodeDidntFit = true; + extraPackingAttempts++; + } + + nodeData->stats.encodeStopped(); + }); + } else { + somethingToSend = false; // this will cause us to drop out of the loop... + } + + if (completedScene || lastNodeDidntFit) { + // we probably want to flush what has accumulated in nodeData but: + // do we have more data to send? and is there room? + if (_packetData.hasContent()) { + // yes, more data to send + quint64 compressAndWriteStart = usecTimestampNow(); + unsigned int additionalSize = _packetData.getFinalizedSize() + sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); + if (additionalSize > nodeData->getAvailable()) { + // no room --> flush what we've got + _packetsSentThisInterval += handlePacketSend(node, nodeData); + } + + // either there is room, or we've flushed and reset nodeData's data buffer + // so we can transfer whatever is in _packetData to nodeData + nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); + compressAndWriteElapsedUsec = (float)(usecTimestampNow()- compressAndWriteStart); + } + + bool sendNow = completedScene || + nodeData->getAvailable() < MINIMUM_ATTEMPT_MORE_PACKING || + extraPackingAttempts > REASONABLE_NUMBER_OF_PACKING_ATTEMPTS; + + int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; + if (sendNow) { + quint64 packetSendingStart = usecTimestampNow(); + _packetsSentThisInterval += handlePacketSend(node, nodeData); + quint64 packetSendingEnd = usecTimestampNow(); + packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); + + targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); + extraPackingAttempts = 0; + } else { + // We want to see if we have room for more in this wire packet but we've copied the _packetData, + // so we want to start a new section. We will do that by resetting the packet settings with the max + // size of our current available space in the wire packet plus room for our section header and a + // little bit of padding. + targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING; + } + _packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed + } + OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec); + OctreeServer::trackEncodeTime(encodeElapsedUsec); + OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec); + OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec); + + quint64 endInside = usecTimestampNow(); + quint64 elapsedInsideUsecs = endInside - startInside; + OctreeServer::trackInsideTime((float)elapsedInsideUsecs); + } + + if (somethingToSend && _myServer->wantsVerboseDebug()) { + qCDebug(octree) << "Hit PPS Limit, packetsSentThisInterval =" << _packetsSentThisInterval + << " maxPacketsPerInterval = " << maxPacketsPerInterval + << " clientMaxPacketsPerInterval = " << clientMaxPacketsPerInterval; + } } diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 06c9b5f1d6..d158539f57 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -34,7 +34,7 @@ public: void setIsShuttingDown(); bool isShuttingDown() { return _isShuttingDown; } - + QUuid getNodeUuid() const { return _nodeUuid; } static AtomicUIntStat _totalBytes; @@ -53,20 +53,23 @@ protected: /// Called before a packetDistributor pass to allow for pre-distribution processing virtual void preDistributionProcessing() {}; + virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); OctreeServer* _myServer { nullptr }; QWeakPointer _node; private: - int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent, bool dontSuppressDuplicate = false); + int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); - + QUuid _nodeUuid; OctreePacketData _packetData; - int _nodeMissingCount { 0 }; + int _truePacketsSent { 0 }; // available for debug stats + int _trueBytesSent { 0 }; // available for debug stats + int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition bool _isShuttingDown { false }; }; diff --git a/interface/src/Application.h b/interface/src/Application.h index cf0ae91a0f..ce27c4a70a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -678,7 +678,7 @@ private: QTimer _addAssetToWorldErrorTimer; FileScriptingInterface* _fileDownload; - AudioInjector* _snapshotSoundInjector { nullptr }; + AudioInjectorPointer _snapshotSoundInjector; SharedSoundPointer _snapshotSound; DisplayPluginPointer _autoSwitchDisplayModeSupportedHMDPlugin; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8b616c3029..c7223be208 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -680,7 +680,7 @@ Menu::Menu() { // Developer > Physics >>> MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); { - auto drawStatusConfig = qApp->getRenderEngine()->getConfiguration()->getConfig(); + auto drawStatusConfig = qApp->getRenderEngine()->getConfiguration()->getConfig("RenderMainView.DrawStatus"); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned, 0, false, drawStatusConfig, SLOT(setShowNetwork(bool))); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1c35d45709..d3fa87b163 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -433,8 +433,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents // but most avatars are roughly the same size, so let's not be so fancy yet. const float AVATAR_STRETCH_FACTOR = 1.0f; - - _collisionInjectors.remove_if([](QPointer& injector) { + _collisionInjectors.remove_if([](const AudioInjectorPointer& injector) { return !injector || injector->isFinished(); }); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index f1e71f7367..30801807d6 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -22,11 +22,11 @@ #include #include #include +#include #include "AvatarMotionState.h" #include "MyAvatar.h" -class AudioInjector; class AvatarManager : public AvatarHashMap { Q_OBJECT @@ -104,7 +104,7 @@ private: std::shared_ptr _myAvatar; quint64 _lastSendAvatarDataTime = 0; // Controls MyAvatar send data rate. - std::list> _collisionInjectors; + std::list _collisionInjectors; RateCounter<> _myAvatarSendRate; int _numAvatarsUpdated { 0 }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c630fe09e4..3d9b1de10f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -210,9 +210,9 @@ AudioClient::AudioClient() : connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &AudioClient::processReceivedSamples, Qt::DirectConnection); - connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { + connect(this, &AudioClient::changeDevice, this, [=](const QAudioDeviceInfo& outputDeviceInfo) { qCDebug(audioclient) << "got AudioClient::changeDevice signal, about to call switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; - switchOutputToAudioDevice(outputDeviceInfo); + switchOutputToAudioDevice(outputDeviceInfo); }); connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); @@ -261,10 +261,10 @@ void AudioClient::cleanupBeforeQuit() { // so this must be explicitly, synchronously stopped static ConditionalGuard guard; if (QThread::currentThread() != thread()) { - // This will likely be called from the main thread, but we don't want to do blocking queued calls - // from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait + // This will likely be called from the main thread, but we don't want to do blocking queued calls + // from the main thread, so we use a normal auto-connection invoke, and then use a conditional to wait // for completion - // The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection + // The effect is the same, yes, but we actually want to avoid the use of Qt::BlockingQueuedConnection // in the code QMetaObject::invokeMethod(this, "cleanupBeforeQuit"); guard.wait(); @@ -630,7 +630,7 @@ void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointerreadPrimitive(&bitset); bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT); - + if (hasReverb) { float reverbTime, wetLevel; message->readPrimitive(&reverbTime); @@ -728,7 +728,7 @@ void AudioClient::Gate::flush() { void AudioClient::handleNoisyMutePacket(QSharedPointer message) { if (!_muted) { toggleMute(); - + // have the audio scripting interface emit a signal to say we were muted by the mixer emit mutedByMixer(); } @@ -737,7 +737,7 @@ void AudioClient::handleNoisyMutePacket(QSharedPointer message) void AudioClient::handleMuteEnvironmentPacket(QSharedPointer message) { glm::vec3 position; float radius; - + message->readPrimitive(&position); message->readPrimitive(&radius); @@ -770,7 +770,7 @@ void AudioClient::handleSelectedAudioFormat(QSharedPointer mess } void AudioClient::selectAudioFormat(const QString& selectedCodecName) { - + _selectedCodecName = selectedCodecName; qCDebug(audioclient) << "Selected Codec:" << _selectedCodecName; @@ -787,7 +787,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) { for (auto& plugin : codecPlugins) { if (_selectedCodecName == plugin->getName()) { _codec = plugin; - _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); + _receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO); _encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO); qCDebug(audioclient) << "Selected Codec Plugin:" << _codec.get(); break; @@ -795,7 +795,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) { } } - + bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo) { auto device = deviceInfo; @@ -1203,11 +1203,11 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { // lock the injectors Lock lock(_injectorsMutex); - QVector injectorsToRemove; + QVector injectorsToRemove; memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float)); - for (AudioInjector* injector : _activeLocalAudioInjectors) { + for (const AudioInjectorPointer& injector : _activeLocalAudioInjectors) { // the lock guarantees that injectorBuffer, if found, is invariant AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); if (injectorBuffer) { @@ -1220,7 +1220,7 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { // get one frame from the injector memset(_localScratchBuffer, 0, bytesToRead); if (0 < injectorBuffer->readData((char*)_localScratchBuffer, bytesToRead)) { - + if (injector->isAmbisonic()) { // no distance attenuation @@ -1249,36 +1249,36 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) { mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain; } - + } else { // calculate distance, gain and azimuth for hrtf glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); float distance = glm::max(glm::length(relativePosition), EPSILON); - float gain = gainForSource(distance, injector->getVolume()); + float gain = gainForSource(distance, injector->getVolume()); float azimuth = azimuthForSource(relativePosition); - + // mono gets spatialized into mixBuffer - injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX, + injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX, azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL); } - + } else { - + qCDebug(audioclient) << "injector has no more data, marking finished for removal"; injector->finishLocalInjection(); injectorsToRemove.append(injector); } } else { - + qCDebug(audioclient) << "injector has no local buffer, marking as finished for removal"; injector->finishLocalInjection(); injectorsToRemove.append(injector); } } - - for (AudioInjector* injector : injectorsToRemove) { + + for (const AudioInjectorPointer& injector : injectorsToRemove) { qCDebug(audioclient) << "removing injector"; _activeLocalAudioInjectors.removeOne(injector); } @@ -1369,7 +1369,7 @@ void AudioClient::setIsStereoInput(bool isStereoInput) { } } -bool AudioClient::outputLocalInjector(AudioInjector* injector) { +bool AudioClient::outputLocalInjector(const AudioInjectorPointer& injector) { AudioInjectorLocalBuffer* injectorBuffer = injector->getLocalBuffer(); if (injectorBuffer) { // local injectors are on the AudioInjectorsThread, so we must guard access @@ -1711,9 +1711,9 @@ int AudioClient::calculateNumberOfFrameSamples(int numBytes) const { float AudioClient::azimuthForSource(const glm::vec3& relativePosition) { glm::quat inverseOrientation = glm::inverse(_orientationGetter()); - + glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition; - + // project the rotated source position vector onto the XZ plane rotatedSourcePosition.y = 0.0f; @@ -1721,15 +1721,15 @@ float AudioClient::azimuthForSource(const glm::vec3& relativePosition) { float rotatedSourcePositionLength2 = glm::length2(rotatedSourcePosition); if (rotatedSourcePositionLength2 > SOURCE_DISTANCE_THRESHOLD) { - + // produce an oriented angle about the y-axis glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2)); float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward" return (direction.x < 0.0f) ? -angle : angle; - } else { + } else { // no azimuth if they are in same spot - return 0.0f; + return 0.0f; } } @@ -1869,9 +1869,9 @@ void AudioClient::startThread() { moveToNewNamedThread(this, "Audio Thread", [this] { start(); }); } -void AudioClient::setInputVolume(float volume) { - if (_audioInput && volume != (float)_audioInput->volume()) { - _audioInput->setVolume(volume); +void AudioClient::setInputVolume(float volume) { + if (_audioInput && volume != (float)_audioInput->volume()) { + _audioInput->setVolume(volume); emit inputVolumeChanged(_audioInput->volume()); } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 62b99d2443..31e36671c7 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -143,7 +143,7 @@ public: Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale); - bool outputLocalInjector(AudioInjector* injector) override; + bool outputLocalInjector(const AudioInjectorPointer& injector) override; QAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const; QList getAudioDevices(QAudio::Mode mode) const; @@ -380,7 +380,7 @@ private: bool _hasReceivedFirstPacket { false }; - QVector _activeLocalAudioInjectors; + QVector _activeLocalAudioInjectors; bool _isPlayingBackRecording { false }; diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 2e14b9956b..8b48b55206 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -18,6 +18,7 @@ #include #include "AudioInjectorOptions.h" +#include "AudioInjector.h" class AudioInjector; class AudioInjectorLocalBuffer; @@ -35,7 +36,7 @@ public: // threadsafe // moves injector->getLocalBuffer() to another thread (so removes its parent) // take care to delete it when ~AudioInjector, as parenting Qt semantics will not work - virtual bool outputLocalInjector(AudioInjector* injector) = 0; + virtual bool outputLocalInjector(const AudioInjectorPointer& injector) = 0; public slots: virtual bool shouldLoopbackInjectors() { return false; } diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 47e6c98144..ee57e42e77 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -92,11 +92,6 @@ void AudioInjector::finish() { emit finished(); deleteLocalBuffer(); - - if (stateHas(AudioInjectorState::PendingDelete)) { - // we've been asked to delete after finishing, trigger a deleteLater here - deleteLater(); - } } void AudioInjector::restart() { @@ -132,7 +127,7 @@ void AudioInjector::restart() { } } -bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector*)) { +bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)) { _state = AudioInjectorState::NotFinished; int byteOffset = 0; @@ -150,7 +145,7 @@ bool AudioInjector::inject(bool(AudioInjectorManager::*injection)(AudioInjector* bool success = true; if (!_options.localOnly) { auto injectorManager = DependencyManager::get(); - if (!(*injectorManager.*injection)(this)) { + if (!(*injectorManager.*injection)(sharedFromThis())) { success = false; finishNetworkInjection(); } @@ -173,7 +168,7 @@ bool AudioInjector::injectLocally() { // call this function on the AudioClient's thread // this will move the local buffer's thread to the LocalInjectorThread - success = _localAudioInterface->outputLocalInjector(this); + success = _localAudioInterface->outputLocalInjector(sharedFromThis()); if (!success) { qCDebug(audio) << "AudioInjector::injectLocally could not output locally via _localAudioInterface"; @@ -418,20 +413,16 @@ void AudioInjector::triggerDeleteAfterFinish() { } if (stateHas(AudioInjectorState::Finished)) { - stopAndDeleteLater(); + stop(); } else { _state |= AudioInjectorState::PendingDelete; } } -void AudioInjector::stopAndDeleteLater() { - stop(); - QMetaObject::invokeMethod(this, "deleteLater", Qt::QueuedConnection); -} - -AudioInjector* AudioInjector::playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position) { +AudioInjectorPointer AudioInjector::playSound(SharedSoundPointer sound, const float volume, + const float stretchFactor, const glm::vec3 position) { if (!sound || !sound->isReady()) { - return nullptr; + return AudioInjectorPointer(); } AudioInjectorOptions options; @@ -462,8 +453,8 @@ AudioInjector* AudioInjector::playSound(SharedSoundPointer sound, const float vo return playSoundAndDelete(resampled, options); } -AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) { - AudioInjector* sound = playSound(buffer, options); +AudioInjectorPointer AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options) { + AudioInjectorPointer sound = playSound(buffer, options); if (sound) { sound->_state |= AudioInjectorState::PendingDelete; @@ -473,8 +464,9 @@ AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const } -AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) { - AudioInjector* injector = new AudioInjector(buffer, options); +AudioInjectorPointer AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options) { + AudioInjectorPointer injector = AudioInjectorPointer::create(buffer, options); + if (!injector->inject(&AudioInjectorManager::threadInjector)) { qWarning() << "AudioInjector::playSound failed to thread injector"; } diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index a901c2520f..aed51c5f85 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -32,6 +32,8 @@ class AbstractAudioInterface; class AudioInjectorManager; +class AudioInjector; +using AudioInjectorPointer = QSharedPointer; enum class AudioInjectorState : uint8_t { @@ -46,19 +48,19 @@ AudioInjectorState operator& (AudioInjectorState lhs, AudioInjectorState rhs); AudioInjectorState& operator|= (AudioInjectorState& lhs, AudioInjectorState rhs); // In order to make scripting cleaner for the AudioInjector, the script now holds on to the AudioInjector object -// until it dies. -class AudioInjector : public QObject { +// until it dies. +class AudioInjector : public QObject, public QEnableSharedFromThis { Q_OBJECT public: AudioInjector(const Sound& sound, const AudioInjectorOptions& injectorOptions); AudioInjector(const QByteArray& audioData, const AudioInjectorOptions& injectorOptions); ~AudioInjector(); - + bool isFinished() const { return (stateHas(AudioInjectorState::Finished)); } - + int getCurrentSendOffset() const { return _currentSendOffset; } void setCurrentSendOffset(int currentSendOffset) { _currentSendOffset = currentSendOffset; } - + AudioInjectorLocalBuffer* getLocalBuffer() const { return _localBuffer; } AudioHRTF& getLocalHRTF() { return _localHRTF; } AudioFOA& getLocalFOA() { return _localFOA; } @@ -72,36 +74,36 @@ public: bool stateHas(AudioInjectorState state) const ; static void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } - static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options); - static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options); - static AudioInjector* playSound(SharedSoundPointer sound, const float volume, const float stretchFactor, const glm::vec3 position); + static AudioInjectorPointer playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options); + static AudioInjectorPointer playSound(const QByteArray& buffer, const AudioInjectorOptions options); + static AudioInjectorPointer playSound(SharedSoundPointer sound, const float volume, + const float stretchFactor, const glm::vec3 position); public slots: void restart(); - + void stop(); void triggerDeleteAfterFinish(); - void stopAndDeleteLater(); - + const AudioInjectorOptions& getOptions() const { return _options; } void setOptions(const AudioInjectorOptions& options); - + float getLoudness() const { return _loudness; } bool isPlaying() const { return !stateHas(AudioInjectorState::Finished); } void finish(); void finishLocalInjection(); void finishNetworkInjection(); - + signals: void finished(); void restarting(); - + private: int64_t injectNextFrame(); - bool inject(bool(AudioInjectorManager::*injection)(AudioInjector*)); + bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&)); bool injectLocally(); void deleteLocalBuffer(); - + static AbstractAudioInterface* _localAudioInterface; QByteArray _audioData; @@ -112,17 +114,17 @@ private: int _currentSendOffset { 0 }; std::unique_ptr _currentPacket { nullptr }; AudioInjectorLocalBuffer* _localBuffer { nullptr }; - + int64_t _nextFrame { 0 }; std::unique_ptr _frameTimer { nullptr }; quint16 _outgoingSequenceNumber { 0 }; - + // when the injector is local, we need this AudioHRTF _localHRTF; AudioFOA _localFOA; friend class AudioInjectorManager; }; -Q_DECLARE_METATYPE(AudioInjector*) - +Q_DECLARE_METATYPE(AudioInjectorPointer) + #endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioInjectorManager.cpp b/libraries/audio/src/AudioInjectorManager.cpp index c66e209ea9..f30d3093ec 100644 --- a/libraries/audio/src/AudioInjectorManager.cpp +++ b/libraries/audio/src/AudioInjectorManager.cpp @@ -21,26 +21,26 @@ AudioInjectorManager::~AudioInjectorManager() { _shouldStop = true; - + Lock lock(_injectorsMutex); - + // make sure any still living injectors are stopped and deleted while (!_injectors.empty()) { // grab the injector at the front auto& timePointerPair = _injectors.top(); - + // ask it to stop and be deleted - timePointerPair.second->stopAndDeleteLater(); - + timePointerPair.second->stop(); + _injectors.pop(); } - + // get rid of the lock now that we've stopped all living injectors lock.unlock(); - + // in case the thread is waiting for injectors wake it up now _injectorReady.notify_one(); - + // quit and wait on the manager thread, if we ever created it if (_thread) { _thread->quit(); @@ -51,10 +51,10 @@ AudioInjectorManager::~AudioInjectorManager() { void AudioInjectorManager::createThread() { _thread = new QThread; _thread->setObjectName("Audio Injector Thread"); - + // when the thread is started, have it call our run to handle injection of audio connect(_thread, &QThread::started, this, &AudioInjectorManager::run, Qt::DirectConnection); - + // start the thread _thread->start(); } @@ -63,20 +63,20 @@ void AudioInjectorManager::run() { while (!_shouldStop) { // wait until the next injector is ready, or until we get a new injector given to us Lock lock(_injectorsMutex); - + if (_injectors.size() > 0) { // when does the next injector need to send a frame? // do we get to wait or should we just go for it now? - + auto timeInjectorPair = _injectors.top(); - + auto nextTimestamp = timeInjectorPair.first; int64_t difference = int64_t(nextTimestamp - usecTimestampNow()); - + if (difference > 0) { _injectorReady.wait_for(lock, std::chrono::microseconds(difference)); } - + if (_injectors.size() > 0) { // loop through the injectors in the map and send whatever frames need to go out auto front = _injectors.top(); @@ -90,7 +90,7 @@ void AudioInjectorManager::run() { // either way we're popping this injector off - get a copy first auto injector = front.second; _injectors.pop(); - + if (!injector.isNull()) { // this is an injector that's ready to go, have it send a frame now auto nextCallDelta = injector->injectNextFrame(); @@ -100,7 +100,7 @@ void AudioInjectorManager::run() { heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector); } } - + if (_injectors.size() > 0) { front = _injectors.top(); } else { @@ -120,10 +120,10 @@ void AudioInjectorManager::run() { // we have no current injectors, wait until we get at least one before we do anything _injectorReady.wait(lock); } - + // unlock the lock in case something in process events needs to modify the queue lock.unlock(); - + QCoreApplication::processEvents(); } } @@ -139,36 +139,36 @@ bool AudioInjectorManager::wouldExceedLimits() { // Should be called inside of a return false; } -bool AudioInjectorManager::threadInjector(AudioInjector* injector) { +bool AudioInjectorManager::threadInjector(const AudioInjectorPointer& injector) { if (_shouldStop) { qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; return false; } - + // guard the injectors vector with a mutex Lock lock(_injectorsMutex); - + if (wouldExceedLimits()) { return false; } else { if (!_thread) { createThread(); } - + // move the injector to the QThread injector->moveToThread(_thread); - + // add the injector to the queue with a send timestamp of now - _injectors.emplace(usecTimestampNow(), InjectorQPointer { injector }); - + _injectors.emplace(usecTimestampNow(), injector); + // notify our wait condition so we can inject two frames for this injector immediately _injectorReady.notify_one(); - + return true; } } -bool AudioInjectorManager::restartFinishedInjector(AudioInjector* injector) { +bool AudioInjectorManager::restartFinishedInjector(const AudioInjectorPointer& injector) { if (_shouldStop) { qCDebug(audio) << "AudioInjectorManager::threadInjector asked to thread injector but is shutting down."; return false; @@ -181,8 +181,8 @@ bool AudioInjectorManager::restartFinishedInjector(AudioInjector* injector) { return false; } else { // add the injector to the queue with a send timestamp of now - _injectors.emplace(usecTimestampNow(), InjectorQPointer { injector }); - + _injectors.emplace(usecTimestampNow(), injector); + // notify our wait condition so we can inject two frames for this injector immediately _injectorReady.notify_one(); } diff --git a/libraries/audio/src/AudioInjectorManager.h b/libraries/audio/src/AudioInjectorManager.h index de5537856e..9aca3014e3 100644 --- a/libraries/audio/src/AudioInjectorManager.h +++ b/libraries/audio/src/AudioInjectorManager.h @@ -23,7 +23,7 @@ #include -class AudioInjector; +#include "AudioInjector.h" class AudioInjectorManager : public QObject, public Dependency { Q_OBJECT @@ -33,39 +33,38 @@ public: private slots: void run(); private: - - using InjectorQPointer = QPointer; - using TimeInjectorPointerPair = std::pair; - + + using TimeInjectorPointerPair = std::pair; + struct greaterTime { bool operator() (const TimeInjectorPointerPair& x, const TimeInjectorPointerPair& y) const { return x.first > y.first; } }; - + using InjectorQueue = std::priority_queue, greaterTime>; using Mutex = std::mutex; using Lock = std::unique_lock; - - bool threadInjector(AudioInjector* injector); - bool restartFinishedInjector(AudioInjector* injector); + + bool threadInjector(const AudioInjectorPointer& injector); + bool restartFinishedInjector(const AudioInjectorPointer& injector); void notifyInjectorReadyCondition() { _injectorReady.notify_one(); } bool wouldExceedLimits(); - + AudioInjectorManager() {}; AudioInjectorManager(const AudioInjectorManager&) = delete; AudioInjectorManager& operator=(const AudioInjectorManager&) = delete; - + void createThread(); - + QThread* _thread { nullptr }; bool _shouldStop { false }; InjectorQueue _injectors; Mutex _injectorsMutex; std::condition_variable _injectorReady; - + friend class AudioInjector; }; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 8ebc3fc24a..59f0d7375e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1102,7 +1102,34 @@ int Avatar::getJointIndex(const QString& name) const { QStringList Avatar::getJointNames() const { QStringList result; withValidJointIndicesCache([&]() { - result = _modelJointIndicesCache.keys(); + // find out how large the vector needs to be + int maxJointIndex = -1; + QHashIterator k(_modelJointIndicesCache); + while (k.hasNext()) { + k.next(); + int index = k.value(); + if (index > maxJointIndex) { + maxJointIndex = index; + } + } + // iterate through the hash and put joint names + // into the vector at their indices + QVector resultVector(maxJointIndex+1); + QHashIterator i(_modelJointIndicesCache); + while (i.hasNext()) { + i.next(); + int index = i.value(); + resultVector[index] = i.key(); + } + // convert to QList and drop out blanks + result = resultVector.toList(); + QMutableListIterator j(result); + while (j.hasNext()) { + QString jointName = j.next(); + if (jointName.isEmpty()) { + j.remove(); + } + } }); return result; } diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/script-engine/src/ScriptAudioInjector.cpp index c0ad2debd9..516f62401f 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/script-engine/src/ScriptAudioInjector.cpp @@ -21,7 +21,7 @@ QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* c // when the script goes down we want to cleanup the injector QObject::connect(engine, &QScriptEngine::destroyed, in, &ScriptAudioInjector::stopInjectorImmediately, Qt::DirectConnection); - + return engine->newQObject(in, QScriptEngine::ScriptOwnership); } @@ -29,10 +29,10 @@ void injectorFromScriptValue(const QScriptValue& object, ScriptAudioInjector*& o out = qobject_cast(object.toQObject()); } -ScriptAudioInjector::ScriptAudioInjector(AudioInjector* injector) : +ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) : _injector(injector) { - QObject::connect(injector, &AudioInjector::finished, this, &ScriptAudioInjector::finished); + QObject::connect(injector.data(), &AudioInjector::finished, this, &ScriptAudioInjector::finished); } ScriptAudioInjector::~ScriptAudioInjector() { @@ -44,5 +44,5 @@ ScriptAudioInjector::~ScriptAudioInjector() { void ScriptAudioInjector::stopInjectorImmediately() { qCDebug(scriptengine) << "ScriptAudioInjector::stopInjectorImmediately called to stop audio injector immediately."; - _injector->stopAndDeleteLater(); + _injector->stop(); } diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 4de12af62c..4c2871dd34 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -18,31 +18,31 @@ class ScriptAudioInjector : public QObject { Q_OBJECT - + Q_PROPERTY(bool playing READ isPlaying) Q_PROPERTY(float loudness READ getLoudness) Q_PROPERTY(AudioInjectorOptions options WRITE setOptions READ getOptions) public: - ScriptAudioInjector(AudioInjector* injector); + ScriptAudioInjector(const AudioInjectorPointer& injector); ~ScriptAudioInjector(); public slots: void restart() { _injector->restart(); } void stop() { _injector->stop(); } - + const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } - + float getLoudness() const { return _injector->getLoudness(); } bool isPlaying() const { return _injector->isPlaying(); } - + signals: void finished(); - + protected slots: void stopInjectorImmediately(); private: - QPointer _injector; - + AudioInjectorPointer _injector; + friend QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in); }; diff --git a/libraries/ui/src/ui/types/SoundEffect.h b/libraries/ui/src/ui/types/SoundEffect.h index 656f98dd8d..a7e29d86f9 100644 --- a/libraries/ui/src/ui/types/SoundEffect.h +++ b/libraries/ui/src/ui/types/SoundEffect.h @@ -15,6 +15,7 @@ #include class AudioInjector; +using AudioInjectorPointer = QSharedPointer; // SoundEffect object, exposed to qml only, not interface JavaScript. // This is used to play spatial sound effects on tablets/web entities from within QML. @@ -38,7 +39,7 @@ protected: QUrl _url; float _volume { 1.0f }; SharedSoundPointer _sound; - AudioInjector* _injector { nullptr }; + AudioInjectorPointer _injector; }; #endif // hifi_SoundEffect_h