// Version of voxel distributor that sends the deepest LOD level at once void deepestLevelVoxelDistributor(NodeList* nodeList, NodeList::iterator& node, VoxelNodeData* nodeData, bool viewFrustumChanged) { pthread_mutex_lock(&::treeLock); int truePacketsSent = 0; int trueBytesSent = 0; // 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 = LOW_RES_MONO && nodeData->getWantLowResMoving() && viewFrustumChanged ? false : nodeData->getWantColor(); // 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 (wantColor != nodeData->getCurrentPacketIsColor()) { if (nodeData->isPacketWaiting()) { if (::debugVoxelSending) { printf("wantColor=%s --- SENDING PARTIAL PACKET! nodeData->getCurrentPacketIsColor()=%s\n", debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor())); } handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent); } else { if (::debugVoxelSending) { printf("wantColor=%s --- FIXING HEADER! nodeData->getCurrentPacketIsColor()=%s\n", debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor())); } nodeData->resetVoxelPacket(); } } if (::debugVoxelSending) { printf("wantColor=%s getCurrentPacketIsColor()=%s, viewFrustumChanged=%s, getWantLowResMoving()=%s\n", debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()), debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving())); } const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL; if (::debugVoxelSending) { printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n", debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()), debug::valueOf(nodeData->getViewSent()) ); } // 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()) { uint64_t now = usecTimestampNow(); if (::debugVoxelSending) { printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n", debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty())); if (nodeData->getLastTimeBagEmpty() > 0) { float elapsedSceneSend = (now - nodeData->getLastTimeBagEmpty()) / 1000000.0f; if (viewFrustumChanged) { printf("viewFrustumChanged resetting after elapsed time to send scene = %f seconds", elapsedSceneSend); } else { printf("elapsed time to send scene = %f seconds", elapsedSceneSend); } printf(" [occlusionCulling:%s, wantDelta:%s, wantColor:%s ]\n", debug::valueOf(nodeData->getWantOcclusionCulling()), debug::valueOf(wantDelta), debug::valueOf(wantColor)); } } // if our view has changed, we need to reset these things... if (viewFrustumChanged) { nodeData->nodeBag.deleteAll(); nodeData->map.erase(); } if (!viewFrustumChanged && !nodeData->getWantDelta()) { // only set our last sent time if we weren't resetting due to frustum change uint64_t now = usecTimestampNow(); nodeData->setLastTimeBagEmpty(now); } nodeData->stats.sceneCompleted(); if (::displayVoxelStats) { nodeData->stats.printDebugDetails(); } // This is the start of "resending" the scene. nodeData->nodeBag.insert(serverTree.rootNode); // start tracking our stats bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging(); nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, ::serverTree.rootNode); } // If we have something in our nodeBag, then turn them into packets and send them out... if (!nodeData->nodeBag.isEmpty()) { static unsigned char tempOutputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static int bytesWritten = 0; int packetsSentThisInterval = 0; uint64_t start = usecTimestampNow(); bool shouldSendEnvironments = shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS); while (packetsSentThisInterval < PACKETS_PER_CLIENT_PER_INTERVAL - (shouldSendEnvironments ? 1 : 0)) { // Check to see if we're taking too long, and if so bail early... uint64_t now = usecTimestampNow(); long elapsedUsec = (now - start); long elapsedUsecPerPacket = (truePacketsSent == 0) ? 0 : (elapsedUsec / truePacketsSent); long usecRemaining = (VOXEL_SEND_INTERVAL_USECS - elapsedUsec); if (elapsedUsecPerPacket + SENDING_TIME_TO_SPARE > usecRemaining) { if (::debugVoxelSending) { printf("packetLoop() usecRemaining=%ld bailing early took %ld usecs to generate %d bytes in %d packets (%ld usec avg), %d nodes still to send\n", usecRemaining, elapsedUsec, trueBytesSent, truePacketsSent, elapsedUsecPerPacket, nodeData->nodeBag.count()); } break; } if (!nodeData->nodeBag.isEmpty()) { VoxelNode* subTree = nodeData->nodeBag.extract(); bool wantOcclusionCulling = nodeData->getWantOcclusionCulling(); CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP; int boundaryLevelAdjust = viewFrustumChanged && nodeData->getWantLowResMoving() ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST; bool isFullScene = (!viewFrustumChanged || !nodeData->getWantDelta()) && nodeData->getViewFrustumJustStoppedChanging(); EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor, WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum, wantOcclusionCulling, coverageMap, boundaryLevelAdjust, nodeData->getLastTimeBagEmpty(), isFullScene, &nodeData->stats); nodeData->stats.encodeStarted(); bytesWritten = serverTree.encodeTreeBitstream(subTree, &tempOutputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeData->nodeBag, params); nodeData->stats.encodeStopped(); if (nodeData->getAvailable() >= bytesWritten) { nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } else { handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent); packetsSentThisInterval++; nodeData->resetVoxelPacket(); nodeData->writeToPacket(&tempOutputBuffer[0], bytesWritten); } } else { if (nodeData->isPacketWaiting()) { handlePacketSend(nodeList, node, nodeData, trueBytesSent, truePacketsSent); nodeData->resetVoxelPacket(); } packetsSentThisInterval = PACKETS_PER_CLIENT_PER_INTERVAL; // done for now, no nodes left } } // send the environment packet if (shouldSendEnvironments) { int numBytesPacketHeader = populateTypeAndVersion(tempOutputBuffer, PACKET_TYPE_ENVIRONMENT_DATA); int envPacketLength = numBytesPacketHeader; for (int i = 0; i < sizeof(environmentData) / sizeof(EnvironmentData); i++) { envPacketLength += environmentData[i].getBroadcastData(tempOutputBuffer + envPacketLength); } nodeList->getNodeSocket()->send(node->getActiveSocket(), tempOutputBuffer, envPacketLength); trueBytesSent += envPacketLength; truePacketsSent++; } uint64_t end = usecTimestampNow(); int elapsedmsec = (end - start)/1000; if (elapsedmsec > 100) { if (elapsedmsec > 1000) { int elapsedsec = (end - start)/1000000; printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets %d nodes still to send\n", elapsedsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); } else { printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n", elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); } } else if (::debugVoxelSending) { printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n", elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count()); } // 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); if (::debugVoxelSending) { nodeData->map.printStats(); } 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... pthread_mutex_unlock(&::treeLock); }