int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { OctreeServer::didHandlePacketSend(this); // if we're shutting down, then exit early if (nodeData->isShuttingDown()) { return 0; } bool debug = _myServer->wantsDebugSending(); quint64 now = usecTimestampNow(); bool packetSent = false; // did we send a packet? int packetsSent = 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 (nodeData->shouldSuppressDuplicatePacket()) { nodeData->resetOctreePacket(); // we still need to reset it though! return packetsSent; // without sending... } // If we've got a stats message ready to send, then see if we can piggyback them together if (nodeData->stats.isReadyToSend() && !nodeData->isShuttingDown()) { // Send the stats message to the client unsigned char* statsMessage = nodeData->stats.getStatsMessage(); int statsMessageLength = nodeData->stats.getStatsMessageLength(); int piggyBackSize = nodeData->getPacketLength() + statsMessageLength; // If the size of the stats message and the voxel message will fit in a packet, then piggyback them if (piggyBackSize < MAX_PACKET_SIZE) { // copy voxel message to back of stats message memcpy(statsMessage + statsMessageLength, nodeData->getPacket(), nodeData->getPacketLength()); statsMessageLength += nodeData->getPacketLength(); // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since // there was nothing else to send. int thisWastedBytes = 0; _totalWastedBytes += thisWastedBytes; _totalBytes += nodeData->getPacketLength(); _totalPackets++; if (debug) { const unsigned char* messageData = nodeData->getPacket(); int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<const char*>(messageData)); const unsigned char* dataAt = messageData + numBytesPacketHeader; dataAt += sizeof(OCTREE_PACKET_FLAGS); OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); dataAt += sizeof(OCTREE_PACKET_SEQUENCE); OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); dataAt += sizeof(OCTREE_PACKET_SENT_TIME); qDebug() << "Adding stats to packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << " timestamp: " << timestamp << " statsMessageLength: " << statsMessageLength << " original size: " << nodeData->getPacketLength() << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } // actually send it OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, _node); packetSent = true; } else { // not enough room in the packet, send two packets OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, _node); // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since // there was nothing else to send. int thisWastedBytes = 0; _totalWastedBytes += thisWastedBytes; _totalBytes += statsMessageLength; _totalPackets++; if (debug) { const unsigned char* messageData = nodeData->getPacket(); int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<const char*>(messageData)); const unsigned char* dataAt = messageData + numBytesPacketHeader; dataAt += sizeof(OCTREE_PACKET_FLAGS); OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); dataAt += sizeof(OCTREE_PACKET_SEQUENCE); OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); dataAt += sizeof(OCTREE_PACKET_SENT_TIME); qDebug() << "Sending separate stats packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << " timestamp: " << timestamp << " size: " << statsMessageLength << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } trueBytesSent += statsMessageLength; truePacketsSent++; packetsSent++; OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), _node); packetSent = true; thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); _totalWastedBytes += thisWastedBytes; _totalBytes += nodeData->getPacketLength(); _totalPackets++; if (debug) { const unsigned char* messageData = nodeData->getPacket(); int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<const char*>(messageData)); const unsigned char* dataAt = messageData + numBytesPacketHeader; dataAt += sizeof(OCTREE_PACKET_FLAGS); OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); dataAt += sizeof(OCTREE_PACKET_SEQUENCE); OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); dataAt += sizeof(OCTREE_PACKET_SENT_TIME); qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << " timestamp: " << timestamp << " size: " << nodeData->getPacketLength() << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } } nodeData->stats.markAsSent(); } else { // If there's actually a packet waiting, then send it. if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) { // just send the voxel packet OctreeServer::didCallWriteDatagram(this); NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), _node); packetSent = true; int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); _totalWastedBytes += thisWastedBytes; _totalBytes += nodeData->getPacketLength(); _totalPackets++; if (debug) { const unsigned char* messageData = nodeData->getPacket(); int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<const char*>(messageData)); const unsigned char* dataAt = messageData + numBytesPacketHeader; dataAt += sizeof(OCTREE_PACKET_FLAGS); OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); dataAt += sizeof(OCTREE_PACKET_SEQUENCE); OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); dataAt += sizeof(OCTREE_PACKET_SENT_TIME); qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << " timestamp: " << timestamp << " size: " << nodeData->getPacketLength() << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } } } // remember to track our stats if (packetSent) { nodeData->stats.packetSent(nodeData->getPacketLength()); trueBytesSent += nodeData->getPacketLength(); truePacketsSent++; packetsSent++; nodeData->incrementSequenceNumber(); nodeData->resetOctreePacket(); } return packetsSent; }
void OctreeRenderer::processDatagram(ReceivedMessage& message, SharedNodePointer sourceNode) { bool extraDebugging = false; if (extraDebugging) { qCDebug(octree) << "OctreeRenderer::processDatagram()"; } if (!_tree) { qCDebug(octree) << "OctreeRenderer::processDatagram() called before init, calling init()..."; this->init(); } bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram()", showTimingDetails); if (message.getType() == getExpectedPacketType()) { PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram expected PacketType", showTimingDetails); // if we are getting inbound packets, then our tree is also viewing, and we should remember that fact. _tree->setIsViewing(true); OCTREE_PACKET_FLAGS flags; message.readPrimitive(&flags); OCTREE_PACKET_SEQUENCE sequence; message.readPrimitive(&sequence); OCTREE_PACKET_SENT_TIME sentAt; message.readPrimitive(&sentAt); bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); qint64 clockSkew = sourceNode ? sourceNode->getClockSkewUsec() : 0; qint64 flightTime = arrivedAt - sentAt + clockSkew; OCTREE_PACKET_INTERNAL_SECTION_SIZE sectionLength = 0; if (extraDebugging) { qCDebug(octree) << "OctreeRenderer::processDatagram() ... " "Got Packet Section color:" << packetIsColored << "compressed:" << packetIsCompressed << "sequence: " << sequence << "flight: " << flightTime << " usec" << "size:" << message.getSize() << "data:" << message.getBytesLeftToRead(); } _packetsInLastWindow++; int elementsPerPacket = 0; int entitiesPerPacket = 0; quint64 totalWaitingForLock = 0; quint64 totalUncompress = 0; quint64 totalReadBitsteam = 0; const QUuid& sourceUUID = message.getSourceID(); int subsection = 1; bool error = false; while (message.getBytesLeftToRead() > 0 && !error) { if (packetIsCompressed) { if (message.getBytesLeftToRead() > (qint64) sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { message.readPrimitive(§ionLength); } else { sectionLength = 0; error = true; } } else { sectionLength = message.getBytesLeftToRead(); } if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, sourceUUID, sourceNode, false, message.getVersion()); quint64 startUncompress, startLock = usecTimestampNow(); quint64 startReadBitsteam, endReadBitsteam; // FIXME STUTTER - there may be an opportunity to bump this lock outside of the // loop to reduce the amount of locking/unlocking we're doing _tree->withWriteLock([&] { startUncompress = usecTimestampNow(); OctreePacketData packetData(packetIsCompressed); packetData.loadFinalizedContent(reinterpret_cast<const unsigned char*>(message.getRawMessage() + message.getPosition()), sectionLength); if (extraDebugging) { qCDebug(octree) << "OctreeRenderer::processDatagram() ... " "Got Packet Section color:" << packetIsColored << "compressed:" << packetIsCompressed << "sequence: " << sequence << "flight: " << flightTime << " usec" << "size:" << message.getSize() << "data:" << message.getBytesLeftToRead() << "subsection:" << subsection << "sectionLength:" << sectionLength << "uncompressed:" << packetData.getUncompressedSize(); } if (extraDebugging) { qCDebug(octree) << "OctreeRenderer::processDatagram() ******* START _tree->readBitstreamToTree()..."; } startReadBitsteam = usecTimestampNow(); _tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); endReadBitsteam = usecTimestampNow(); if (extraDebugging) { qCDebug(octree) << "OctreeRenderer::processDatagram() ******* END _tree->readBitstreamToTree()..."; } }); // seek forwards in packet message.seek(message.getPosition() + sectionLength); elementsPerPacket += args.elementsPerPacket; entitiesPerPacket += args.entitiesPerPacket; _elementsInLastWindow += args.elementsPerPacket; _entitiesInLastWindow += args.entitiesPerPacket; totalWaitingForLock += (startUncompress - startLock); totalUncompress += (startReadBitsteam - startUncompress); totalReadBitsteam += (endReadBitsteam - startReadBitsteam); } subsection++; } _elementsPerPacket.updateAverage(elementsPerPacket); _entitiesPerPacket.updateAverage(entitiesPerPacket); _waitLockPerPacket.updateAverage(totalWaitingForLock); _uncompressPerPacket.updateAverage(totalUncompress); _readBitstreamPerPacket.updateAverage(totalReadBitsteam); quint64 now = usecTimestampNow(); if (_lastWindowAt == 0) { _lastWindowAt = now; } quint64 sinceLastWindow = now - _lastWindowAt; if (sinceLastWindow > USECS_PER_SECOND) { float packetsPerSecondInWindow = (float)_packetsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); float elementsPerSecondInWindow = (float)_elementsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); float entitiesPerSecondInWindow = (float)_entitiesInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); _packetsPerSecond.updateAverage(packetsPerSecondInWindow); _elementsPerSecond.updateAverage(elementsPerSecondInWindow); _entitiesPerSecond.updateAverage(entitiesPerSecondInWindow); _lastWindowAt = now; _packetsInLastWindow = 0; _elementsInLastWindow = 0; _entitiesInLastWindow = 0; } } }
void AudioMixer::run() { commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); NodeList* nodeList = NodeList::getInstance(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); nodeList->linkedDataCreateCallback = attachNewBufferToNode; int nextFrame = 0; timeval startTime; gettimeofday(&startTime, NULL); char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; while (!_isFinished) { foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData()) { ((AudioMixerClientData*) node->getLinkedData())->checkBuffersBeforeFrameSend(JITTER_BUFFER_SAMPLES); } } foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) { prepareMixForListeningNode(node.data()); int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeMixedAudio); memcpy(clientMixBuffer + numBytesPacketHeader, _clientSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); nodeList->writeDatagram(clientMixBuffer, NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader, node); } } // push forward the next output pointers for any audio buffers we used foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getLinkedData()) { ((AudioMixerClientData*) node->getLinkedData())->pushBuffersAfterFrameSend(); } } QCoreApplication::processEvents(); if (_isFinished) { break; } int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); } else { qDebug() << "AudioMixer loop took" << -usecToSleep << "of extra time. Not sleeping."; } } delete[] clientMixBuffer; }
inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight) { NodeList* nodeList = NodeList::getInstance(); Application* interface = Application::getInstance(); Avatar* interfaceAvatar = interface->getAvatar(); // Add Procedural effects to input samples addProceduralSounds(inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL); if (nodeList && inputLeft) { // Measure the loudness of the signal from the microphone and store in audio object float loudness = 0; for (int i = 0; i < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { loudness += abs(inputLeft[i]); } loudness /= BUFFER_LENGTH_SAMPLES_PER_CHANNEL; _lastInputLoudness = loudness; // add input (@microphone) data to the scope _scope->addSamples(0, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL); Node* audioMixer = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER); if (audioMixer) { glm::vec3 headPosition = interfaceAvatar->getHeadJointPosition(); glm::quat headOrientation = interfaceAvatar->getHead().getOrientation(); int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO); int leadingBytes = numBytesPacketHeader + sizeof(headPosition) + sizeof(headOrientation); // we need the amount of bytes in the buffer + 1 for type // + 12 for 3 floats for position + float for bearing + 1 attenuation byte unsigned char dataPacket[BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes]; PACKET_TYPE packetType = (Application::getInstance()->shouldEchoAudio()) ? PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO : PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO; unsigned char* currentPacketPtr = dataPacket + populateTypeAndVersion(dataPacket, packetType); // memcpy the three float positions memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); currentPacketPtr += (sizeof(headPosition)); // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); // copy the audio data to the last BUFFER_LENGTH_BYTES bytes of the data packet memcpy(currentPacketPtr, inputLeft, BUFFER_LENGTH_BYTES_PER_CHANNEL); nodeList->getNodeSocket()->send(audioMixer->getActiveSocket(), dataPacket, BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); interface->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO) .updateValue(BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); } } memset(outputLeft, 0, PACKET_LENGTH_BYTES_PER_CHANNEL); memset(outputRight, 0, PACKET_LENGTH_BYTES_PER_CHANNEL); AudioRingBuffer* ringBuffer = &_ringBuffer; // if there is anything in the ring buffer, decide what to do: if (ringBuffer->getEndOfLastWrite()) { if (!ringBuffer->isStarted() && ringBuffer->diffLastWriteNextOutput() < (PACKET_LENGTH_SAMPLES + _jitterBufferSamples * (ringBuffer->isStereo() ? 2 : 1))) { // // If not enough audio has arrived to start playback, keep waiting // #ifdef SHOW_AUDIO_DEBUG printLog("%i,%i,%i,%i\n", _packetsReceivedThisPlayback, ringBuffer->diffLastWriteNextOutput(), PACKET_LENGTH_SAMPLES, _jitterBufferSamples); #endif } else if (ringBuffer->isStarted() && ringBuffer->diffLastWriteNextOutput() == 0) { // // If we have started and now have run out of audio to send to the audio device, // this means we've starved and should restart. // ringBuffer->setStarted(false); _numStarves++; _packetsReceivedThisPlayback = 0; _wasStarved = 10; // Frames for which to render the indication that the system was starved. #ifdef SHOW_AUDIO_DEBUG printLog("Starved, remaining samples = %d\n", ringBuffer->diffLastWriteNextOutput()); #endif } else { // // We are either already playing back, or we have enough audio to start playing back. // if (!ringBuffer->isStarted()) { ringBuffer->setStarted(true); #ifdef SHOW_AUDIO_DEBUG printLog("starting playback %0.1f msecs delayed, jitter = %d, pkts recvd: %d \n", (usecTimestampNow() - usecTimestamp(&_firstPacketReceivedTime))/1000.0, _jitterBufferSamples, _packetsReceivedThisPlayback); #endif } // // play whatever we have in the audio buffer // // if we haven't fired off the flange effect, check if we should // TODO: lastMeasuredHeadYaw is now relative to body - check if this still works. int lastYawMeasured = fabsf(interfaceAvatar->getHeadYawRate()); if (!_samplesLeftForFlange && lastYawMeasured > MIN_FLANGE_EFFECT_THRESHOLD) { // we should flange for one second if ((_lastYawMeasuredMaximum = std::max(_lastYawMeasuredMaximum, lastYawMeasured)) != lastYawMeasured) { _lastYawMeasuredMaximum = std::min(_lastYawMeasuredMaximum, MIN_FLANGE_EFFECT_THRESHOLD); _samplesLeftForFlange = SAMPLE_RATE; _flangeIntensity = MIN_FLANGE_INTENSITY + ((_lastYawMeasuredMaximum - MIN_FLANGE_EFFECT_THRESHOLD) / (float)(MAX_FLANGE_EFFECT_THRESHOLD - MIN_FLANGE_EFFECT_THRESHOLD)) * (1 - MIN_FLANGE_INTENSITY); _flangeRate = FLANGE_BASE_RATE * _flangeIntensity; _flangeWeight = MAX_FLANGE_SAMPLE_WEIGHT * _flangeIntensity; } } for (int s = 0; s < PACKET_LENGTH_SAMPLES_PER_CHANNEL; s++) { int leftSample = ringBuffer->getNextOutput()[s]; int rightSample = ringBuffer->getNextOutput()[s + PACKET_LENGTH_SAMPLES_PER_CHANNEL]; if (_samplesLeftForFlange > 0) { float exponent = (SAMPLE_RATE - _samplesLeftForFlange - (SAMPLE_RATE / _flangeRate)) / (SAMPLE_RATE / _flangeRate); int sampleFlangeDelay = (SAMPLE_RATE / (1000 * _flangeIntensity)) * powf(2, exponent); if (_samplesLeftForFlange != SAMPLE_RATE || s >= (SAMPLE_RATE / 2000)) { // we have a delayed sample to add to this sample int16_t *flangeFrame = ringBuffer->getNextOutput(); int flangeIndex = s - sampleFlangeDelay; if (flangeIndex < 0) { // we need to grab the flange sample from earlier in the buffer flangeFrame = ringBuffer->getNextOutput() != ringBuffer->getBuffer() ? ringBuffer->getNextOutput() - PACKET_LENGTH_SAMPLES : ringBuffer->getNextOutput() + RING_BUFFER_LENGTH_SAMPLES - PACKET_LENGTH_SAMPLES; flangeIndex = PACKET_LENGTH_SAMPLES_PER_CHANNEL + (s - sampleFlangeDelay); } int16_t leftFlangeSample = flangeFrame[flangeIndex]; int16_t rightFlangeSample = flangeFrame[flangeIndex + PACKET_LENGTH_SAMPLES_PER_CHANNEL]; leftSample = (1 - _flangeWeight) * leftSample + (_flangeWeight * leftFlangeSample); rightSample = (1 - _flangeWeight) * rightSample + (_flangeWeight * rightFlangeSample); _samplesLeftForFlange--; if (_samplesLeftForFlange == 0) { _lastYawMeasuredMaximum = 0; } } } #ifndef TEST_AUDIO_LOOPBACK outputLeft[s] = leftSample; outputRight[s] = rightSample; #else outputLeft[s] = inputLeft[s]; outputRight[s] = inputLeft[s]; #endif } ringBuffer->setNextOutput(ringBuffer->getNextOutput() + PACKET_LENGTH_SAMPLES); if (ringBuffer->getNextOutput() == ringBuffer->getBuffer() + RING_BUFFER_LENGTH_SAMPLES) { ringBuffer->setNextOutput(ringBuffer->getBuffer()); } } } eventuallySendRecvPing(inputLeft, outputLeft, outputRight); // add output (@speakers) data just written to the scope _scope->addSamples(1, outputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL); _scope->addSamples(2, outputRight, BUFFER_LENGTH_SAMPLES_PER_CHANNEL); gettimeofday(&_lastCallbackTime, NULL); }
void AudioMixer::run() { ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer); auto nodeList = DependencyManager::get<NodeList>(); nodeList->addNodeTypeToInterestSet(NodeType::Agent); nodeList->linkedDataCreateCallback = [](Node* node) { node->setLinkedData(new AudioMixerClientData()); }; // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = nodeList->getDomainHandler(); 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); domainHandler.requestDomainSettings(); loop.exec(); if (domainHandler.getSettingsObject().isEmpty()) { qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; setFinished(true); return; } const QJsonObject& settingsObject = domainHandler.getSettingsObject(); // check the settings object to see if we have anything we can parse out parseSettingsObject(settingsObject); int nextFrame = 0; QElapsedTimer timer; timer.start(); int usecToSleep = AudioConstants::NETWORK_FRAME_USECS; const int TRAILING_AVERAGE_FRAMES = 100; int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; while (!_isFinished) { const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; const float RATIO_BACK_OFF = 0.02f; const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; if (usecToSleep < 0) { usecToSleep = 0; } _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + (usecToSleep * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS); float lastCutoffRatio = _performanceThrottlingRatio; bool hasRatioChanged = false; if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { // we're struggling - change our min required loudness to reduce some load _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { // we've recovered and can back off the required loudness _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; if (_performanceThrottlingRatio < 0) { _performanceThrottlingRatio = 0; } qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; hasRatioChanged = true; } if (hasRatioChanged) { // set out min audability threshold from the new ratio _minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio)); qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold; framesSinceCutoffEvent = 0; } } if (!hasRatioChanged) { ++framesSinceCutoffEvent; } quint64 now = usecTimestampNow(); if (now - _lastPerSecondCallbackTime > USECS_PER_SECOND) { perSecondActions(); _lastPerSecondCallbackTime = now; } nodeList->eachNode([&](const SharedNodePointer& node) { if (node->getLinkedData()) { AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData(); // this function will attempt to pop a frame from each audio stream. // a pointer to the popped data is stored as a member in InboundAudioStream. // That's how the popped audio data will be read for mixing (but only if the pop was successful) nodeData->checkBuffersBeforeFrameSend(); // if the stream should be muted, send mute packet if (nodeData->getAvatarAudioStream() && shouldMute(nodeData->getAvatarAudioStream()->getQuietestFrameLoudness())) { auto mutePacket = NLPacket::create(PacketType::NoisyMute, 0); nodeList->sendPacket(std::move(mutePacket), *node); } if (node->getType() == NodeType::Agent && node->getActiveSocket() && nodeData->getAvatarAudioStream()) { int streamsMixed = prepareMixForListeningNode(node.data()); std::unique_ptr<NLPacket> mixPacket; if (streamsMixed > 0) { int mixPacketBytes = sizeof(quint16) + AudioConstants::NETWORK_FRAME_BYTES_STEREO; mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes); // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); // pack mixed audio samples mixPacket->write(reinterpret_cast<char*>(_mixSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO); } else { int silentPacketBytes = sizeof(quint16) + sizeof(quint16); mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes); // pack sequence number quint16 sequence = nodeData->getOutgoingSequenceNumber(); mixPacket->writePrimitive(sequence); // pack number of silent audio samples quint16 numSilentSamples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; mixPacket->writePrimitive(numSilentSamples); } // Send audio environment sendAudioEnvironmentPacket(node); // send mixed audio packet nodeList->sendPacket(std::move(mixPacket), *node); nodeData->incrementOutgoingMixedAudioSequenceNumber(); // send an audio stream stats packet if it's time if (_sendAudioStreamStats) { nodeData->sendAudioStreamStatsPackets(node); _sendAudioStreamStats = false; } ++_sumListeners; } } }); ++_numStatFrames; // since we're a while loop we need to help Qt's event processing QCoreApplication::processEvents(); if (_isFinished) { // at this point the audio-mixer is done // check if we have a deferred delete event to process (which we should once finished) QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete); break; } usecToSleep = (++nextFrame * AudioConstants::NETWORK_FRAME_USECS) - timer.nsecsElapsed() / 1000; // ns to us if (usecToSleep > 0) { usleep(usecToSleep); } } }
void OctreeSceneStats::encodeStarted() { _encodeStart = usecTimestampNow(); }
void LODManager::autoAdjustLOD(float currentFPS) { // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't // really want to count them in our average, so we will ignore the real frame rates and stuff // our moving average with simulated good data const int IGNORE_THESE_SAMPLES = 100; if (_fpsAverageUpWindow.getSampleCount() < IGNORE_THESE_SAMPLES) { currentFPS = ASSUMED_FPS; _lastStable = _lastUpShift = _lastDownShift = usecTimestampNow(); } _fpsAverageStartWindow.updateAverage(currentFPS); _fpsAverageDownWindow.updateAverage(currentFPS); _fpsAverageUpWindow.updateAverage(currentFPS); quint64 now = usecTimestampNow(); bool changed = false; quint64 elapsedSinceDownShift = now - _lastDownShift; quint64 elapsedSinceUpShift = now - _lastUpShift; quint64 lastStableOrUpshift = glm::max(_lastUpShift, _lastStable); quint64 elapsedSinceStableOrUpShift = now - lastStableOrUpshift; if (_automaticLODAdjust) { // LOD Downward adjustment // If we've been downshifting, we watch a shorter downshift window so that we will quickly move toward our // target frame rate. But if we haven't just done a downshift (either because our last shift was an upshift, // or because we've just started out) then we look at a much longer window to consider whether or not to start // downshifting. bool doDownShift = false; if (_isDownshifting) { // only consider things if our DOWN_SHIFT time has elapsed... if (elapsedSinceDownShift > DOWN_SHIFT_ELPASED) { doDownShift = _fpsAverageDownWindow.getAverage() < getLODDecreaseFPS(); if (!doDownShift) { qCDebug(interfaceapp) << "---- WE APPEAR TO BE DONE DOWN SHIFTING -----"; _isDownshifting = false; _lastStable = now; } } } else { doDownShift = (elapsedSinceStableOrUpShift > START_SHIFT_ELPASED && _fpsAverageStartWindow.getAverage() < getLODDecreaseFPS()); } if (doDownShift) { // Octree items... stepwise adjustment if (_octreeSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale *= ADJUST_LOD_DOWN_BY; if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } changed = true; } if (changed) { if (_isDownshifting) { // subsequent downshift qCDebug(interfaceapp) << "adjusting LOD DOWN..." << "average fps for last "<< DOWN_SHIFT_WINDOW_IN_SECS <<"seconds was " << _fpsAverageDownWindow.getAverage() << "minimum is:" << getLODDecreaseFPS() << "elapsedSinceDownShift:" << elapsedSinceDownShift << " NEW _octreeSizeScale=" << _octreeSizeScale; } else { // first downshift qCDebug(interfaceapp) << "adjusting LOD DOWN after initial delay..." << "average fps for last "<< START_DELAY_WINDOW_IN_SECS <<"seconds was " << _fpsAverageStartWindow.getAverage() << "minimum is:" << getLODDecreaseFPS() << "elapsedSinceUpShift:" << elapsedSinceUpShift << " NEW _octreeSizeScale=" << _octreeSizeScale; } _lastDownShift = now; _isDownshifting = true; emit LODDecreased(); } } else { // LOD Upward adjustment if (elapsedSinceUpShift > UP_SHIFT_ELPASED) { if (_fpsAverageUpWindow.getAverage() > getLODIncreaseFPS()) { // Octee items... stepwise adjustment if (_octreeSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { if (_octreeSizeScale < ADJUST_LOD_MIN_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MIN_SIZE_SCALE; } else { _octreeSizeScale *= ADJUST_LOD_UP_BY; } if (_octreeSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { _octreeSizeScale = ADJUST_LOD_MAX_SIZE_SCALE; } changed = true; } } if (changed) { qCDebug(interfaceapp) << "adjusting LOD UP... average fps for last "<< UP_SHIFT_WINDOW_IN_SECS <<"seconds was " << _fpsAverageUpWindow.getAverage() << "upshift point is:" << getLODIncreaseFPS() << "elapsedSinceUpShift:" << elapsedSinceUpShift << " NEW _octreeSizeScale=" << _octreeSizeScale; _lastUpShift = now; _isDownshifting = false; emit LODIncreased(); } } } if (changed) { auto lodToolsDialog = DependencyManager::get<DialogsManager>()->getLodToolsDialog(); if (lodToolsDialog) { lodToolsDialog->reloadSliders(); } } } }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { assert(_entity); assert(entityTreeIsLocked()); if (!_body->isActive()) { // make sure all derivatives are zero _entity->setVelocity(Vectors::ZERO); _entity->setAngularVelocity(Vectors::ZERO); _entity->setAcceleration(Vectors::ZERO); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let // the entity server's estimates include gravity. const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(gravity); } else { _entity->setAcceleration(Vectors::ZERO); } if (!_body->isStaticOrKinematicObject()) { const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec bool movingSlowlyLinear = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD); bool movingSlowlyAngular = glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == Vectors::ZERO; if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies glm::vec3 zero(0.0f); _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } } _numInactiveUpdates = 0; } // remember properties for local server prediction Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getActionData(); EntityItemProperties properties; // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); if (_entity->actionDataNeedsTransmit()) { _entity->setActionDataNeedsTransmit(false); properties.setActionData(_serverActionData); } if (properties.parentRelatedPropertyChanged() && _entity->computePuffedQueryAACube()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); #ifdef WANT_DEBUG quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); #endif //def WANT_DEBUG if (_numInactiveUpdates > 0) { // we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); _outgoingPriority = 0; _entity->setPendingOwnershipPriority(_outgoingPriority, now); } else if (Physics::getSessionUUID() != _entity->getSimulatorID()) { // we don't own the simulation for this entity yet, but we're sending a bid for it quint8 bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; // copy _outgoingPriority into pendingPriority... _entity->setPendingOwnershipPriority(_outgoingPriority, now); // ...then reset _outgoingPriority in preparation for the next frame _outgoingPriority = 0; } else if (_outgoingPriority != _entity->getSimulationPriority()) { // we own the simulation but our desired priority has changed if (_outgoingPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to change the priority properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); } _entity->setPendingOwnershipPriority(_outgoingPriority, now); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender); #ifdef WANT_DEBUG qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; #endif EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; properties.setClientOnly(_entity->getClientOnly()); properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, id, properties); _entity->setLastBroadcast(now); // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. _entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { if (descendant->getNestableType() == NestableType::Entity) { EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant); if (descendant->computePuffedQueryAACube()) { EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, tree, descendant->getID(), newQueryCubeProperties); entityDescendant->setLastBroadcast(now); } } }); _lastStep = step; }
void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar(); //Static variables used for storing controller state static quint64 pressedTime[NUMBER_OF_RETICLES] = { 0ULL, 0ULL, 0ULL }; static bool isPressed[NUMBER_OF_RETICLES] = { false, false, false }; static bool stateWhenPressed[NUMBER_OF_RETICLES] = { false, false, false }; const HandData* handData = DependencyManager::get<AvatarManager>()->getMyAvatar()->getHandData(); for (unsigned int palmIndex = 2; palmIndex < 4; palmIndex++) { const int index = palmIndex - 1; const PalmData* palmData = NULL; if (palmIndex >= handData->getPalms().size()) { return; } if (handData->getPalms()[palmIndex].isActive()) { palmData = &handData->getPalms()[palmIndex]; } else { continue; } int controllerButtons = palmData->getControllerButtons(); //Check for if we should toggle or drag the magnification window if (controllerButtons & BUTTON_3) { if (isPressed[index] == false) { //We are now dragging the window isPressed[index] = true; //set the pressed time in us pressedTime[index] = usecTimestampNow(); stateWhenPressed[index] = _magActive[index]; } } else if (isPressed[index]) { isPressed[index] = false; //If the button was only pressed for < 250 ms //then disable it. const int MAX_BUTTON_PRESS_TIME = 250 * MSECS_TO_USECS; if (usecTimestampNow() < pressedTime[index] + MAX_BUTTON_PRESS_TIME) { _magActive[index] = !stateWhenPressed[index]; } } //if we have the oculus, we should make the cursor smaller since it will be //magnified if (qApp->isHMDMode()) { QPoint point = getPalmClickLocation(palmData); _reticlePosition[index] = point; //When button 2 is pressed we drag the mag window if (isPressed[index]) { _magActive[index] = true; } // If oculus is enabled, we draw the crosshairs later continue; } auto canvasSize = qApp->getCanvasSize(); int mouseX, mouseY; if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseLasers)) { QPoint res = getPalmClickLocation(palmData); mouseX = res.x(); mouseY = res.y(); } else { // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection(); // Get the angles, scaled between (-0.5,0.5) float xAngle = (atan2(direction.z, direction.x) + M_PI_2); float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2)); // Get the pixel range over which the xAngle and yAngle are scaled float cursorRange = canvasSize.x * SixenseManager::getInstance().getCursorPixelRangeMult(); mouseX = (canvasSize.x / 2.0f + cursorRange * xAngle); mouseY = (canvasSize.y / 2.0f + cursorRange * yAngle); } //If the cursor is out of the screen then don't render it if (mouseX < 0 || mouseX >= (int)canvasSize.x || mouseY < 0 || mouseY >= (int)canvasSize.y) { _reticleActive[index] = false; continue; } _reticleActive[index] = true; const float reticleSize = 40.0f; mouseX -= reticleSize / 2.0f; mouseY += reticleSize / 2.0f; glm::vec2 topLeft(mouseX, mouseY); glm::vec2 bottomRight(mouseX + reticleSize, mouseY - reticleSize); glm::vec2 texCoordTopLeft(0.0f, 0.0f); glm::vec2 texCoordBottomRight(1.0f, 1.0f); DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, glm::vec4(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f)); } }
int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) { const struct mg_request_info* ri = mg_get_request_info(connection); VoxelServer* theServer = GetInstance(); #ifdef FORCE_CRASH if (strcmp(ri->uri, "/force_crash") == 0 && strcmp(ri->request_method, "GET") == 0) { qDebug() << "About to force a crash!\n"; int foo; int* forceCrash = &foo; mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n\r\n"); mg_printf(connection, "%s", "forcing a crash....\r\n"); delete[] forceCrash; mg_printf(connection, "%s", "did it crash....\r\n"); return 1; } #endif bool showStats = false; if (strcmp(ri->uri, "/") == 0 && strcmp(ri->request_method, "GET") == 0) { showStats = true; } if (strcmp(ri->uri, "/resetStats") == 0 && strcmp(ri->request_method, "GET") == 0) { theServer->_voxelServerPacketProcessor->resetStats(); showStats = true; } if (showStats) { uint64_t checkSum; // return a 200 mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n"); mg_printf(connection, "%s", "Content-Type: text/html\r\n\r\n"); mg_printf(connection, "%s", "<html><doc>\r\n"); mg_printf(connection, "%s", "<pre>\r\n"); mg_printf(connection, "%s", "<b>Your Voxel Server is running... <a href='/'>[RELOAD]</a></b>\r\n"); tm* localtm = localtime(&theServer->_started); const int MAX_TIME_LENGTH = 128; char buffer[MAX_TIME_LENGTH]; strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm); mg_printf(connection, "Running since: %s", buffer); // Convert now to tm struct for UTC tm* gmtm = gmtime(&theServer->_started); if (gmtm != NULL) { strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", gmtm); mg_printf(connection, " [%s UTM] ", buffer); } mg_printf(connection, "%s", "\r\n"); uint64_t now = usecTimestampNow(); const int USECS_PER_MSEC = 1000; uint64_t msecsElapsed = (now - theServer->_startedUSecs) / USECS_PER_MSEC; const int MSECS_PER_SEC = 1000; const int SECS_PER_MIN = 60; const int MIN_PER_HOUR = 60; const int MSECS_PER_MIN = MSECS_PER_SEC * SECS_PER_MIN; float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC; int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR; int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR)); mg_printf(connection, "%s", "Uptime: "); if (hours > 0) { mg_printf(connection, "%d hour%s ", hours, (hours > 1) ? "s" : "" ); } if (minutes > 0) { mg_printf(connection, "%d minute%s ", minutes, (minutes > 1) ? "s" : ""); } if (seconds > 0) { mg_printf(connection, "%.3f seconds ", seconds); } mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "\r\n"); // display voxel file load time if (theServer->isInitialLoadComplete()) { tm* voxelsLoadedAtLocal = localtime(theServer->getLoadCompleted()); const int MAX_TIME_LENGTH = 128; char buffer[MAX_TIME_LENGTH]; strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", voxelsLoadedAtLocal); mg_printf(connection, "Voxels Loaded At: %s", buffer); // Convert now to tm struct for UTC tm* voxelsLoadedAtUTM = gmtime(theServer->getLoadCompleted()); if (gmtm != NULL) { strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", voxelsLoadedAtUTM); mg_printf(connection, " [%s UTM] ", buffer); } mg_printf(connection, "%s", "\r\n"); uint64_t msecsElapsed = theServer->getLoadElapsedTime() / USECS_PER_MSEC;; float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC; int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR; int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR)); mg_printf(connection, "%s", "Voxels Load Took: "); if (hours > 0) { mg_printf(connection, "%d hour%s ", hours, (hours > 1) ? "s" : "" ); } if (minutes > 0) { mg_printf(connection, "%d minute%s ", minutes, (minutes > 1) ? "s" : ""); } if (seconds >= 0) { mg_printf(connection, "%.3f seconds", seconds); } mg_printf(connection, "%s", "\r\n"); } else { mg_printf(connection, "%s", "Voxels not yet loaded...\r\n"); } mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "<b>Configuration:</b>\r\n"); for (int i = 1; i < theServer->_argc; i++) { mg_printf(connection, "%s ", theServer->_argv[i]); } mg_printf(connection, "%s", "\r\n"); // one to end the config line mg_printf(connection, "%s", "\r\n"); // two more for spacing mg_printf(connection, "%s", "\r\n"); // display scene stats unsigned long nodeCount = VoxelNode::getNodeCount(); unsigned long internalNodeCount = VoxelNode::getInternalNodeCount(); unsigned long leafNodeCount = VoxelNode::getLeafNodeCount(); QLocale locale(QLocale::English); const float AS_PERCENT = 100.0; mg_printf(connection, "%s", "<b>Current Nodes in scene:</b>\r\n"); mg_printf(connection, " Total Nodes: %s nodes\r\n", locale.toString((uint)nodeCount).rightJustified(16, ' ').toLocal8Bit().constData()); mg_printf(connection, " Internal Nodes: %s nodes (%5.2f%%)\r\n", locale.toString((uint)internalNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)internalNodeCount / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Leaf Nodes: %s nodes (%5.2f%%)\r\n", locale.toString((uint)leafNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)leafNodeCount / (float)nodeCount) * AS_PERCENT); mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "\r\n"); // display inbound packet stats mg_printf(connection, "%s", "<b>Voxel Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n"); uint64_t averageTransitTimePerPacket = theServer->_voxelServerPacketProcessor->getAverateTransitTimePerPacket(); uint64_t averageProcessTimePerPacket = theServer->_voxelServerPacketProcessor->getAverageProcessTimePerPacket(); uint64_t averageLockWaitTimePerPacket = theServer->_voxelServerPacketProcessor->getAverageLockWaitTimePerPacket(); uint64_t averageProcessTimePerVoxel = theServer->_voxelServerPacketProcessor->getAverageProcessTimePerVoxel(); uint64_t averageLockWaitTimePerVoxel = theServer->_voxelServerPacketProcessor->getAverageLockWaitTimePerVoxel(); uint64_t totalVoxelsProcessed = theServer->_voxelServerPacketProcessor->getTotalVoxelsProcessed(); uint64_t totalPacketsProcessed = theServer->_voxelServerPacketProcessor->getTotalPacketsProcessed(); float averageVoxelsPerPacket = totalPacketsProcessed == 0 ? 0 : totalVoxelsProcessed / totalPacketsProcessed; const int COLUMN_WIDTH = 10; mg_printf(connection, " Total Inbound Packets: %s packets\r\n", locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Total Inbound Voxels: %s voxels\r\n", locale.toString((uint)totalVoxelsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Inbound Voxels/Packet: %f voxels/packet\r\n", averageVoxelsPerPacket); mg_printf(connection, " Average Transit Time/Packet: %s usecs\r\n", locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Process Time/Packet: %s usecs\r\n", locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Wait Lock Time/Packet: %s usecs\r\n", locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Process Time/Voxel: %s usecs\r\n", locale.toString((uint)averageProcessTimePerVoxel).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Wait Lock Time/Voxel: %s usecs\r\n", locale.toString((uint)averageLockWaitTimePerVoxel).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); int senderNumber = 0; NodeToSenderStatsMap& allSenderStats = theServer->_voxelServerPacketProcessor->getSingleSenderStats(); for (NodeToSenderStatsMapIterator i = allSenderStats.begin(); i != allSenderStats.end(); i++) { senderNumber++; QUuid senderID = i->first; SingleSenderStats& senderStats = i->second; mg_printf(connection, "\r\n Stats for sender %d uuid: %s\r\n", senderNumber, senderID.toString().toLocal8Bit().constData()); averageTransitTimePerPacket = senderStats.getAverateTransitTimePerPacket(); averageProcessTimePerPacket = senderStats.getAverageProcessTimePerPacket(); averageLockWaitTimePerPacket = senderStats.getAverageLockWaitTimePerPacket(); averageProcessTimePerVoxel = senderStats.getAverageProcessTimePerVoxel(); averageLockWaitTimePerVoxel = senderStats.getAverageLockWaitTimePerVoxel(); totalVoxelsProcessed = senderStats.getTotalVoxelsProcessed(); totalPacketsProcessed = senderStats.getTotalPacketsProcessed(); mg_printf(connection, " Total Inbound Packets: %s packets\r\n", locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Total Inbound Voxels: %s voxels\r\n", locale.toString((uint)totalVoxelsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Inbound Voxels/Packet: %f voxels/packet\r\n", averageVoxelsPerPacket); mg_printf(connection, " Average Transit Time/Packet: %s usecs\r\n", locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Process Time/Packet: %s usecs\r\n", locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Wait Lock Time/Packet: %s usecs\r\n", locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Process Time/Voxel: %s usecs\r\n", locale.toString((uint)averageProcessTimePerVoxel).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); mg_printf(connection, " Average Wait Lock Time/Voxel: %s usecs\r\n", locale.toString((uint)averageLockWaitTimePerVoxel).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData()); } mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "\r\n"); // display memory usage stats mg_printf(connection, "%s", "<b>Current Memory Usage Statistics</b>\r\n"); mg_printf(connection, "\r\nVoxelNode size... %ld bytes\r\n", sizeof(VoxelNode)); mg_printf(connection, "%s", "\r\n"); const char* memoryScaleLabel; const float MEGABYTES = 1000000.f; const float GIGABYTES = 1000000000.f; float memoryScale; if (VoxelNode::getTotalMemoryUsage() / MEGABYTES < 1000.0f) { memoryScaleLabel = "MB"; memoryScale = MEGABYTES; } else { memoryScaleLabel = "GB"; memoryScale = GIGABYTES; } mg_printf(connection, "Voxel Node Memory Usage: %8.2f %s\r\n", VoxelNode::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "Octcode Memory Usage: %8.2f %s\r\n", VoxelNode::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "External Children Memory Usage: %8.2f %s\r\n", VoxelNode::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "%s", " -----------\r\n"); mg_printf(connection, " Total: %8.2f %s\r\n", VoxelNode::getTotalMemoryUsage() / memoryScale, memoryScaleLabel); mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "VoxelNode Children Population Statistics...\r\n"); checkSum = 0; for (int i=0; i <= NUMBER_OF_CHILDREN; i++) { checkSum += VoxelNode::getChildrenCount(i); mg_printf(connection, " Nodes with %d children: %s nodes (%5.2f%%)\r\n", i, locale.toString((uint)VoxelNode::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)VoxelNode::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT); } mg_printf(connection, "%s", " ----------------------\r\n"); mg_printf(connection, " Total: %s nodes\r\n", locale.toString((uint)checkSum).rightJustified(16, ' ').toLocal8Bit().constData()); #ifdef BLENDED_UNION_CHILDREN mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "VoxelNode Children Encoding Statistics...\r\n"); mg_printf(connection, " Single or No Children: %10.llu nodes (%5.2f%%)\r\n", VoxelNode::getSingleChildrenCount(), ((float)VoxelNode::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Two Children as Offset: %10.llu nodes (%5.2f%%)\r\n", VoxelNode::getTwoChildrenOffsetCount(), ((float)VoxelNode::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Two Children as External: %10.llu nodes (%5.2f%%)\r\n", VoxelNode::getTwoChildrenExternalCount(), ((float)VoxelNode::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Three Children as Offset: %10.llu nodes (%5.2f%%)\r\n", VoxelNode::getThreeChildrenOffsetCount(), ((float)VoxelNode::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Three Children as External: %10.llu nodes (%5.2f%%)\r\n", VoxelNode::getThreeChildrenExternalCount(), ((float)VoxelNode::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); mg_printf(connection, " Children as External Array: %10.llu nodes (%5.2f%%)\r\n", VoxelNode::getExternalChildrenCount(), ((float)VoxelNode::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT); checkSum = VoxelNode::getSingleChildrenCount() + VoxelNode::getTwoChildrenOffsetCount() + VoxelNode::getTwoChildrenExternalCount() + VoxelNode::getThreeChildrenOffsetCount() + VoxelNode::getThreeChildrenExternalCount() + VoxelNode::getExternalChildrenCount(); mg_printf(connection, "%s", " ----------------\r\n"); mg_printf(connection, " Total: %10.llu nodes\r\n", checkSum); mg_printf(connection, " Expected: %10.lu nodes\r\n", nodeCount); mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "In other news....\r\n"); mg_printf(connection, "could store 4 children internally: %10.llu nodes\r\n", VoxelNode::getCouldStoreFourChildrenInternally()); mg_printf(connection, "could NOT store 4 children internally: %10.llu nodes\r\n", VoxelNode::getCouldNotStoreFourChildrenInternally()); #endif mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "\r\n"); mg_printf(connection, "%s", "</pre>\r\n"); mg_printf(connection, "%s", "</doc></html>"); return 1; } else { // have mongoose process this request from the document_root return 0; } }
//int main(int argc, const char * argv[]) { void VoxelServer::run() { const char VOXEL_SERVER_LOGGING_TARGET_NAME[] = "voxel-server"; // change the logging target name while this is running Logging::setTargetName(VOXEL_SERVER_LOGGING_TARGET_NAME); // Now would be a good time to parse our arguments, if we got them as assignment if (getNumPayloadBytes() > 0) { parsePayload(); } qInstallMessageHandler(Logging::verboseMessageHandler); const char* STATUS_PORT = "--statusPort"; const char* statusPort = getCmdOption(_argc, _argv, STATUS_PORT); if (statusPort) { int statusPortNumber = atoi(statusPort); initMongoose(statusPortNumber); } const char* JURISDICTION_FILE = "--jurisdictionFile"; const char* jurisdictionFile = getCmdOption(_argc, _argv, JURISDICTION_FILE); if (jurisdictionFile) { qDebug("jurisdictionFile=%s\n", jurisdictionFile); qDebug("about to readFromFile().... jurisdictionFile=%s\n", jurisdictionFile); _jurisdiction = new JurisdictionMap(jurisdictionFile); qDebug("after readFromFile().... jurisdictionFile=%s\n", jurisdictionFile); } else { const char* JURISDICTION_ROOT = "--jurisdictionRoot"; const char* jurisdictionRoot = getCmdOption(_argc, _argv, JURISDICTION_ROOT); if (jurisdictionRoot) { qDebug("jurisdictionRoot=%s\n", jurisdictionRoot); } const char* JURISDICTION_ENDNODES = "--jurisdictionEndNodes"; const char* jurisdictionEndNodes = getCmdOption(_argc, _argv, JURISDICTION_ENDNODES); if (jurisdictionEndNodes) { qDebug("jurisdictionEndNodes=%s\n", jurisdictionEndNodes); } if (jurisdictionRoot || jurisdictionEndNodes) { _jurisdiction = new JurisdictionMap(jurisdictionRoot, jurisdictionEndNodes); } } // should we send environments? Default is yes, but this command line suppresses sending const char* DUMP_VOXELS_ON_MOVE = "--dumpVoxelsOnMove"; _dumpVoxelsOnMove = cmdOptionExists(_argc, _argv, DUMP_VOXELS_ON_MOVE); qDebug("dumpVoxelsOnMove=%s\n", debug::valueOf(_dumpVoxelsOnMove)); // should we send environments? Default is yes, but this command line suppresses sending const char* SEND_ENVIRONMENTS = "--sendEnvironments"; bool dontSendEnvironments = !cmdOptionExists(_argc, _argv, SEND_ENVIRONMENTS); if (dontSendEnvironments) { qDebug("Sending environments suppressed...\n"); _sendEnvironments = false; } else { // should we send environments? Default is yes, but this command line suppresses sending const char* MINIMAL_ENVIRONMENT = "--minimalEnvironment"; _sendMinimalEnvironment = cmdOptionExists(_argc, _argv, MINIMAL_ENVIRONMENT); qDebug("Using Minimal Environment=%s\n", debug::valueOf(_sendMinimalEnvironment)); } qDebug("Sending environments=%s\n", debug::valueOf(_sendEnvironments)); NodeList* nodeList = NodeList::getInstance(); nodeList->setOwnerType(NODE_TYPE_VOXEL_SERVER); // we need to ask the DS about agents so we can ping/reply with them const char nodeTypesOfInterest[] = { NODE_TYPE_AGENT, NODE_TYPE_ANIMATION_SERVER}; nodeList->setNodeTypesOfInterest(nodeTypesOfInterest, sizeof(nodeTypesOfInterest)); setvbuf(stdout, NULL, _IOLBF, 0); // tell our NodeList about our desire to get notifications nodeList->addHook(&_nodeWatcher); nodeList->linkedDataCreateCallback = &attachVoxelNodeDataToNode; nodeList->startSilentNodeRemovalThread(); srand((unsigned)time(0)); const char* DISPLAY_VOXEL_STATS = "--displayVoxelStats"; _displayVoxelStats = cmdOptionExists(_argc, _argv, DISPLAY_VOXEL_STATS); qDebug("displayVoxelStats=%s\n", debug::valueOf(_displayVoxelStats)); const char* VERBOSE_DEBUG = "--verboseDebug"; _verboseDebug = cmdOptionExists(_argc, _argv, VERBOSE_DEBUG); qDebug("verboseDebug=%s\n", debug::valueOf(_verboseDebug)); const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending"; _debugVoxelSending = cmdOptionExists(_argc, _argv, DEBUG_VOXEL_SENDING); qDebug("debugVoxelSending=%s\n", debug::valueOf(_debugVoxelSending)); const char* DEBUG_VOXEL_RECEIVING = "--debugVoxelReceiving"; _debugVoxelReceiving = cmdOptionExists(_argc, _argv, DEBUG_VOXEL_RECEIVING); qDebug("debugVoxelReceiving=%s\n", debug::valueOf(_debugVoxelReceiving)); const char* WANT_ANIMATION_DEBUG = "--shouldShowAnimationDebug"; _shouldShowAnimationDebug = cmdOptionExists(_argc, _argv, WANT_ANIMATION_DEBUG); qDebug("shouldShowAnimationDebug=%s\n", debug::valueOf(_shouldShowAnimationDebug)); // By default we will voxel persist, if you want to disable this, then pass in this parameter const char* NO_VOXEL_PERSIST = "--NoVoxelPersist"; if (cmdOptionExists(_argc, _argv, NO_VOXEL_PERSIST)) { _wantVoxelPersist = false; } qDebug("wantVoxelPersist=%s\n", debug::valueOf(_wantVoxelPersist)); // if we want Voxel Persistence, set up the local file and persist thread if (_wantVoxelPersist) { // Check to see if the user passed in a command line option for setting packet send rate const char* VOXELS_PERSIST_FILENAME = "--voxelsPersistFilename"; const char* voxelsPersistFilenameParameter = getCmdOption(_argc, _argv, VOXELS_PERSIST_FILENAME); if (voxelsPersistFilenameParameter) { strcpy(_voxelPersistFilename, voxelsPersistFilenameParameter); } else { //strcpy(voxelPersistFilename, _wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); strcpy(_voxelPersistFilename, LOCAL_VOXELS_PERSIST_FILE); } qDebug("voxelPersistFilename=%s\n", _voxelPersistFilename); // now set up VoxelPersistThread _voxelPersistThread = new VoxelPersistThread(&_serverTree, _voxelPersistFilename); if (_voxelPersistThread) { _voxelPersistThread->initialize(true); } } // Check to see if the user passed in a command line option for loading an old style local // Voxel File. If so, load it now. This is not the same as a voxel persist file const char* INPUT_FILE = "-i"; const char* voxelsFilename = getCmdOption(_argc, _argv, INPUT_FILE); if (voxelsFilename) { _serverTree.readFromSVOFile(voxelsFilename); } // Check to see if the user passed in a command line option for setting packet send rate const char* PACKETS_PER_SECOND = "--packetsPerSecond"; const char* packetsPerSecond = getCmdOption(_argc, _argv, PACKETS_PER_SECOND); if (packetsPerSecond) { _packetsPerClientPerInterval = atoi(packetsPerSecond) / INTERVALS_PER_SECOND; if (_packetsPerClientPerInterval < 1) { _packetsPerClientPerInterval = 1; } qDebug("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, _packetsPerClientPerInterval); } sockaddr senderAddress; unsigned char* packetData = new unsigned char[MAX_PACKET_SIZE]; ssize_t packetLength; timeval lastDomainServerCheckIn = {}; // set up our jurisdiction broadcaster... _jurisdictionSender = new JurisdictionSender(_jurisdiction); if (_jurisdictionSender) { _jurisdictionSender->initialize(true); } // set up our VoxelServerPacketProcessor _voxelServerPacketProcessor = new VoxelServerPacketProcessor(this); if (_voxelServerPacketProcessor) { _voxelServerPacketProcessor->initialize(true); } // Convert now to tm struct for local timezone tm* localtm = localtime(&_started); const int MAX_TIME_LENGTH = 128; char localBuffer[MAX_TIME_LENGTH] = { 0 }; char utcBuffer[MAX_TIME_LENGTH] = { 0 }; strftime(localBuffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm); // Convert now to tm struct for UTC tm* gmtm = gmtime(&_started); if (gmtm != NULL) { strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm); } qDebug() << "Now running... started at: " << localBuffer << utcBuffer << "\n"; // loop to send to nodes requesting data while (true) { // check for >= in case one gets past the goalie if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { qDebug() << "Exit loop... getInstance()->getNumNoReplyDomainCheckIns() >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS\n"; break; } // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); NodeList::getInstance()->sendDomainServerCheckIn(); } // ping our inactive nodes to punch holes with them nodeList->possiblyPingInactiveNodes(); if (nodeList->getNodeSocket()->receive(&senderAddress, packetData, &packetLength) && packetVersionMatch(packetData)) { int numBytesPacketHeader = numBytesForPacketHeader(packetData); if (packetData[0] == PACKET_TYPE_VOXEL_QUERY) { // If we got a PACKET_TYPE_VOXEL_QUERY, then we're talking to an NODE_TYPE_AVATAR, and we // need to make sure we have it in our nodeList. QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*)packetData + numBytesPacketHeader, NUM_BYTES_RFC4122_UUID)); Node* node = nodeList->nodeWithUUID(nodeUUID); if (node) { nodeList->updateNodeWithData(node, &senderAddress, packetData, packetLength); if (!node->getActiveSocket()) { // we don't have an active socket for this node, but they're talking to us // this means they've heard from us and can reply, let's assume public is active node->activatePublicSocket(); } VoxelNodeData* nodeData = (VoxelNodeData*) node->getLinkedData(); if (nodeData && !nodeData->isVoxelSendThreadInitalized()) { nodeData->initializeVoxelSendThread(this); } } } else if (packetData[0] == PACKET_TYPE_VOXEL_JURISDICTION_REQUEST) { if (_jurisdictionSender) { _jurisdictionSender->queueReceivedPacket(senderAddress, packetData, packetLength); } } else if (_voxelServerPacketProcessor && (packetData[0] == PACKET_TYPE_SET_VOXEL || packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE || packetData[0] == PACKET_TYPE_ERASE_VOXEL || packetData[0] == PACKET_TYPE_Z_COMMAND)) { const char* messageName; switch (packetData[0]) { case PACKET_TYPE_SET_VOXEL: messageName = "PACKET_TYPE_SET_VOXEL"; break; case PACKET_TYPE_SET_VOXEL_DESTRUCTIVE: messageName = "PACKET_TYPE_SET_VOXEL_DESTRUCTIVE"; break; case PACKET_TYPE_ERASE_VOXEL: messageName = "PACKET_TYPE_ERASE_VOXEL"; break; } int numBytesPacketHeader = numBytesForPacketHeader(packetData); if (packetData[0] != PACKET_TYPE_Z_COMMAND) { unsigned short int sequence = (*((unsigned short int*)(packetData + numBytesPacketHeader))); uint64_t sentAt = (*((uint64_t*)(packetData + numBytesPacketHeader + sizeof(sequence)))); uint64_t arrivedAt = usecTimestampNow(); uint64_t transitTime = arrivedAt - sentAt; if (wantShowAnimationDebug() || wantsDebugVoxelReceiving()) { printf("RECEIVE THREAD: got %s - command from client receivedBytes=%ld sequence=%d transitTime=%llu usecs\n", messageName, packetLength, sequence, transitTime); } } _voxelServerPacketProcessor->queueReceivedPacket(senderAddress, packetData, packetLength); } else { // let processNodeData handle it. NodeList::getInstance()->processNodeData(&senderAddress, packetData, packetLength); } } } // call NodeList::clear() so that all of our node specific objects, including our sending threads, are // properly shutdown and cleaned up. NodeList::getInstance()->clear(); if (_jurisdictionSender) { _jurisdictionSender->terminate(); delete _jurisdictionSender; } if (_voxelServerPacketProcessor) { _voxelServerPacketProcessor->terminate(); delete _voxelServerPacketProcessor; } if (_voxelPersistThread) { _voxelPersistThread->terminate(); delete _voxelPersistThread; } // tell our NodeList we're done with notifications nodeList->removeHook(&_nodeWatcher); delete _jurisdiction; _jurisdiction = NULL; qDebug() << "VoxelServer::run()... DONE\n"; }
// 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<EntityNodeData*>(node->getLinkedData()); if (nodeData) { quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); quint64 considerEntitiesSince = EntityTree::getAdjustedConsiderSince(deletedEntitiesSentAt); quint64 deletePacketSentAt = usecTimestampNow(); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); auto recentlyDeleted = tree->getRecentlyDeletedEntityIDs(); bool hasMoreToSend = true; packetsSent = 0; // create a new special packet std::unique_ptr<NLPacket> deletesPacket = NLPacket::create(PacketType::EntityErase); // pack in flags OCTREE_PACKET_FLAGS flags = 0; deletesPacket->writePrimitive(flags); // pack in sequence number auto sequenceNumber = queryNode->getSequenceNumber(); 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 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<NodeList>()->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<NodeList>()->sendPacket(std::move(deletesPacket), *node); #ifdef EXTRA_ERASE_DEBUGGING qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize; #endif nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt); } #ifdef EXTRA_ERASE_DEBUGGING if (packetsSent > 0) { qDebug() << "EntityServer::sendSpecialPackets() sent " << packetsSent << "special packets of " << totalBytes << " total bytes to node:" << node->getUUID(); } #endif // TODO: caller is expecting a packetLength, what if we send more than one packet?? return totalBytes; }
void Agent::readPendingDatagrams() { QByteArray receivedPacket; HifiSockAddr senderSockAddr; auto nodeList = DependencyManager::get<NodeList>(); while (readAvailableDatagram(receivedPacket, senderSockAddr)) { if (nodeList->packetVersionAndHashMatch(receivedPacket)) { PacketType datagramPacketType = packetTypeForPacket(receivedPacket); if (datagramPacketType == PacketTypeJurisdiction) { int headerBytes = numBytesForPacketHeader(receivedPacket); SharedNodePointer matchedNode = nodeList->sendingNodeForPacket(receivedPacket); if (matchedNode) { // PacketType_JURISDICTION, first byte is the node type... switch (receivedPacket[headerBytes]) { case NodeType::EntityServer: _scriptEngine.getEntityScriptingInterface()->getJurisdictionListener()-> queueReceivedPacket(matchedNode, receivedPacket); break; } } } else if (datagramPacketType == PacketTypeEntityAddResponse) { // this will keep creatorTokenIDs to IDs mapped correctly EntityItemID::handleAddEntityResponse(receivedPacket); // also give our local entity tree a chance to remap any internal locally created entities _entityViewer.getTree()->handleAddEntityResponse(receivedPacket); // Make sure our Node and NodeList knows we've heard from this node. SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket); sourceNode->setLastHeardMicrostamp(usecTimestampNow()); } else if (datagramPacketType == PacketTypeOctreeStats || datagramPacketType == PacketTypeEntityData || datagramPacketType == PacketTypeEntityErase ) { // Make sure our Node and NodeList knows we've heard from this node. SharedNodePointer sourceNode = nodeList->sendingNodeForPacket(receivedPacket); sourceNode->setLastHeardMicrostamp(usecTimestampNow()); QByteArray mutablePacket = receivedPacket; int messageLength = mutablePacket.size(); if (datagramPacketType == PacketTypeOctreeStats) { int statsMessageLength = OctreeHeadlessViewer::parseOctreeStats(mutablePacket, sourceNode); if (messageLength > statsMessageLength) { mutablePacket = mutablePacket.mid(statsMessageLength); // TODO: this needs to be fixed, the goal is to test the packet version for the piggyback, but // this is testing the version and hash of the original packet // need to use numBytesArithmeticCodingFromBuffer()... if (!DependencyManager::get<NodeList>()->packetVersionAndHashMatch(receivedPacket)) { return; // bail since piggyback data doesn't match our versioning } } else { return; // bail since no piggyback data } datagramPacketType = packetTypeForPacket(mutablePacket); } // fall through to piggyback message if (datagramPacketType == PacketTypeEntityData || datagramPacketType == PacketTypeEntityErase) { _entityViewer.processDatagram(mutablePacket, sourceNode); } } else if (datagramPacketType == PacketTypeMixedAudio || datagramPacketType == PacketTypeSilentAudioFrame) { _receivedAudioStream.parseData(receivedPacket); _lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness(); _receivedAudioStream.clearBuffer(); // let this continue through to the NodeList so it updates last heard timestamp // for the sending audio mixer DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket); } else if (datagramPacketType == PacketTypeBulkAvatarData || datagramPacketType == PacketTypeAvatarIdentity || datagramPacketType == PacketTypeAvatarBillboard || datagramPacketType == PacketTypeKillAvatar) { // let the avatar hash map process it _avatarHashMap.processAvatarMixerDatagram(receivedPacket, nodeList->sendingNodeForPacket(receivedPacket)); // let this continue through to the NodeList so it updates last heard timestamp // for the sending avatar-mixer DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket); } else { DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, receivedPacket); } } } }
/// Version of voxel distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrustumChanged) { OctreeServer::didPacketDistributor(this); // if shutting down, exit early if (nodeData->isShuttingDown()) { return 0; } int truePacketsSent = 0; int trueBytesSent = 0; int packetsSentThisInterval = 0; bool isFullScene = ((!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging()) || nodeData->hasLodChanged(); bool somethingToSend = true; // assume we have something // FOR NOW... node tells us if it wants to receive only view frustum deltas bool wantDelta = viewFrustumChanged && nodeData->getWantDelta(); // 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. bool wantColor = nodeData->getWantColor(); bool wantCompression = nodeData->getWantCompression(); // 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->getCurrentPacketFormatMatches()) { if (nodeData->isPacketWaiting()) { packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); } else { nodeData->resetOctreePacket(); } int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; if (wantCompression) { targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); } _packetData.changeSettings(wantCompression, targetSize); } const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL; // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) { // if our view has changed, we need to reset these things... if (viewFrustumChanged) { if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) { nodeData->dumpOutOfView(); } nodeData->map.erase(); } if (!viewFrustumChanged && !nodeData->getWantDelta()) { // only set our last sent time if we weren't resetting due to frustum change quint64 now = usecTimestampNow(); nodeData->setLastTimeBagEmpty(now); } // track completed scenes and send out the stats packet accordingly nodeData->stats.sceneCompleted(); nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged()); // TODO: add these to stats page //::endSceneSleepTime = _usleepTime; //unsigned long sleepTime = ::endSceneSleepTime - ::startSceneSleepTime; //unsigned long encodeTime = nodeData->stats.getTotalEncodeTime(); //unsigned long elapsedTime = nodeData->stats.getElapsedTime(); int packetsJustSent = handlePacketSend(nodeData, trueBytesSent, truePacketsSent); packetsSentThisInterval += packetsJustSent; // If we're starting a full scene, then definitely we want to empty the nodeBag if (isFullScene) { nodeData->nodeBag.deleteAll(); } // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; // start tracking our stats nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot(), _myServer->getJurisdiction()); // This is the start of "resending" the scene. bool dontRestartSceneOnMove = false; // this is experimental if (dontRestartSceneOnMove) { if (nodeData->nodeBag.isEmpty()) { nodeData->nodeBag.insert(_myServer->getOctree()->getRoot()); // only in case of empty } } else { nodeData->nodeBag.insert(_myServer->getOctree()->getRoot()); // original behavior, reset on move or empty } } // If we have something in our nodeBag, then turn them into packets and send them out... if (!nodeData->nodeBag.isEmpty()) { int bytesWritten = 0; quint64 start = usecTimestampNow(); // TODO: add these to stats page //quint64 startCompressTimeMsecs = OctreePacketData::getCompressContentTime() / 1000; //quint64 startCompressCalls = OctreePacketData::getCompressContentCalls(); int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxOctreePacketsPerSecond() / INTERVALS_PER_SECOND)); int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); 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->nodeBag.isEmpty()) { OctreeElement* subTree = nodeData->nodeBag.extract(); /* TODO: Looking for a way to prevent locking and encoding a tree that is not // going to result in any packets being sent... // // If our node is root, and the root hasn't changed, and our view hasn't changed, // and we've already seen at least one duplicate packet, then we probably don't need // to lock the tree and encode, because the result should be that no bytes will be // encoded, and this will be a duplicate packet from the last one we sent... OctreeElement* root = _myServer->getOctree()->getRoot(); bool skipEncode = false; if ( (subTree == root) && (nodeData->getLastRootTimestamp() == root->getLastChanged()) && !viewFrustumChanged && (nodeData->getDuplicatePacketCount() > 0) ) { qDebug() << "is root, root not changed, view not changed, already seen a duplicate!" << "Can we skip it?"; skipEncode = true; } */ bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; float voxelSizeScale = nodeData->getOctreeSizeScale(); int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving() ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor, WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, wantOcclusionCulling, coverageMap, boundaryLevelAdjust, voxelSizeScale, nodeData->getLastTimeBagEmpty(), isFullScene, &nodeData->stats, _myServer->getJurisdiction()); // 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(); quint64 lockWaitStart = usecTimestampNow(); _myServer->getOctree()->lockForRead(); quint64 lockWaitEnd = usecTimestampNow(); lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); quint64 encodeStart = usecTimestampNow(); bytesWritten = _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, 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->nodeBag.isEmpty(); // if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case. if (_packetData.getTargetSize() == MAX_OCTREE_PACKET_DATA_SIZE) { if (_packetData.hasContent() && bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { lastNodeDidntFit = true; } } else { // in compressed mode and we are trying to pack more... and we don't care if the _packetData has // content or not... because in this case even if we were unable to pack any data, we want to drop // below to our sendNow logic, but we do want to track that we attempted to pack extra extraPackingAttempts++; if (bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { lastNodeDidntFit = true; } } nodeData->stats.encodeStopped(); _myServer->getOctree()->unlock(); } 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() + (nodeData->getCurrentPacketIsCompressed() ? sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) : 0); if (writtenSize > nodeData->getAvailable()) { packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); } nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); extraPackingAttempts = 0; 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 (nodeData->getCurrentPacketIsCompressed() && 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(nodeData, trueBytesSent, truePacketsSent); quint64 packetSendingEnd = usecTimestampNow(); packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); if (wantCompression) { targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); } } 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(nodeData->getWantCompression(), targetSize); // will do reset } OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec); OctreeServer::trackEncodeTime(encodeElapsedUsec); OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec); OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec); quint64 endInside = usecTimestampNow(); quint64 elapsedInsideUsecs = endInside - startInside; OctreeServer::trackInsideTime((float)elapsedInsideUsecs); } // 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->hasSpecialPacketToSend(_node) && !nodeData->isShuttingDown()) { trueBytesSent += _myServer->sendSpecialPacket(nodeData, _node); nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed truePacketsSent++; packetsSentThisInterval++; } quint64 end = usecTimestampNow(); 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 voxels from the current view frustum if (nodeData->nodeBag.isEmpty()) { nodeData->updateLastKnownViewFrustum(); nodeData->setViewSent(true); nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes } } // end if bag wasn't empty, and so we sent stuff... return truePacketsSent; }
void EntityTests::entityTreeTests(bool verbose) { bool extraVerbose = false; int testsTaken = 0; int testsPassed = 0; int testsFailed = 0; if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << "EntityTests::entityTreeTests()"; // Tree, id, and entity properties used in many tests below... EntityTree tree; QUuid id = QUuid::createUuid(); EntityItemID entityID(id); entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs EntityItemProperties properties; float oneMeter = 1.0f; //float halfMeter = oneMeter / 2.0f; float halfOfDomain = TREE_SCALE * 0.5f; glm::vec3 positionNearOriginInMeters(oneMeter, oneMeter, oneMeter); // when using properties, these are in meter not tree units glm::vec3 positionAtCenterInMeters(halfOfDomain, halfOfDomain, halfOfDomain); glm::vec3 positionNearOriginInTreeUnits = positionNearOriginInMeters / (float)TREE_SCALE; glm::vec3 positionAtCenterInTreeUnits = positionAtCenterInMeters / (float)TREE_SCALE; { testsTaken++; QString testName = "add entity to tree and search"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } properties.setPosition(positionAtCenterInMeters); // TODO: Fix these unit tests. //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); tree.addEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } entityID.isKnownID = true; // this is a temporary workaround to allow local tree entities to be added with known IDs { testsTaken++; QString testName = "change position of entity in tree"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionNearOriginInMeters; properties.setPosition(newPosition); tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOriginInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; QString testName = "change position of entity in tree back to center"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } glm::vec3 newPosition = positionAtCenterInMeters; properties.setPosition(newPosition); tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; //containingElement->printDebugDetails("containingElement"); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - findClosestEntity() "+ QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units quint64 start = usecTimestampNow(); const EntityItem* foundEntityByRadius = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { foundEntityByRadius = tree.findClosestEntity(positionAtCenterInTreeUnits, targetRadius); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; } bool passed = foundEntityByRadius; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - findEntityByID() "+ QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } quint64 start = usecTimestampNow(); const EntityItem* foundEntityByID = NULL; for (int i = 0; i < TEST_ITERATIONS; i++) { // TODO: does this need to be updated?? foundEntityByID = tree.findEntityByEntityItemID(entityID); } quint64 end = usecTimestampNow(); if (verbose) { qDebug() << "foundEntityByID=" << foundEntityByID; } bool passed = foundEntityByID; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecs = (float)(end - start) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed=" << elapsedInMSecs << "msecs"; } { // seed the random number generator so that our tests are reproducible srand(0xFEEDBEEF); testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - add entity to tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedAdd = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entityID.isKnownID = false; // this is a temporary workaround to allow local tree entities to be added with known IDs float randomX = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomY = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); float randomZ = randFloatInRange(1.0f ,(float)TREE_SCALE - 1.0f); glm::vec3 randomPositionInMeters(randomX,randomY,randomZ); glm::vec3 randomPositionInTreeUnits = randomPositionInMeters / (float)TREE_SCALE; properties.setPosition(randomPositionInMeters); // TODO: fix these unit tests //properties.setRadius(halfMeter); //properties.setModelURL("http://s3.amazonaws.com/hifi-public/ozan/theater.fbx"); if (extraVerbose) { qDebug() << "iteration:" << i << "ading entity at x/y/z=" << randomX << "," << randomY << "," << randomZ; qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startAdd = usecTimestampNow(); tree.addEntity(entityID, properties); quint64 endAdd = usecTimestampNow(); totalElapsedAdd += (endAdd - startAdd); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); float targetRadius = oneMeter * 2.0 / (float)TREE_SCALE; // in tree units const EntityItem* foundEntityByRadius = tree.findClosestEntity(randomPositionInTreeUnits, targetRadius); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); AACube elementCube = containingElement ? containingElement->getAACube() : AACube(); bool elementIsBestFit = containingElement->bestFitEntityBounds(foundEntityByID); if (extraVerbose) { qDebug() << "foundEntityByRadius=" << foundEntityByRadius; qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; qDebug() << "containingElement.box=" << elementCube.getCorner().x * TREE_SCALE << "," << elementCube.getCorner().y * TREE_SCALE << "," << elementCube.getCorner().z * TREE_SCALE << ":" << elementCube.getScale() * TREE_SCALE; qDebug() << "elementCube.getScale()=" << elementCube.getScale(); //containingElement->printDebugDetails("containingElement"); qDebug() << "elementIsBestFit=" << elementIsBestFit; } // Every 1000th test, show the size of the tree... if (extraVerbose && (i % 1000 == 0)) { qDebug() << "after test:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = foundEntityByRadius && foundEntityByID && (foundEntityByRadius == foundEntityByID) && elementIsBestFit; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByRadius=" << foundEntityByRadius << "foundEntityByID=" << foundEntityByID << "x/y/z=" << randomX << "," << randomY << "," << randomZ << "elementIsBestFit=" << elementIsBestFit; } } } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == TEST_ITERATIONS; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsAdd = (float)(totalElapsedAdd) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Add=" << elapsedInMSecsAdd << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 1000; QString testName = "Performance - delete entity from tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedDelete = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entityID.isKnownID = true; // this is a temporary workaround to allow local tree entities to be added with known IDs if (extraVerbose) { qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startDelete = usecTimestampNow(); tree.deleteEntity(entityID); quint64 endDelete = usecTimestampNow(); totalElapsedDelete += (endDelete - startDelete); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); EntityTreeElement* containingElement = tree.getContainingElement(entityID); if (extraVerbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; } // Every 1000th test, show the size of the tree... if (extraVerbose && (i % 1000 == 0)) { qDebug() << "after test:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = foundEntityByID == NULL && containingElement == NULL; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByID=" << foundEntityByID << "containingElement=" << containingElement; } } } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == TEST_ITERATIONS; if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsDelete = (float)(totalElapsedDelete) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Delete=" << elapsedInMSecsDelete << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } { testsTaken++; const int TEST_ITERATIONS = 100; const int ENTITIES_PER_ITERATION = 10; QString testName = "Performance - delete " + QString::number(ENTITIES_PER_ITERATION) + " entities from tree " + QString::number(TEST_ITERATIONS) + " times"; if (verbose) { qDebug() << "Test" << testsTaken <<":" << qPrintable(testName); } int iterationsPassed = 0; quint64 totalElapsedDelete = 0; quint64 totalElapsedFind = 0; for (int i = 0; i < TEST_ITERATIONS; i++) { QSet<EntityItemID> entitiesToDelete; for (int j = 0; j < ENTITIES_PER_ITERATION; j++) { //uint32_t id = 2 + (i * ENTITIES_PER_ITERATION) + j; // These are the entities we added above QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); entitiesToDelete << entityID; } if (extraVerbose) { qDebug() << "before:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startDelete = usecTimestampNow(); tree.deleteEntities(entitiesToDelete); quint64 endDelete = usecTimestampNow(); totalElapsedDelete += (endDelete - startDelete); if (extraVerbose) { qDebug() << "after:" << i << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } quint64 startFind = usecTimestampNow(); for (int j = 0; j < ENTITIES_PER_ITERATION; j++) { //uint32_t id = 2 + (i * ENTITIES_PER_ITERATION) + j; // These are the entities we added above QUuid id = QUuid::createUuid();// make sure it doesn't collide with previous entity ids EntityItemID entityID(id); const EntityItem* foundEntityByID = tree.findEntityByEntityItemID(entityID); EntityTreeElement* containingElement = tree.getContainingElement(entityID); if (extraVerbose) { qDebug() << "foundEntityByID=" << foundEntityByID; qDebug() << "containingElement=" << containingElement; } bool passed = foundEntityByID == NULL && containingElement == NULL; if (passed) { iterationsPassed++; } else { if (extraVerbose) { qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName) << "iteration:" << i << "foundEntityByID=" << foundEntityByID << "containingElement=" << containingElement; } } } quint64 endFind = usecTimestampNow(); totalElapsedFind += (endFind - startFind); } if (extraVerbose) { qDebug() << "getOctreeElementsCount()=" << tree.getOctreeElementsCount(); } bool passed = iterationsPassed == (TEST_ITERATIONS * ENTITIES_PER_ITERATION); if (passed) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test" << testsTaken <<":" << qPrintable(testName); } float USECS_PER_MSECS = 1000.0f; float elapsedInMSecsDelete = (float)(totalElapsedDelete) / USECS_PER_MSECS; float elapsedInMSecsFind = (float)(totalElapsedFind) / USECS_PER_MSECS; qDebug() << "TIME - Test" << testsTaken <<":" << qPrintable(testName) << "elapsed Delete=" << elapsedInMSecsDelete << "msecs" << "elapsed Find=" << elapsedInMSecsFind << "msecs"; } qDebug() << " tests passed:" << testsPassed << "out of" << testsTaken; if (verbose) { qDebug() << "******************************************************************************************"; } }
void OctreeElement::markWithChangedTime() { _lastChanged = usecTimestampNow(); notifyUpdateHooks(); // if the node has changed, notify our hooks }
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { bool wantDebug = false; if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) { // NOTE: This shouldn't happen. The only versions of the bit stream that didn't support split mtu buffers should // be handled by the model subclass and shouldn't call this routine. qDebug() << "EntityItem::readEntityDataFromBuffer()... " "ERROR CASE...args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU"; return 0; } // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; int bytesRead = 0; if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) { int originalLength = bytesLeftToRead; QByteArray originalDataBuffer((const char*)data, originalLength); int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; const unsigned char* dataAt = data; // id QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size _id = QUuid::fromRfc4122(encodedID); _creatorTokenID = UNKNOWN_ENTITY_TOKEN; // if we know the id, then we don't care about the creator token _newlyCreated = false; dataAt += encodedID.size(); bytesRead += encodedID.size(); // type QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint32> typeCoder = encodedType; encodedType = typeCoder; // determine true length dataAt += encodedType.size(); bytesRead += encodedType.size(); quint32 type = typeCoder; _type = (EntityTypes::EntityType)type; bool overwriteLocalData = true; // assume the new content overwrites our local data // _created quint64 createdFromBuffer = 0; memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); dataAt += sizeof(createdFromBuffer); bytesRead += sizeof(createdFromBuffer); createdFromBuffer -= clockSkew; _created = createdFromBuffer; // TODO: do we ever want to discard this??? if (wantDebug) { quint64 lastEdited = getLastEdited(); float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); QString ageAsString = formatSecondsElapsed(getAge()); qDebug() << "Loading entity " << getEntityItemID() << " from buffer..."; qDebug() << " _created =" << _created; qDebug() << " age=" << getAge() << "seconds - " << ageAsString; qDebug() << " lastEdited =" << lastEdited; qDebug() << " ago=" << editedAgo << "seconds - " << agoAsString; } quint64 now = usecTimestampNow(); quint64 lastEditedFromBuffer = 0; quint64 lastEditedFromBufferAdjusted = 0; // TODO: we could make this encoded as a delta from _created // _lastEdited memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); dataAt += sizeof(lastEditedFromBuffer); bytesRead += sizeof(lastEditedFromBuffer); lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); if (wantDebug) { qDebug() << "data from server **************** "; qDebug() << " entityItemID=" << getEntityItemID(); qDebug() << " now=" << now; qDebug() << " getLastEdited()=" << getLastEdited(); qDebug() << " lastEditedFromBuffer=" << lastEditedFromBuffer << " (BEFORE clockskew adjust)"; qDebug() << " clockSkew=" << clockSkew; qDebug() << " lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted << " (AFTER clockskew adjust)"; qDebug() << " _lastEditedFromRemote=" << _lastEditedFromRemote << " (our local time the last server edit we accepted)"; qDebug() << " _lastEditedFromRemoteInRemoteTime=" << _lastEditedFromRemoteInRemoteTime << " (remote time the last server edit we accepted)"; qDebug() << " fromSameServerEdit=" << fromSameServerEdit; } bool ignoreServerPacket = false; // assume we'll use this server packet // If this packet is from the same server edit as the last packet we accepted from the server // we probably want to use it. if (fromSameServerEdit) { // If this is from the same sever packet, then check against any local changes since we got // the most recent packet from this server time if (_lastEdited > _lastEditedFromRemote) { ignoreServerPacket = true; } } else { // If this isn't from the same sever packet, then honor our skew adjusted times... // If we've changed our local tree more recently than the new data from this packet // then we will not be changing our values, instead we just read and skip the data if (_lastEdited > lastEditedFromBufferAdjusted) { ignoreServerPacket = true; } } if (ignoreServerPacket) { overwriteLocalData = false; if (wantDebug) { qDebug() << "IGNORING old data from server!!! ****************"; } } else { if (wantDebug) { qDebug() << "USING NEW data from server!!! ****************"; } _lastEdited = lastEditedFromBufferAdjusted; _lastEditedFromRemote = now; _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; somethingChangedNotification(); // notify derived classes that something has changed } // last updated is stored as ByteCountCoded delta from lastEdited QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta; quint64 updateDelta = updateDeltaCoder; if (overwriteLocalData) { _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that for _lastEdited if (wantDebug) { qDebug() << "_lastUpdated=" << _lastUpdated; qDebug() << "_lastEdited=" << _lastEdited; qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted; } } encodedUpdateDelta = updateDeltaCoder; // determine true length dataAt += encodedUpdateDelta.size(); bytesRead += encodedUpdateDelta.size(); // Property Flags QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); bytesRead += propertyFlags.getEncodedLength(); READ_ENTITY_PROPERTY_SETTER(PROP_POSITION, glm::vec3, updatePosition); // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { if (propertyFlags.getHasProperty(PROP_RADIUS)) { float fromBuffer; memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); dataAt += sizeof(fromBuffer); bytesRead += sizeof(fromBuffer); if (overwriteLocalData) { setRadius(fromBuffer); } if (wantDebug) { qDebug() << " readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS"; } } } else { READ_ENTITY_PROPERTY_SETTER(PROP_DIMENSIONS, glm::vec3, setDimensions); if (wantDebug) { qDebug() << " readEntityDataFromBuffer() NEW FORMAT... look for PROP_DIMENSIONS"; } } if (wantDebug) { qDebug() << " readEntityDataFromBuffer() _dimensions:" << getDimensionsInMeters() << " in meters"; } READ_ENTITY_PROPERTY_QUAT_SETTER(PROP_ROTATION, updateRotation); READ_ENTITY_PROPERTY_SETTER(PROP_MASS, float, updateMass); READ_ENTITY_PROPERTY_SETTER(PROP_VELOCITY, glm::vec3, updateVelocity); READ_ENTITY_PROPERTY_SETTER(PROP_GRAVITY, glm::vec3, updateGravity); READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping); READ_ENTITY_PROPERTY_SETTER(PROP_LIFETIME, float, updateLifetime); READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint); READ_ENTITY_PROPERTY_SETTER(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible); READ_ENTITY_PROPERTY_SETTER(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); READ_ENTITY_PROPERTY_SETTER(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); READ_ENTITY_PROPERTY(PROP_LOCKED, bool, _locked); READ_ENTITY_PROPERTY_STRING(PROP_USER_DATA,setUserData); if (wantDebug) { qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint; qDebug() << " readEntityDataFromBuffer() _visible:" << _visible; qDebug() << " readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions; qDebug() << " readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove; } bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); recalculateCollisionShape(); } return bytesRead; }
void AvatarMixer::run() { // change the logging target name while AvatarMixer is running Logging::setTargetName(AVATAR_MIXER_LOGGING_NAME); NodeList* nodeList = NodeList::getInstance(); nodeList->setOwnerType(NODE_TYPE_AVATAR_MIXER); nodeList->linkedDataCreateCallback = attachAvatarDataToNode; nodeList->startSilentNodeRemovalThread(); sockaddr* nodeAddress = new sockaddr; ssize_t receivedBytes = 0; unsigned char* packetData = new unsigned char[MAX_PACKET_SIZE]; uint16_t nodeID = 0; Node* avatarNode = NULL; timeval lastDomainServerCheckIn = {}; // we only need to hear back about avatar nodes from the DS nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1); while (true) { if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { break; } // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); NodeList::getInstance()->sendDomainServerCheckIn(); } if (nodeList->getNodeSocket()->receive(nodeAddress, packetData, &receivedBytes) && packetVersionMatch(packetData)) { switch (packetData[0]) { case PACKET_TYPE_HEAD_DATA: // grab the node ID from the packet unpackNodeId(packetData + numBytesForPacketHeader(packetData), &nodeID); // add or update the node in our list avatarNode = nodeList->addOrUpdateNode(nodeAddress, nodeAddress, NODE_TYPE_AGENT, nodeID); // parse positional data from an node nodeList->updateNodeWithData(avatarNode, packetData, receivedBytes); case PACKET_TYPE_INJECT_AUDIO: broadcastAvatarData(nodeList, nodeAddress); break; case PACKET_TYPE_AVATAR_VOXEL_URL: case PACKET_TYPE_AVATAR_FACE_VIDEO: // grab the node ID from the packet unpackNodeId(packetData + numBytesForPacketHeader(packetData), &nodeID); // let everyone else know about the update for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getActiveSocket() && node->getNodeID() != nodeID) { nodeList->getNodeSocket()->send(node->getActiveSocket(), packetData, receivedBytes); } } break; default: // hand this off to the NodeList nodeList->processNodeData(nodeAddress, packetData, receivedBytes); break; } } } nodeList->stopSilentNodeRemovalThread(); }
void OctreeSceneStats::encodeStopped() { _totalEncodeTime += (usecTimestampNow() - _encodeStart); }
bool DdeFaceTracker::isActive() const { static const int ACTIVE_TIMEOUT_USECS = 3000000; //3 secs return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS); }
void OctreeInboundPacketProcessor::processPacket(QSharedPointer<NLPacket> packet, SharedNodePointer sendingNode) { if (_shuttingDown) { qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet"; return; } bool debugProcessPacket = _myServer->wantsVerboseDebug(); if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() payload=%p payloadLength=%lld", packet->getPayload(), packet->getPayloadSize()); } // Ask our tree subclass if it can handle the incoming packet... PacketType packetType = packet->getType(); if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); _receivedPacketCount++; unsigned short int sequence; packet->readPrimitive(&sequence); quint64 sentAt; packet->readPrimitive(&sentAt); quint64 arrivedAt = usecTimestampNow(); if (sentAt > arrivedAt) { if (debugProcessPacket || _myServer->wantsDebugReceiving()) { qDebug() << "unreasonable sentAt=" << sentAt << " usecs"; qDebug() << "setting sentAt to arrivedAt=" << arrivedAt << " usecs"; } sentAt = arrivedAt; } quint64 transitTime = arrivedAt - sentAt; int editsInPacket = 0; quint64 processTime = 0; quint64 lockWaitTime = 0; if (debugProcessPacket || _myServer->wantsDebugReceiving()) { qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount << " command from client"; qDebug() << " receivedBytes=" << packet->getDataSize(); qDebug() << " sequence=" << sequence; qDebug() << " sentAt=" << sentAt << " usecs"; qDebug() << " arrivedAt=" << arrivedAt << " usecs"; qDebug() << " transitTime=" << transitTime << " usecs"; qDebug() << " sendingNode->getClockSkewUsec()=" << sendingNode->getClockSkewUsec() << " usecs"; } if (debugProcessPacket) { qDebug() << " numBytesPacketHeader=" << NLPacket::totalHeaderSize(packetType); qDebug() << " sizeof(sequence)=" << sizeof(sequence); qDebug() << " sizeof(sentAt)=" << sizeof(sentAt); qDebug() << " atByte (in payload)=" << packet->pos(); qDebug() << " payload size=" << packet->getPayloadSize(); if (!packet->bytesLeftToRead()) { qDebug() << " ----- UNEXPECTED ---- got a packet without any edit details!!!! --------"; } } const unsigned char* editData = nullptr; while (packet->bytesLeftToRead() > 0) { editData = reinterpret_cast<const unsigned char*>(packet->getPayload() + packet->pos()); int maxSize = packet->bytesLeftToRead(); if (debugProcessPacket) { qDebug() << " --- inside while loop ---"; qDebug() << " maxSize=" << maxSize; qDebug("OctreeInboundPacketProcessor::processPacket() %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d", packetType, packet->getPayload(), packet->getPayloadSize(), editData, packet->pos(), maxSize); } quint64 startProcess, startLock = usecTimestampNow(); int editDataBytesRead; _myServer->getOctree()->withWriteLock([&] { startProcess = usecTimestampNow(); editDataBytesRead = _myServer->getOctree()->processEditPacketData(*packet, editData, maxSize, sendingNode); }); quint64 endProcess = usecTimestampNow(); if (debugProcessPacket) { qDebug() << "OctreeInboundPacketProcessor::processPacket() after processEditPacketData()..." << "editDataBytesRead=" << editDataBytesRead; } editsInPacket++; quint64 thisProcessTime = endProcess - startProcess; quint64 thisLockWaitTime = startProcess - startLock; processTime += thisProcessTime; lockWaitTime += thisLockWaitTime; // skip to next edit record in the packet packet->seek(packet->pos() + editDataBytesRead); if (debugProcessPacket) { qDebug() << " editDataBytesRead=" << editDataBytesRead; qDebug() << " AFTER processEditPacketData payload position=" << packet->pos(); qDebug() << " AFTER processEditPacketData payload size=" << packet->getPayloadSize(); } } if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld", packetType, packet->getPayload(), packet->getPayloadSize(), editData, packet->pos()); } // Make sure our Node and NodeList knows we've heard from this node. QUuid& nodeUUID = DEFAULT_NODE_ID_REF; if (sendingNode) { nodeUUID = sendingNode->getUUID(); if (debugProcessPacket) { qDebug() << "sender has uuid=" << nodeUUID; } } else { if (debugProcessPacket) { qDebug() << "sender has no known nodeUUID."; } } trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime); } else { qDebug("unknown packet ignored... packetType=%hhu", packetType); } }
void DdeFaceTracker::decodePacket(const QByteArray& buffer) { if(buffer.size() > MIN_PACKET_SIZE) { Packet packet; int bytesToCopy = glm::min((int)sizeof(packet), buffer.size()); memset(&packet.name, '\n', MAX_NAME_SIZE + 1); memcpy(&packet, buffer.data(), bytesToCopy); glm::vec3 translation; memcpy(&translation, packet.translation, sizeof(packet.translation)); glm::quat rotation; memcpy(&rotation, &packet.rotation, sizeof(packet.rotation)); if (_reset) { memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3)); memcpy(&_referenceRotation, &rotation, sizeof(glm::quat)); _reset = false; } // Compute relative translation float LEAN_DAMPING_FACTOR = 40; translation -= _referenceTranslation; translation /= LEAN_DAMPING_FACTOR; translation.x *= -1; // Compute relative rotation rotation = glm::inverse(_referenceRotation) * rotation; // copy values _headTranslation = translation; _headRotation = rotation; // Set blendshapes float BLINK_MAGNIFIER = 2.0f; _blendshapeCoefficients[_leftBlinkIndex] = rescaleCoef(packet.expressions[1]) * BLINK_MAGNIFIER; _blendshapeCoefficients[_rightBlinkIndex] = rescaleCoef(packet.expressions[0]) * BLINK_MAGNIFIER; float leftBrow = 1.0f - rescaleCoef(packet.expressions[14]); if (leftBrow < 0.5f) { _blendshapeCoefficients[_browDownLeftIndex] = 1.0f - 2.0f * leftBrow; _blendshapeCoefficients[_browUpLeftIndex] = 0.0f; } else { _blendshapeCoefficients[_browDownLeftIndex] = 0.0f; _blendshapeCoefficients[_browUpLeftIndex] = 2.0f * (leftBrow - 0.5f); } float rightBrow = 1.0f - rescaleCoef(packet.expressions[15]); if (rightBrow < 0.5f) { _blendshapeCoefficients[_browDownRightIndex] = 1.0f - 2.0f * rightBrow; _blendshapeCoefficients[_browUpRightIndex] = 0.0f; } else { _blendshapeCoefficients[_browDownRightIndex] = 0.0f; _blendshapeCoefficients[_browUpRightIndex] = 2.0f * (rightBrow - 0.5f); } float JAW_OPEN_MAGNIFIER = 1.4f; _blendshapeCoefficients[_jawOpenIndex] = rescaleCoef(packet.expressions[21]) * JAW_OPEN_MAGNIFIER; _blendshapeCoefficients[_mouthSmileLeftIndex] = rescaleCoef(packet.expressions[24]); _blendshapeCoefficients[_mouthSmileRightIndex] = rescaleCoef(packet.expressions[23]); } else { qDebug() << "[Error] DDE Face Tracker Decode Error"; } _lastReceiveTimestamp = usecTimestampNow(); }
void AudioInjector::injectAudio(UDPSocket* injectorSocket, sockaddr* destinationSocket) { if (_audioSampleArray) { _isInjectingAudio = true; timeval startTime; // calculate the number of bytes required for additional data int leadingBytes = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_INJECT_AUDIO) + sizeof(_streamIdentifier) + sizeof(_position) + sizeof(_orientation) + sizeof(_radius) + sizeof(_volume); unsigned char dataPacket[(BUFFER_LENGTH_SAMPLES_PER_CHANNEL * sizeof(int16_t)) + leadingBytes]; unsigned char* currentPacketPtr = dataPacket + populateTypeAndVersion(dataPacket, PACKET_TYPE_INJECT_AUDIO); // copy the identifier for this injector memcpy(currentPacketPtr, &_streamIdentifier, sizeof(_streamIdentifier)); currentPacketPtr += sizeof(_streamIdentifier); memcpy(currentPacketPtr, &_position, sizeof(_position)); currentPacketPtr += sizeof(_position); memcpy(currentPacketPtr, &_orientation, sizeof(_orientation)); currentPacketPtr += sizeof(_orientation); memcpy(currentPacketPtr, &_radius, sizeof(_radius)); currentPacketPtr += sizeof(_radius); *currentPacketPtr = _volume; currentPacketPtr++; gettimeofday(&startTime, NULL); int nextFrame = 0; for (int i = 0; i < _numTotalSamples; i += BUFFER_LENGTH_SAMPLES_PER_CHANNEL) { int numSamplesToCopy = BUFFER_LENGTH_SAMPLES_PER_CHANNEL; if (_numTotalSamples - i < BUFFER_LENGTH_SAMPLES_PER_CHANNEL) { numSamplesToCopy = _numTotalSamples - i; memset(currentPacketPtr + numSamplesToCopy, 0, BUFFER_LENGTH_BYTES_PER_CHANNEL - (numSamplesToCopy * sizeof(int16_t))); } memcpy(currentPacketPtr, _audioSampleArray + i, numSamplesToCopy * sizeof(int16_t)); injectorSocket->send(destinationSocket, dataPacket, sizeof(dataPacket)); // calculate the intensity for this frame float lastRMS = 0; for (int j = 0; j < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; j++) { lastRMS += _audioSampleArray[i + j] * _audioSampleArray[i + j]; } lastRMS /= BUFFER_LENGTH_SAMPLES_PER_CHANNEL; lastRMS = sqrtf(lastRMS); _lastFrameIntensity = lastRMS / std::numeric_limits<int16_t>::max(); int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * INJECT_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); } } _isInjectingAudio = false; } }
void DatagramProcessor::processDatagrams() { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "DatagramProcessor::processDatagrams()"); HifiSockAddr senderSockAddr; static QByteArray incomingPacket; Application* application = Application::getInstance(); NodeList* nodeList = NodeList::getInstance(); while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams()) { incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(), senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); _packetCount++; _byteCount += incomingPacket.size(); if (nodeList->packetVersionAndHashMatch(incomingPacket)) { // only process this packet if we have a match on the packet version switch (packetTypeForPacket(incomingPacket)) { case PacketTypeMixedAudio: case PacketTypeSilentAudioFrame: QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToStream", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); break; case PacketTypeAudioStreamStats: QMetaObject::invokeMethod(&application->_audio, "parseAudioStreamStatsPacket", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); break; case PacketTypeParticleAddResponse: // this will keep creatorTokenIDs to IDs mapped correctly Particle::handleAddParticleResponse(incomingPacket); application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket); break; case PacketTypeModelAddResponse: // this will keep creatorTokenIDs to IDs mapped correctly ModelItem::handleAddModelResponse(incomingPacket); application->getModels()->getTree()->handleAddModelResponse(incomingPacket); break; case PacketTypeParticleData: case PacketTypeParticleErase: case PacketTypeModelData: case PacketTypeModelErase: case PacketTypeVoxelData: case PacketTypeVoxelErase: case PacketTypeOctreeStats: case PacketTypeEnvironmentData: { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::networkReceive()... _octreeProcessor.queueReceivedPacket()"); bool wantExtraDebugging = application->getLogger()->extraDebugging(); if (wantExtraDebugging && packetTypeForPacket(incomingPacket) == PacketTypeVoxelData) { int numBytesPacketHeader = numBytesForPacketHeader(incomingPacket); unsigned char* dataAt = reinterpret_cast<unsigned char*>(incomingPacket.data()) + numBytesPacketHeader; dataAt += sizeof(OCTREE_PACKET_FLAGS); OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); dataAt += sizeof(OCTREE_PACKET_SEQUENCE); OCTREE_PACKET_SENT_TIME sentAt = (*(OCTREE_PACKET_SENT_TIME*)dataAt); dataAt += sizeof(OCTREE_PACKET_SENT_TIME); OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); int flightTime = arrivedAt - sentAt; qDebug("got PacketType_VOXEL_DATA, sequence:%d flightTime:%d", sequence, flightTime); } SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(incomingPacket); if (matchedNode) { // add this packet to our list of voxel packets and process them on the voxel processing application->_octreeProcessor.queueReceivedPacket(matchedNode, incomingPacket); } break; } case PacketTypeMetavoxelData: nodeList->findNodeAndUpdateWithDataFromPacket(incomingPacket); break; case PacketTypeBulkAvatarData: case PacketTypeKillAvatar: case PacketTypeAvatarIdentity: case PacketTypeAvatarBillboard: { // update having heard from the avatar-mixer and record the bytes received SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket); if (avatarMixer) { avatarMixer->setLastHeardMicrostamp(usecTimestampNow()); avatarMixer->recordBytesReceived(incomingPacket.size()); QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram", Q_ARG(const QByteArray&, incomingPacket), Q_ARG(const QWeakPointer<Node>&, avatarMixer)); } application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size()); break; } case PacketTypeDomainOAuthRequest: { QDataStream readStream(incomingPacket); readStream.skipRawData(numBytesForPacketHeader(incomingPacket)); QUrl authorizationURL; readStream >> authorizationURL; QMetaObject::invokeMethod(&OAuthWebViewHandler::getInstance(), "displayWebviewForAuthorizationURL", Q_ARG(const QUrl&, authorizationURL)); break; } case PacketTypeMuteEnvironment: { glm::vec3 position; float radius; int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); memcpy(&position, incomingPacket.constData() + headerSize, sizeof(glm::vec3)); memcpy(&radius, incomingPacket.constData() + headerSize + sizeof(glm::vec3), sizeof(float)); if (glm::distance(Application::getInstance()->getAvatar()->getPosition(), position) < radius && !Application::getInstance()->getAudio()->getMuted()) { Application::getInstance()->getAudio()->toggleMute(); } break; } case PacketTypeVoxelEditNack: if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { application->_voxelEditSender.processNackPacket(incomingPacket); } break; case PacketTypeParticleEditNack: if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { application->_particleEditSender.processNackPacket(incomingPacket); } break; case PacketTypeModelEditNack: if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { application->_modelEditSender.processNackPacket(incomingPacket); } break; default: nodeList->processNodeData(senderSockAddr, incomingPacket); break; } } } }
void AvatarMixer::run() { // change the logging target name while AvatarMixer is running Logging::setTargetName(AVATAR_MIXER_LOGGING_NAME); NodeList* nodeList = NodeList::getInstance(); nodeList->setOwnerType(NODE_TYPE_AVATAR_MIXER); nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1); nodeList->linkedDataCreateCallback = attachAvatarDataToNode; nodeList->startSilentNodeRemovalThread(); sockaddr nodeAddress = {}; ssize_t receivedBytes = 0; unsigned char packetData[MAX_PACKET_SIZE]; QUuid nodeUUID; Node* avatarNode = NULL; timeval lastDomainServerCheckIn = {}; while (true) { if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { break; } // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); NodeList::getInstance()->sendDomainServerCheckIn(); } nodeList->possiblyPingInactiveNodes(); if (nodeList->getNodeSocket()->receive(&nodeAddress, packetData, &receivedBytes) && packetVersionMatch(packetData)) { switch (packetData[0]) { case PACKET_TYPE_HEAD_DATA: nodeUUID = QUuid::fromRfc4122(QByteArray((char*) packetData + numBytesForPacketHeader(packetData), NUM_BYTES_RFC4122_UUID)); // add or update the node in our list avatarNode = nodeList->nodeWithUUID(nodeUUID); if (avatarNode) { // parse positional data from an node nodeList->updateNodeWithData(avatarNode, &nodeAddress, packetData, receivedBytes); } else { break; } case PACKET_TYPE_INJECT_AUDIO: broadcastAvatarData(nodeList, nodeUUID, &nodeAddress); break; case PACKET_TYPE_AVATAR_URLS: case PACKET_TYPE_AVATAR_FACE_VIDEO: nodeUUID = QUuid::fromRfc4122(QByteArray((char*) packetData + numBytesForPacketHeader(packetData), NUM_BYTES_RFC4122_UUID)); // let everyone else know about the update for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getActiveSocket() && node->getUUID() != nodeUUID) { nodeList->getNodeSocket()->send(node->getActiveSocket(), packetData, receivedBytes); } } break; default: // hand this off to the NodeList nodeList->processNodeData(&nodeAddress, packetData, receivedBytes); break; } } } nodeList->stopSilentNodeRemovalThread(); }
bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) { #ifdef FORCE_CRASH if (connection->requestOperation() == QNetworkAccessManager::GetOperation && path == "/force_crash") { qDebug() << "About to force a crash!"; int foo; int* forceCrash = &foo; QString responseString("forcing a crash..."); connection->respond(HTTPConnection::StatusCode200, qPrintable(responseString)); delete[] forceCrash; return true; } #endif bool showStats = false; if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (path == "/") { showStats = true; } else if (path == "/resetStats") { _octreeInboundPacketProcessor->resetStats(); resetSendingStats(); showStats = true; } } if (showStats) { quint64 checkSum; // return a 200 QString statsString("<html><doc>\r\n<pre>\r\n"); statsString += QString("<b>Your %1 Server is running... <a href='/'>[RELOAD]</a></b>\r\n").arg(getMyServerName()); tm* localtm = localtime(&_started); const int MAX_TIME_LENGTH = 128; char buffer[MAX_TIME_LENGTH]; strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm); statsString += QString("Running since: %1").arg(buffer); // Convert now to tm struct for UTC tm* gmtm = gmtime(&_started); if (gmtm) { strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", gmtm); statsString += (QString(" [%1 UTM] ").arg(buffer)); } statsString += "\r\n"; quint64 now = usecTimestampNow(); const int USECS_PER_MSEC = 1000; quint64 msecsElapsed = (now - _startedUSecs) / USECS_PER_MSEC; const int MSECS_PER_SEC = 1000; const int SECS_PER_MIN = 60; const int MIN_PER_HOUR = 60; const int MSECS_PER_MIN = MSECS_PER_SEC * SECS_PER_MIN; float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC; int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR; int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR)); statsString += "Uptime: "; if (hours > 0) { statsString += QString("%1 hour").arg(hours); if (hours > 1) { statsString += QString("s"); } } if (minutes > 0) { if (hours > 0) { statsString += QString(" "); } statsString += QString("%1 minute").arg(minutes); if (minutes > 1) { statsString += QString("s"); } } if (seconds > 0) { if (hours > 0 || minutes > 0) { statsString += QString(" "); } statsString += QString().sprintf("%.3f seconds", seconds); } statsString += "\r\n\r\n"; // display voxel file load time if (isInitialLoadComplete()) { if (isPersistEnabled()) { statsString += QString("%1 File Persist Enabled...\r\n").arg(getMyServerName()); } else { statsString += QString("%1 File Persist Disabled...\r\n").arg(getMyServerName()); } statsString += "\r\n"; quint64 msecsElapsed = getLoadElapsedTime() / USECS_PER_MSEC;; float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC; int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR; int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR)); statsString += QString("%1 File Load Took ").arg(getMyServerName()); if (hours > 0) { statsString += QString("%1 hour").arg(hours); if (hours > 1) { statsString += QString("s"); } } if (minutes > 0) { if (hours > 0) { statsString += QString(" "); } statsString += QString("%1 minute").arg(minutes); if (minutes > 1) { statsString += QString("s"); } } if (seconds >= 0) { if (hours > 0 || minutes > 0) { statsString += QString(" "); } statsString += QString().sprintf("%.3f seconds", seconds); } statsString += "\r\n"; } else { statsString += "Voxels not yet loaded...\r\n"; } statsString += "\r\n\r\n"; statsString += "<b>Configuration:</b>\r\n"; for (int i = 1; i < _argc; i++) { statsString += _argv[i]; } statsString += "\r\n"; //one to end the config line statsString += "\r\n\r\n"; // two more for spacing // display scene stats unsigned long nodeCount = OctreeElement::getNodeCount(); unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); QLocale locale(QLocale::English); const float AS_PERCENT = 100.0; statsString += "<b>Current Nodes in scene:</b>\r\n"; statsString += QString(" Total Nodes: %1 nodes\r\n").arg(locale.toString((uint)nodeCount).rightJustified(16, ' ')); statsString += QString().sprintf(" Internal Nodes: %s nodes (%5.2f%%)\r\n", locale.toString((uint)internalNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)internalNodeCount / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Leaf Nodes: %s nodes (%5.2f%%)\r\n", locale.toString((uint)leafNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)leafNodeCount / (float)nodeCount) * AS_PERCENT); statsString += "\r\n"; statsString += "\r\n"; // display outbound packet stats statsString += QString("<b>%1 Outbound Packet Statistics... " "<a href='/resetStats'>[RESET]</a></b>\r\n").arg(getMyServerName()); quint64 totalOutboundPackets = OctreeSendThread::_totalPackets; quint64 totalOutboundBytes = OctreeSendThread::_totalBytes; quint64 totalWastedBytes = OctreeSendThread::_totalWastedBytes; quint64 totalBytesOfOctalCodes = OctreePacketData::getTotalBytesOfOctalCodes(); quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks(); quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor(); const int COLUMN_WIDTH = 19; statsString += QString(" Configured Max PPS/Client: %1 pps/client\r\n") .arg(locale.toString((uint)getPacketsPerClientPerSecond()).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Configured Max PPS/Server: %1 pps/server\r\n\r\n") .arg(locale.toString((uint)getPacketsTotalPerSecond()).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Total Clients Connected: %1 clients\r\n\r\n") .arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' ')); float averageLoopTime = getAverageLoopTime(); statsString += QString().sprintf(" Average packetLoop() time: %7.2f msecs\r\n", averageLoopTime); float averageInsideTime = getAverageInsideTime(); statsString += QString().sprintf(" Average 'inside' time: %9.2f usecs\r\n\r\n", averageInsideTime); int allWaitTimes = _extraLongTreeWait +_longTreeWait + _shortTreeWait + _noTreeWait; float averageTreeWaitTime = getAverageTreeWaitTime(); statsString += QString().sprintf(" Average tree lock wait time:" " %9.2f usecs samples: %12d \r\n", averageTreeWaitTime, allWaitTimes); float zeroVsTotal = (allWaitTimes > 0) ? ((float)_noTreeWait / (float)allWaitTimes) : 0.0f; statsString += QString().sprintf(" No Lock Wait:" " (%6.2f%%) samples: %12d \r\n", zeroVsTotal * AS_PERCENT, _noTreeWait); float shortVsTotal = (allWaitTimes > 0) ? ((float)_shortTreeWait / (float)allWaitTimes) : 0.0f; statsString += QString().sprintf(" Avg tree lock short wait time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n", _averageTreeShortWaitTime.getAverage(), shortVsTotal * AS_PERCENT, _shortTreeWait); float longVsTotal = (allWaitTimes > 0) ? ((float)_longTreeWait / (float)allWaitTimes) : 0.0f; statsString += QString().sprintf(" Avg tree lock long wait time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n", _averageTreeLongWaitTime.getAverage(), longVsTotal * AS_PERCENT, _longTreeWait); float extraLongVsTotal = (allWaitTimes > 0) ? ((float)_extraLongTreeWait / (float)allWaitTimes) : 0.0f; statsString += QString().sprintf(" Avg tree lock extra long wait time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n", _averageTreeExtraLongWaitTime.getAverage(), extraLongVsTotal * AS_PERCENT, _extraLongTreeWait); float averageEncodeTime = getAverageEncodeTime(); statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", averageEncodeTime); int allEncodeTimes = _noEncode + _shortEncode + _longEncode + _extraLongEncode; float zeroVsTotalEncode = (allEncodeTimes > 0) ? ((float)_noEncode / (float)allEncodeTimes) : 0.0f; statsString += QString().sprintf(" No Encode:" " (%6.2f%%) samples: %12d \r\n", zeroVsTotalEncode * AS_PERCENT, _noEncode); float shortVsTotalEncode = (allEncodeTimes > 0) ? ((float)_shortEncode / (float)allEncodeTimes) : 0.0f; statsString += QString().sprintf(" Avg short encode time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n", _averageShortEncodeTime.getAverage(), shortVsTotalEncode * AS_PERCENT, _shortEncode); float longVsTotalEncode = (allEncodeTimes > 0) ? ((float)_longEncode / (float)allEncodeTimes) : 0.0f; statsString += QString().sprintf(" Avg long encode time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n", _averageLongEncodeTime.getAverage(), longVsTotalEncode * AS_PERCENT, _longEncode); float extraLongVsTotalEncode = (allEncodeTimes > 0) ? ((float)_extraLongEncode / (float)allEncodeTimes) : 0.0f; statsString += QString().sprintf(" Avg extra long encode time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n", _averageExtraLongEncodeTime.getAverage(), extraLongVsTotalEncode * AS_PERCENT, _extraLongEncode); float averageCompressAndWriteTime = getAverageCompressAndWriteTime(); statsString += QString().sprintf(" Average compress and write time: %9.2f usecs\r\n", averageCompressAndWriteTime); int allCompressTimes = _noCompress + _shortCompress + _longCompress + _extraLongCompress; float zeroVsTotalCompress = (allCompressTimes > 0) ? ((float)_noCompress / (float)allCompressTimes) : 0.0f; statsString += QString().sprintf(" No compression:" " (%6.2f%%) samples: %12d \r\n", zeroVsTotalCompress * AS_PERCENT, _noCompress); float shortVsTotalCompress = (allCompressTimes > 0) ? ((float)_shortCompress / (float)allCompressTimes) : 0.0f; statsString += QString().sprintf(" Avg short compress time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n", _averageShortCompressTime.getAverage(), shortVsTotalCompress * AS_PERCENT, _shortCompress); float longVsTotalCompress = (allCompressTimes > 0) ? ((float)_longCompress / (float)allCompressTimes) : 0.0f; statsString += QString().sprintf(" Avg long compress time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n", _averageLongCompressTime.getAverage(), longVsTotalCompress * AS_PERCENT, _longCompress); float extraLongVsTotalCompress = (allCompressTimes > 0) ? ((float)_extraLongCompress / (float)allCompressTimes) : 0.0f; statsString += QString().sprintf(" Avg extra long compress time:" " %9.2f usecs (%6.2f%%) samples: %12d \r\n\r\n", _averageExtraLongCompressTime.getAverage(), extraLongVsTotalCompress * AS_PERCENT, _extraLongCompress); float averagePacketSendingTime = getAveragePacketSendingTime(); statsString += QString().sprintf(" Average packet sending time: %9.2f usecs (includes node lock)\r\n", averagePacketSendingTime); float noVsTotalSend = (_averagePacketSendingTime.getSampleCount() > 0) ? ((float)_noSend / (float)_averagePacketSendingTime.getSampleCount()) : 0.0f; statsString += QString().sprintf(" Not sending:" " (%6.2f%%) samples: %12d \r\n", noVsTotalSend * AS_PERCENT, _noSend); float averageNodeWaitTime = getAverageNodeWaitTime(); statsString += QString().sprintf(" Average node lock wait time: %9.2f usecs\r\n", averageNodeWaitTime); statsString += QString().sprintf("--------------------------------------------------------------\r\n"); float encodeToInsidePercent = averageInsideTime == 0.0f ? 0.0f : (averageEncodeTime / averageInsideTime) * AS_PERCENT; statsString += QString().sprintf(" encode ratio: %5.2f%%\r\n", encodeToInsidePercent); float waitToInsidePercent = averageInsideTime == 0.0f ? 0.0f : ((averageTreeWaitTime + averageNodeWaitTime) / averageInsideTime) * AS_PERCENT; statsString += QString().sprintf(" waiting ratio: %5.2f%%\r\n", waitToInsidePercent); float compressAndWriteToInsidePercent = averageInsideTime == 0.0f ? 0.0f : (averageCompressAndWriteTime / averageInsideTime) * AS_PERCENT; statsString += QString().sprintf(" compress and write ratio: %5.2f%%\r\n", compressAndWriteToInsidePercent); float sendingToInsidePercent = averageInsideTime == 0.0f ? 0.0f : (averagePacketSendingTime / averageInsideTime) * AS_PERCENT; statsString += QString().sprintf(" sending ratio: %5.2f%%\r\n", sendingToInsidePercent); statsString += QString("\r\n"); statsString += QString(" Total Outbound Packets: %1 packets\r\n") .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 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", locale.toString((uint)totalBytesOfOctalCodes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(), ((float)totalBytesOfOctalCodes / (float)totalOutboundBytes) * AS_PERCENT); statsString += QString().sprintf(" Total BitMasks Bytes: %s bytes (%5.2f%%)\r\n", locale.toString((uint)totalBytesOfBitMasks).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(), ((float)totalBytesOfBitMasks / (float)totalOutboundBytes) * AS_PERCENT); statsString += QString().sprintf(" Total Color Bytes: %s bytes (%5.2f%%)\r\n", locale.toString((uint)totalBytesOfColor).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(), ((float)totalBytesOfColor / (float)totalOutboundBytes) * AS_PERCENT); statsString += "\r\n"; statsString += "\r\n"; // display inbound packet stats statsString += QString().sprintf("<b>%s Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n", getMyServerName()); quint64 averageTransitTimePerPacket = _octreeInboundPacketProcessor->getAverageTransitTimePerPacket(); quint64 averageProcessTimePerPacket = _octreeInboundPacketProcessor->getAverageProcessTimePerPacket(); quint64 averageLockWaitTimePerPacket = _octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket(); quint64 averageProcessTimePerElement = _octreeInboundPacketProcessor->getAverageProcessTimePerElement(); quint64 averageLockWaitTimePerElement = _octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); quint64 totalElementsProcessed = _octreeInboundPacketProcessor->getTotalElementsProcessed(); quint64 totalPacketsProcessed = _octreeInboundPacketProcessor->getTotalPacketsProcessed(); float averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed; statsString += QString(" Total Inbound Packets: %1 packets\r\n") .arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Total Inbound Elements: %1 elements\r\n") .arg(locale.toString((uint)totalElementsProcessed).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString().sprintf(" Average Inbound Elements/Packet: %f elements/packet\r\n", averageElementsPerPacket); statsString += QString(" Average Transit Time/Packet: %1 usecs\r\n") .arg(locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Process Time/Packet: %1 usecs\r\n") .arg(locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Wait Lock Time/Packet: %1 usecs\r\n") .arg(locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Process Time/Element: %1 usecs\r\n") .arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n") .arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' ')); int senderNumber = 0; NodeToSenderStatsMap& allSenderStats = _octreeInboundPacketProcessor->getSingleSenderStats(); for (NodeToSenderStatsMapIterator i = allSenderStats.begin(); i != allSenderStats.end(); i++) { senderNumber++; QUuid senderID = i->first; SingleSenderStats& senderStats = i->second; statsString += QString("\r\n Stats for sender %1 uuid: %2\r\n") .arg(senderNumber).arg(senderID.toString()); averageTransitTimePerPacket = senderStats.getAverageTransitTimePerPacket(); averageProcessTimePerPacket = senderStats.getAverageProcessTimePerPacket(); averageLockWaitTimePerPacket = senderStats.getAverageLockWaitTimePerPacket(); averageProcessTimePerElement = senderStats.getAverageProcessTimePerElement(); averageLockWaitTimePerElement = senderStats.getAverageLockWaitTimePerElement(); totalElementsProcessed = senderStats.getTotalElementsProcessed(); totalPacketsProcessed = senderStats.getTotalPacketsProcessed(); averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed; statsString += QString(" Total Inbound Packets: %1 packets\r\n") .arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Total Inbound Elements: %1 elements\r\n") .arg(locale.toString((uint)totalElementsProcessed).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString().sprintf(" Average Inbound Elements/Packet: %f elements/packet\r\n", averageElementsPerPacket); statsString += QString(" Average Transit Time/Packet: %1 usecs\r\n") .arg(locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Process Time/Packet: %1 usecs\r\n") .arg(locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Wait Lock Time/Packet: %1 usecs\r\n") .arg(locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Process Time/Element: %1 usecs\r\n") .arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' ')); statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n") .arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' ')); } statsString += "\r\n\r\n"; // display memory usage stats statsString += "<b>Current Memory Usage Statistics</b>\r\n"; statsString += QString().sprintf("\r\nOctreeElement size... %ld bytes\r\n", sizeof(OctreeElement)); statsString += "\r\n"; const char* memoryScaleLabel; const float MEGABYTES = 1000000.f; const float GIGABYTES = 1000000000.f; float memoryScale; if (OctreeElement::getTotalMemoryUsage() / MEGABYTES < 1000.0f) { memoryScaleLabel = "MB"; memoryScale = MEGABYTES; } else { memoryScaleLabel = "GB"; memoryScale = GIGABYTES; } statsString += QString().sprintf("Element Node Memory Usage: %8.2f %s\r\n", OctreeElement::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel); statsString += QString().sprintf("Octcode Memory Usage: %8.2f %s\r\n", OctreeElement::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel); statsString += QString().sprintf("External Children Memory Usage: %8.2f %s\r\n", OctreeElement::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel); statsString += " -----------\r\n"; statsString += QString().sprintf(" Total: %8.2f %s\r\n", OctreeElement::getTotalMemoryUsage() / memoryScale, memoryScaleLabel); statsString += "\r\n"; statsString += "OctreeElement Children Population Statistics...\r\n"; checkSum = 0; for (int i=0; i <= NUMBER_OF_CHILDREN; i++) { checkSum += OctreeElement::getChildrenCount(i); statsString += QString().sprintf(" Nodes with %d children: %s nodes (%5.2f%%)\r\n", i, locale.toString((uint)OctreeElement::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(), ((float)OctreeElement::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT); } statsString += " ----------------------\r\n"; statsString += QString(" Total: %1 nodes\r\n") .arg(locale.toString((uint)checkSum).rightJustified(16, ' ')); #ifdef BLENDED_UNION_CHILDREN statsString += "\r\n"; statsString += "OctreeElement Children Encoding Statistics...\r\n"; statsString += QString().sprintf(" Single or No Children: %10.llu nodes (%5.2f%%)\r\n", OctreeElement::getSingleChildrenCount(), ((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT)); statsString += QString().sprintf(" Two Children as Offset: %10.llu nodes (%5.2f%%)\r\n", OctreeElement::getTwoChildrenOffsetCount(), ((float)OctreeElement::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT)); statsString += QString().sprintf(" Two Children as External: %10.llu nodes (%5.2f%%)\r\n", OctreeElement::getTwoChildrenExternalCount(), ((float)OctreeElement::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Three Children as Offset: %10.llu nodes (%5.2f%%)\r\n", OctreeElement::getThreeChildrenOffsetCount(), ((float)OctreeElement::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Three Children as External: %10.llu nodes (%5.2f%%)\r\n", OctreeElement::getThreeChildrenExternalCount(), ((float)OctreeElement::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT); statsString += QString().sprintf(" Children as External Array: %10.llu nodes (%5.2f%%)\r\n", OctreeElement::getExternalChildrenCount(), ((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT); checkSum = OctreeElement::getSingleChildrenCount() + OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() + OctreeElement::getThreeChildrenOffsetCount() + OctreeElement::getThreeChildrenExternalCount() + OctreeElement::getExternalChildrenCount(); statsString += " ----------------\r\n"; statsString += QString().sprintf(" Total: %10.llu nodes\r\n", checkSum); statsString += QString().sprintf(" Expected: %10.lu nodes\r\n", nodeCount); statsString += "\r\n"; statsString += "In other news....\r\n"; statsString += QString().sprintf("could store 4 children internally: %10.llu nodes\r\n", OctreeElement::getCouldStoreFourChildrenInternally()); statsString += QString().sprintf("could NOT store 4 children internally: %10.llu nodes\r\n", OctreeElement::getCouldNotStoreFourChildrenInternally()); #endif statsString += "\r\n\r\n"; statsString += "</pre>\r\n"; statsString += "</doc></html>"; connection->respond(HTTPConnection::StatusCode200, qPrintable(statsString), "text/html"); return true; } else { // have HTTPManager attempt to process this request from the document_root return false;
int main(int argc, const char * argv[]) { pthread_mutex_init(&::treeLock, NULL); qInstallMessageHandler(sharedMessageHandler); int listenPort = VOXEL_LISTEN_PORT; // Check to see if the user passed in a command line option for setting listen port const char* PORT_PARAMETER = "--port"; const char* portParameter = getCmdOption(argc, argv, PORT_PARAMETER); if (portParameter) { listenPort = atoi(portParameter); if (listenPort < 1) { listenPort = VOXEL_LISTEN_PORT; } printf("portParameter=%s listenPort=%d\n", portParameter, listenPort); } const char* JURISDICTION_FILE = "--jurisdictionFile"; const char* jurisdictionFile = getCmdOption(argc, argv, JURISDICTION_FILE); if (jurisdictionFile) { printf("jurisdictionFile=%s\n", jurisdictionFile); printf("about to readFromFile().... jurisdictionFile=%s\n", jurisdictionFile); jurisdiction = new JurisdictionMap(jurisdictionFile); printf("after readFromFile().... jurisdictionFile=%s\n", jurisdictionFile); } else { const char* JURISDICTION_ROOT = "--jurisdictionRoot"; const char* jurisdictionRoot = getCmdOption(argc, argv, JURISDICTION_ROOT); if (jurisdictionRoot) { printf("jurisdictionRoot=%s\n", jurisdictionRoot); } const char* JURISDICTION_ENDNODES = "--jurisdictionEndNodes"; const char* jurisdictionEndNodes = getCmdOption(argc, argv, JURISDICTION_ENDNODES); if (jurisdictionEndNodes) { printf("jurisdictionEndNodes=%s\n", jurisdictionEndNodes); } if (jurisdictionRoot || jurisdictionEndNodes) { ::jurisdiction = new JurisdictionMap(jurisdictionRoot, jurisdictionEndNodes); } } // should we send environments? Default is yes, but this command line suppresses sending const char* DUMP_VOXELS_ON_MOVE = "--dumpVoxelsOnMove"; ::dumpVoxelsOnMove = cmdOptionExists(argc, argv, DUMP_VOXELS_ON_MOVE); printf("dumpVoxelsOnMove=%s\n", debug::valueOf(::dumpVoxelsOnMove)); // should we send environments? Default is yes, but this command line suppresses sending const char* DONT_SEND_ENVIRONMENTS = "--dontSendEnvironments"; bool dontSendEnvironments = cmdOptionExists(argc, argv, DONT_SEND_ENVIRONMENTS); if (dontSendEnvironments) { printf("Sending environments suppressed...\n"); ::sendEnvironments = false; } else { // should we send environments? Default is yes, but this command line suppresses sending const char* MINIMAL_ENVIRONMENT = "--MinimalEnvironment"; ::sendMinimalEnvironment = cmdOptionExists(argc, argv, MINIMAL_ENVIRONMENT); printf("Using Minimal Environment=%s\n", debug::valueOf(::sendMinimalEnvironment)); } printf("Sending environments=%s\n", debug::valueOf(::sendEnvironments)); NodeList* nodeList = NodeList::createInstance(NODE_TYPE_VOXEL_SERVER, listenPort); setvbuf(stdout, NULL, _IOLBF, 0); // tell our NodeList about our desire to get notifications nodeList->addHook(&nodeWatcher); // Handle Local Domain testing with the --local command line const char* local = "--local"; ::wantLocalDomain = cmdOptionExists(argc, argv,local); if (::wantLocalDomain) { printf("Local Domain MODE!\n"); nodeList->setDomainIPToLocalhost(); } else { const char* domainIP = getCmdOption(argc, argv, "--domain"); if (domainIP) { NodeList::getInstance()->setDomainHostname(domainIP); } } nodeList->linkedDataCreateCallback = &attachVoxelNodeDataToNode; nodeList->startSilentNodeRemovalThread(); srand((unsigned)time(0)); const char* DISPLAY_VOXEL_STATS = "--displayVoxelStats"; ::displayVoxelStats = cmdOptionExists(argc, argv, DISPLAY_VOXEL_STATS); printf("displayVoxelStats=%s\n", debug::valueOf(::displayVoxelStats)); const char* DEBUG_VOXEL_SENDING = "--debugVoxelSending"; ::debugVoxelSending = cmdOptionExists(argc, argv, DEBUG_VOXEL_SENDING); printf("debugVoxelSending=%s\n", debug::valueOf(::debugVoxelSending)); const char* DEBUG_VOXEL_RECEIVING = "--debugVoxelReceiving"; ::debugVoxelReceiving = cmdOptionExists(argc, argv, DEBUG_VOXEL_RECEIVING); printf("debugVoxelReceiving=%s\n", debug::valueOf(::debugVoxelReceiving)); const char* WANT_ANIMATION_DEBUG = "--shouldShowAnimationDebug"; ::shouldShowAnimationDebug = cmdOptionExists(argc, argv, WANT_ANIMATION_DEBUG); printf("shouldShowAnimationDebug=%s\n", debug::valueOf(::shouldShowAnimationDebug)); // By default we will voxel persist, if you want to disable this, then pass in this parameter const char* NO_VOXEL_PERSIST = "--NoVoxelPersist"; if (cmdOptionExists(argc, argv, NO_VOXEL_PERSIST)) { ::wantVoxelPersist = false; } printf("wantVoxelPersist=%s\n", debug::valueOf(::wantVoxelPersist)); // if we want Voxel Persistence, load the local file now... bool persistantFileRead = false; if (::wantVoxelPersist) { // Check to see if the user passed in a command line option for setting packet send rate const char* VOXELS_PERSIST_FILENAME = "--voxelsPersistFilename"; const char* voxelsPersistFilenameParameter = getCmdOption(argc, argv, VOXELS_PERSIST_FILENAME); if (voxelsPersistFilenameParameter) { strcpy(voxelPersistFilename, voxelsPersistFilenameParameter); } else { strcpy(voxelPersistFilename, ::wantLocalDomain ? LOCAL_VOXELS_PERSIST_FILE : VOXELS_PERSIST_FILE); } printf("loading voxels from file: %s...\n", voxelPersistFilename); persistantFileRead = ::serverTree.readFromSVOFile(::voxelPersistFilename); if (persistantFileRead) { PerformanceWarning warn(::shouldShowAnimationDebug, "persistVoxelsWhenDirty() - reaverageVoxelColors()", ::shouldShowAnimationDebug); // after done inserting all these voxels, then reaverage colors serverTree.reaverageVoxelColors(serverTree.rootNode); printf("Voxels reAveraged\n"); } ::serverTree.clearDirtyBit(); // the tree is clean since we just loaded it printf("DONE loading voxels from file... fileRead=%s\n", debug::valueOf(persistantFileRead)); unsigned long nodeCount = ::serverTree.rootNode->getSubTreeNodeCount(); unsigned long internalNodeCount = ::serverTree.rootNode->getSubTreeInternalNodeCount(); unsigned long leafNodeCount = ::serverTree.rootNode->getSubTreeLeafNodeCount(); printf("Nodes after loading scene %lu nodes %lu internal %lu leaves\n", nodeCount, internalNodeCount, leafNodeCount); // now set up VoxelPersistThread ::voxelPersistThread = new VoxelPersistThread(&::serverTree, ::voxelPersistFilename); if (::voxelPersistThread) { ::voxelPersistThread->initialize(true); } } // Check to see if the user passed in a command line option for loading an old style local // Voxel File. If so, load it now. This is not the same as a voxel persist file const char* INPUT_FILE = "-i"; const char* voxelsFilename = getCmdOption(argc, argv, INPUT_FILE); if (voxelsFilename) { serverTree.readFromSVOFile(voxelsFilename); } // Check to see if the user passed in a command line option for setting packet send rate const char* PACKETS_PER_SECOND = "--packetsPerSecond"; const char* packetsPerSecond = getCmdOption(argc, argv, PACKETS_PER_SECOND); if (packetsPerSecond) { PACKETS_PER_CLIENT_PER_INTERVAL = atoi(packetsPerSecond)/INTERVALS_PER_SECOND; if (PACKETS_PER_CLIENT_PER_INTERVAL < 1) { PACKETS_PER_CLIENT_PER_INTERVAL = 1; } printf("packetsPerSecond=%s PACKETS_PER_CLIENT_PER_INTERVAL=%d\n", packetsPerSecond, PACKETS_PER_CLIENT_PER_INTERVAL); } // for now, initialize the environments with fixed values environmentData[1].setID(1); environmentData[1].setGravity(1.0f); environmentData[1].setAtmosphereCenter(glm::vec3(0.5, 0.5, (0.25 - 0.06125)) * (float)TREE_SCALE); environmentData[1].setAtmosphereInnerRadius(0.030625f * TREE_SCALE); environmentData[1].setAtmosphereOuterRadius(0.030625f * TREE_SCALE * 1.05f); environmentData[2].setID(2); environmentData[2].setGravity(1.0f); environmentData[2].setAtmosphereCenter(glm::vec3(0.5f, 0.5f, 0.5f) * (float)TREE_SCALE); environmentData[2].setAtmosphereInnerRadius(0.1875f * TREE_SCALE); environmentData[2].setAtmosphereOuterRadius(0.1875f * TREE_SCALE * 1.05f); environmentData[2].setScatteringWavelengths(glm::vec3(0.475f, 0.570f, 0.650f)); // swaps red and blue sockaddr senderAddress; unsigned char *packetData = new unsigned char[MAX_PACKET_SIZE]; ssize_t packetLength; timeval lastDomainServerCheckIn = {}; // set up our jurisdiction broadcaster... ::jurisdictionSender = new JurisdictionSender(::jurisdiction); if (::jurisdictionSender) { ::jurisdictionSender->initialize(true); } // set up our VoxelServerPacketProcessor ::voxelServerPacketProcessor = new VoxelServerPacketProcessor(); if (::voxelServerPacketProcessor) { ::voxelServerPacketProcessor->initialize(true); } // loop to send to nodes requesting data while (true) { // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); NodeList::getInstance()->sendDomainServerCheckIn(); } if (nodeList->getNodeSocket()->receive(&senderAddress, packetData, &packetLength) && packetVersionMatch(packetData)) { int numBytesPacketHeader = numBytesForPacketHeader(packetData); if (packetData[0] == PACKET_TYPE_HEAD_DATA) { // If we got a PACKET_TYPE_HEAD_DATA, then we're talking to an NODE_TYPE_AVATAR, and we // need to make sure we have it in our nodeList. uint16_t nodeID = 0; unpackNodeId(packetData + numBytesPacketHeader, &nodeID); Node* node = NodeList::getInstance()->addOrUpdateNode(&senderAddress, &senderAddress, NODE_TYPE_AGENT, nodeID); NodeList::getInstance()->updateNodeWithData(node, packetData, packetLength); } else if (packetData[0] == PACKET_TYPE_PING) { // If the packet is a ping, let processNodeData handle it. NodeList::getInstance()->processNodeData(&senderAddress, packetData, packetLength); } else if (packetData[0] == PACKET_TYPE_DOMAIN) { NodeList::getInstance()->processNodeData(&senderAddress, packetData, packetLength); } else if (packetData[0] == PACKET_TYPE_VOXEL_JURISDICTION_REQUEST) { if (::jurisdictionSender) { ::jurisdictionSender->queueReceivedPacket(senderAddress, packetData, packetLength); } } else if (::voxelServerPacketProcessor) { ::voxelServerPacketProcessor->queueReceivedPacket(senderAddress, packetData, packetLength); } else { printf("unknown packet ignored... packetData[0]=%c\n", packetData[0]); } } } if (::jurisdiction) { delete ::jurisdiction; } if (::jurisdictionSender) { ::jurisdictionSender->terminate(); delete ::jurisdictionSender; } if (::voxelServerPacketProcessor) { ::voxelServerPacketProcessor->terminate(); delete ::voxelServerPacketProcessor; } if (::voxelPersistThread) { ::voxelPersistThread->terminate(); delete ::voxelPersistThread; } // tell our NodeList we're done with notifications nodeList->removeHook(&nodeWatcher); pthread_mutex_destroy(&::treeLock); return 0; }
void OctreeTests::byteCountCodingTests(bool verbose) { int testsTaken = 0; int testsPassed = 0; int testsFailed = 0; if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << "OctreeTests::byteCountCodingTests()"; QByteArray encoded; if (verbose) { qDebug() << "ByteCountCodedUINT zero(0)"; } ByteCountCodedUINT zero(0); encoded = zero.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedUINT decodedZero; decodedZero.decode(encoded); if (verbose) { qDebug() << "decodedZero=" << decodedZero.data; qDebug() << "decodedZero==zero" << (decodedZero == zero) << " { expected true } "; } testsTaken++; bool result1 = (decodedZero.data == 0); bool expected1 = true; if (result1 == expected1) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 1: ByteCountCodedUINT zero(0) decodedZero.data == 0"; } testsTaken++; bool result2 = (decodedZero == zero); bool expected2 = true; if (result2 == expected2) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 2: ByteCountCodedUINT zero(0) (decodedZero == zero)"; } ByteCountCodedUINT decodedZeroB(encoded); if (verbose) { qDebug() << "decodedZeroB=" << decodedZeroB.data; } testsTaken++; bool result3 = (decodedZeroB.data == 0); bool expected3 = true; if (result3 == expected3) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 3: (decodedZeroB.data == 0)"; } if (verbose) { qDebug() << "ByteCountCodedUINT foo(259)"; } ByteCountCodedUINT foo(259); encoded = foo.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedUINT decodedFoo; decodedFoo.decode(encoded); if (verbose) { qDebug() << "decodedFoo=" << decodedFoo.data; qDebug() << "decodedFoo==foo" << (decodedFoo == foo) << " { expected true } "; } testsTaken++; bool result4 = (decodedFoo.data == 259); bool expected4 = true; if (result4 == expected4) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 4: ByteCountCodedUINT zero(0) (decodedFoo.data == 259)"; } testsTaken++; bool result5 = (decodedFoo == foo); bool expected5 = true; if (result5 == expected5) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 5: (decodedFoo == foo)"; } ByteCountCodedUINT decodedFooB(encoded); if (verbose) { qDebug() << "decodedFooB=" << decodedFooB.data; } testsTaken++; bool result6 = (decodedFooB.data == 259); bool expected6 = true; if (result6 == expected6) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 6: (decodedFooB.data == 259)"; } if (verbose) { qDebug() << "ByteCountCodedUINT bar(1000000)"; } ByteCountCodedUINT bar(1000000); encoded = bar.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedUINT decodedBar; decodedBar.decode(encoded); if (verbose) { qDebug() << "decodedBar=" << decodedBar.data; qDebug() << "decodedBar==bar" << (decodedBar == bar) << " { expected true } "; } testsTaken++; bool result7 = (decodedBar.data == 1000000); bool expected7 = true; if (result7 == expected7) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 7: ByteCountCodedUINT zero(0) (decodedBar.data == 1000000)"; } testsTaken++; bool result8 = (decodedBar == bar); bool expected8 = true; if (result8 == expected8) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 8: (decodedBar == bar)"; } if (verbose) { qDebug() << "ByteCountCodedUINT spam(4294967295/2)"; } ByteCountCodedUINT spam(4294967295/2); encoded = spam.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedUINT decodedSpam; decodedSpam.decode(encoded); if (verbose) { qDebug() << "decodedSpam=" << decodedSpam.data; qDebug() << "decodedSpam==spam" << (decodedSpam==spam) << " { expected true } "; } testsTaken++; bool result9 = (decodedSpam.data == 4294967295/2); bool expected9 = true; if (result9 == expected9) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 9: (decodedSpam.data == 4294967295/2)"; } testsTaken++; bool result10 = (decodedSpam == spam); bool expected10 = true; if (result10 == expected10) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 10: (decodedSpam == spam)"; } if (verbose) { qDebug() << "ByteCountCodedQUINT64 foo64(259)"; } ByteCountCodedQUINT64 foo64(259); encoded = foo64.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } if (verbose) { qDebug() << "testing... quint64 foo64POD = foo64;"; } quint64 foo64POD = foo64; if (verbose) { qDebug() << "foo64POD=" << foo64POD; } testsTaken++; bool result11 = (foo64POD == 259); bool expected11 = true; if (result11 == expected11) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 11: quint64 foo64POD = foo64"; } if (verbose) { qDebug() << "testing... encoded = foo64;"; } encoded = foo64; if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedQUINT64 decodedFoo64; decodedFoo64 = encoded; if (verbose) { qDebug() << "decodedFoo64=" << decodedFoo64.data; qDebug() << "decodedFoo64==foo64" << (decodedFoo64==foo64) << " { expected true } "; } testsTaken++; bool result12 = (decodedFoo64.data == 259); bool expected12 = true; if (result12 == expected12) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 12: decodedFoo64.data == 259"; } testsTaken++; bool result13 = (decodedFoo64==foo64); bool expected13 = true; if (result13 == expected13) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 13: decodedFoo64==foo64"; } if (verbose) { qDebug() << "ByteCountCodedQUINT64 bar64(1000000)"; } ByteCountCodedQUINT64 bar64(1000000); encoded = bar64.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedQUINT64 decodedBar64; decodedBar64.decode(encoded); if (verbose) { qDebug() << "decodedBar64=" << decodedBar64.data; qDebug() << "decodedBar64==bar64" << (decodedBar64==bar64) << " { expected true } "; } testsTaken++; bool result14 = (decodedBar64.data == 1000000); bool expected14 = true; if (result14 == expected14) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 14: decodedBar64.data == 1000000"; } testsTaken++; bool result15 = (decodedBar64==bar64); bool expected15 = true; if (result15 == expected15) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 15: decodedBar64==bar64"; } if (verbose) { qDebug() << "ByteCountCodedQUINT64 spam64(4294967295/2)"; } ByteCountCodedQUINT64 spam64(4294967295/2); encoded = spam64.encode(); if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } ByteCountCodedQUINT64 decodedSpam64; decodedSpam64.decode(encoded); if (verbose) { qDebug() << "decodedSpam64=" << decodedSpam64.data; qDebug() << "decodedSpam64==spam64" << (decodedSpam64==spam64) << " { expected true } "; } testsTaken++; bool result16 = (decodedSpam64.data == 4294967295/2); bool expected16 = true; if (result16 == expected16) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 16: decodedSpam64.data == 4294967295/2"; } testsTaken++; bool result17 = (decodedSpam64==spam64); bool expected17 = true; if (result17 == expected17) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 17: decodedSpam64==spam64"; } if (verbose) { qDebug() << "testing encoded << spam64"; } encoded.clear(); encoded << spam64; if (verbose) { outputBufferBits((const unsigned char*)encoded.constData(), encoded.size()); } if (verbose) { qDebug() << "testing encoded >> decodedSpam64"; } encoded >> decodedSpam64; if (verbose) { qDebug() << "decodedSpam64=" << decodedSpam64.data; } testsTaken++; bool result18 = (decodedSpam64==spam64); bool expected18 = true; if (result18 == expected18) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 18: decodedSpam64==spam64"; } //ByteCountCodedINT shouldFail(-100); if (verbose) { qDebug() << "NOW..."; } quint64 now = usecTimestampNow(); ByteCountCodedQUINT64 nowCoded = now; QByteArray nowEncoded = nowCoded; if (verbose) { outputBufferBits((const unsigned char*)nowEncoded.constData(), nowEncoded.size()); } ByteCountCodedQUINT64 decodedNow = nowEncoded; testsTaken++; bool result19 = (decodedNow.data==now); bool expected19 = true; if (result19 == expected19) { testsPassed++; } else { testsFailed++; qDebug() << "FAILED - Test 19: now test..."; } if (verbose) { qDebug() << "******************************************************************************************"; } qDebug() << " tests passed:" << testsPassed << "out of" << testsTaken; if (verbose) { qDebug() << "******************************************************************************************"; } }
void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { if (_shuttingDown) { qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet"; return; } bool debugProcessPacket = _myServer->wantsVerboseDebug(); if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() packetData=%p packetLength=%d", &packet, packet.size()); } int numBytesPacketHeader = numBytesForPacketHeader(packet); // Ask our tree subclass if it can handle the incoming packet... PacketType packetType = packetTypeForPacket(packet); if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE",debugProcessPacket); _receivedPacketCount++; const unsigned char* packetData = reinterpret_cast<const unsigned char*>(packet.data()); unsigned short int sequence = (*((unsigned short int*)(packetData + numBytesPacketHeader))); quint64 sentAt = (*((quint64*)(packetData + numBytesPacketHeader + sizeof(sequence)))); quint64 arrivedAt = usecTimestampNow(); quint64 transitTime = arrivedAt - sentAt; int editsInPacket = 0; quint64 processTime = 0; quint64 lockWaitTime = 0; if (_myServer->wantsDebugReceiving()) { qDebug() << "PROCESSING THREAD: got '" << packetType << "' packet - " << _receivedPacketCount << " command from client receivedBytes=" << packet.size() << " sequence=" << sequence << " transitTime=" << transitTime << " usecs"; } int atByte = numBytesPacketHeader + sizeof(sequence) + sizeof(sentAt); unsigned char* editData = (unsigned char*)&packetData[atByte]; while (atByte < packet.size()) { int maxSize = packet.size() - atByte; if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() %c " "packetData=%p packetLength=%d voxelData=%p atByte=%d maxSize=%d", packetType, packetData, packet.size(), editData, atByte, maxSize); } quint64 startLock = usecTimestampNow(); _myServer->getOctree()->lockForWrite(); quint64 startProcess = usecTimestampNow(); int editDataBytesRead = _myServer->getOctree()->processEditPacketData(packetType, reinterpret_cast<const unsigned char*>(packet.data()), packet.size(), editData, maxSize, sendingNode); _myServer->getOctree()->unlock(); quint64 endProcess = usecTimestampNow(); editsInPacket++; quint64 thisProcessTime = endProcess - startProcess; quint64 thisLockWaitTime = startProcess - startLock; processTime += thisProcessTime; lockWaitTime += thisLockWaitTime; // skip to next voxel edit record in the packet editData += editDataBytesRead; atByte += editDataBytesRead; } if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %c " "packetData=%p packetLength=%d voxelData=%p atByte=%d", packetType, packetData, packet.size(), editData, atByte); } // Make sure our Node and NodeList knows we've heard from this node. QUuid& nodeUUID = DEFAULT_NODE_ID_REF; if (sendingNode) { sendingNode->setLastHeardMicrostamp(usecTimestampNow()); nodeUUID = sendingNode->getUUID(); if (debugProcessPacket) { qDebug() << "sender has uuid=" << nodeUUID; } } else { if (debugProcessPacket) { qDebug() << "sender has no known nodeUUID."; } } trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime); } else { qDebug("unknown packet ignored... packetType=%d", packetType); } }
void DdeFaceTracker::decodePacket(const QByteArray& buffer) { if(buffer.size() > MIN_PACKET_SIZE) { bool isFiltering = Menu::getInstance()->isOptionChecked(MenuOption::VelocityFilter); Packet packet; int bytesToCopy = glm::min((int)sizeof(packet), buffer.size()); memset(&packet.name, '\n', MAX_NAME_SIZE + 1); memcpy(&packet, buffer.data(), bytesToCopy); glm::vec3 translation; memcpy(&translation, packet.translation, sizeof(packet.translation)); glm::quat rotation; memcpy(&rotation, &packet.rotation, sizeof(packet.rotation)); if (_reset || (_lastReceiveTimestamp == 0)) { memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3)); memcpy(&_referenceRotation, &rotation, sizeof(glm::quat)); _reset = false; } // Compute relative translation float LEAN_DAMPING_FACTOR = 75.0f; translation -= _referenceTranslation; translation /= LEAN_DAMPING_FACTOR; translation.x *= -1; if (isFiltering) { glm::vec3 linearVelocity = (translation - _lastHeadTranslation) / _averageMessageTime; const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f; float velocityFilter = glm::clamp(1.0f - glm::length(linearVelocity) * LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); _filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * translation; _lastHeadTranslation = translation; _headTranslation = _filteredHeadTranslation; } else { _headTranslation = translation; } // Compute relative rotation rotation = glm::inverse(_referenceRotation) * rotation; if (isFiltering) { glm::quat r = rotation * glm::inverse(_headRotation); float theta = 2 * acos(r.w); glm::vec3 angularVelocity; if (theta > EPSILON) { float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); angularVelocity = theta / _averageMessageTime * glm::vec3(r.x, r.y, r.z) / rMag; } else { angularVelocity = glm::vec3(0, 0, 0); } const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f; _headRotation = safeMix(_headRotation, rotation, glm::clamp(glm::length(angularVelocity) * ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f)); } else { _headRotation = rotation; } // Translate DDE coefficients to Faceshift compatible coefficients for (int i = 0; i < NUM_EXPRESSIONS; i += 1) { _coefficients[DDE_TO_FACESHIFT_MAPPING[i]] = packet.expressions[i]; } // Use EyeBlink values to control both EyeBlink and EyeOpen static const float RELAXED_EYE_VALUE = 0.1f; float leftEye = _coefficients[_leftBlinkIndex]; float rightEye = _coefficients[_rightBlinkIndex]; if (isFiltering) { const float BLINK_VELOCITY_FILTER_STRENGTH = 0.3f; float velocity = fabs(leftEye - _lastLeftEyeBlink) / _averageMessageTime; float velocityFilter = glm::clamp(velocity * BLINK_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); _filteredLeftEyeBlink = velocityFilter * leftEye + (1.0f - velocityFilter) * _filteredLeftEyeBlink; _lastLeftEyeBlink = leftEye; leftEye = _filteredLeftEyeBlink; velocity = fabs(rightEye - _lastRightEyeBlink) / _averageMessageTime; velocityFilter = glm::clamp(velocity * BLINK_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f); _filteredRightEyeBlink = velocityFilter * rightEye + (1.0f - velocityFilter) * _filteredRightEyeBlink; _lastRightEyeBlink = rightEye; rightEye = _filteredRightEyeBlink; } if (leftEye > RELAXED_EYE_VALUE) { _coefficients[_leftBlinkIndex] = leftEye - RELAXED_EYE_VALUE; _coefficients[_leftEyeOpenIndex] = 0.0f; } else { _coefficients[_leftBlinkIndex] = 0.0f; _coefficients[_leftEyeOpenIndex] = RELAXED_EYE_VALUE - leftEye; } if (rightEye > RELAXED_EYE_VALUE) { _coefficients[_rightBlinkIndex] = rightEye - RELAXED_EYE_VALUE; _coefficients[_rightEyeOpenIndex] = 0.0f; } else { _coefficients[_rightBlinkIndex] = 0.0f; _coefficients[_rightEyeOpenIndex] = RELAXED_EYE_VALUE - rightEye; } // Use BrowsU_C to control both brows' up and down _coefficients[_browDownLeftIndex] = -_coefficients[_browUpCenterIndex]; _coefficients[_browDownRightIndex] = -_coefficients[_browUpCenterIndex]; _coefficients[_browUpLeftIndex] = _coefficients[_browUpCenterIndex]; _coefficients[_browUpRightIndex] = _coefficients[_browUpCenterIndex]; // Offset jaw open coefficient static const float JAW_OPEN_THRESHOLD = 0.16f; _coefficients[_jawOpenIndex] = _coefficients[_jawOpenIndex] - JAW_OPEN_THRESHOLD; // Offset smile coefficients static const float SMILE_THRESHOLD = 0.18f; _coefficients[_mouthSmileLeftIndex] = _coefficients[_mouthSmileLeftIndex] - SMILE_THRESHOLD; _coefficients[_mouthSmileRightIndex] = _coefficients[_mouthSmileRightIndex] - SMILE_THRESHOLD; // Scale all coefficients for (int i = 0; i < NUM_EXPRESSIONS; i += 1) { _blendshapeCoefficients[i] = glm::clamp(DDE_COEFFICIENT_SCALES[i] * _coefficients[i], 0.0f, 1.0f); } // Calculate average frame time const float FRAME_AVERAGING_FACTOR = 0.99f; quint64 usecsNow = usecTimestampNow(); if (_lastMessageReceived != 0) { _averageMessageTime = FRAME_AVERAGING_FACTOR * _averageMessageTime + (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f; } _lastMessageReceived = usecsNow; FaceTracker::countFrame(); } else { qCWarning(interfaceapp) << "DDE Face Tracker: Decode error"; } _lastReceiveTimestamp = usecTimestampNow(); }