diff --git a/.gitignore b/.gitignore index df91e0ca7b..8d92fe770b 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,6 @@ interface/compiledResources # GPUCache interface/resources/GPUCache/* + +# package lock file for JSDoc tool +tools/jsdoc/package-lock.json diff --git a/BUILD_LINUX.md b/BUILD_LINUX.md index 417ed9b8de..0daef5ae05 100644 --- a/BUILD_LINUX.md +++ b/BUILD_LINUX.md @@ -11,7 +11,7 @@ Should you choose not to install Qt5 via a package manager that handles dependen ## Ubuntu 16.04 specific build guide ### Prepare environment - +hifiqt5.10.1 Install qt: ```bash wget http://debian.highfidelity.com/pool/h/hi/hifiqt5.10.1_5.10.1_amd64.deb diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 10b8d44545..1df901dd98 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -380,7 +380,7 @@ void Agent::executeScript() { using namespace recording; static const FrameType AVATAR_FRAME_TYPE = Frame::registerFrameType(AvatarData::FRAME_NAME); - Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [this, scriptedAvatar](Frame::ConstPointer frame) { + Frame::registerFrameHandler(AVATAR_FRAME_TYPE, [scriptedAvatar](Frame::ConstPointer frame) { auto recordingInterface = DependencyManager::get(); bool useFrameSkeleton = recordingInterface->getPlayerUseSkeletonModel(); diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 594f423838..1e2bd15429 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -48,8 +48,6 @@ private: void preDistributionProcessing() override; bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); } bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); } - void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {}; - bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; } DiffTraversal _traversal; EntityPriorityQueue _sendQueue; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 715e83f403..de49bd461c 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -304,23 +304,6 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* return numPackets; } -void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) { - // If we're starting a full scene, then definitely we want to empty the elementBag - if (isFullScene) { - nodeData->elementBag.deleteAll(); - } - - // This is the start of "resending" the scene. - bool dontRestartSceneOnMove = false; // this is experimental - if (dontRestartSceneOnMove) { - if (nodeData->elementBag.isEmpty()) { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } - } else { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } -} - /// Version of octree element distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { OctreeServer::didPacketDistributor(this); @@ -366,16 +349,8 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // the current view frustum for things to send. if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { - // if our view has changed, we need to reset these things... - if (viewFrustumChanged) { - if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) { - nodeData->dumpOutOfView(); - } - } - // track completed scenes and send out the stats packet accordingly nodeData->stats.sceneCompleted(); - nodeData->setLastRootTimestamp(_myServer->getOctree()->getRoot()->getLastChanged()); _myServer->getOctree()->releaseSceneEncodeData(&nodeData->extraEncodeData); // TODO: add these to stats page @@ -389,111 +364,74 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; - nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE); // start tracking our stats nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot()); - - preStartNewScene(nodeData, isFullScene); } - // If we have something in our elementBag, then turn them into packets and send them out... - if (shouldTraverseAndSend(nodeData)) { - quint64 start = usecTimestampNow(); + quint64 start = usecTimestampNow(); - _myServer->getOctree()->withReadLock([&]{ - traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); - }); + _myServer->getOctree()->withReadLock([&]{ + traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + }); - // Here's where we can/should allow the server to send other data... - // send the environment packet - // TODO: should we turn this into a while loop to better handle sending multiple special packets - if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { - int specialPacketsSent = 0; - int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); - nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed - _truePacketsSent += specialPacketsSent; - _trueBytesSent += specialBytesSent; - _packetsSentThisInterval += specialPacketsSent; + // Here's where we can/should allow the server to send other data... + // send the environment packet + // TODO: should we turn this into a while loop to better handle sending multiple special packets + if (_myServer->hasSpecialPacketsToSend(node) && !nodeData->isShuttingDown()) { + int specialPacketsSent = 0; + int specialBytesSent = _myServer->sendSpecialPackets(node, nodeData, specialPacketsSent); + nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed + _truePacketsSent += specialPacketsSent; + _trueBytesSent += specialBytesSent; + _packetsSentThisInterval += specialPacketsSent; - _totalPackets += specialPacketsSent; - _totalBytes += specialBytesSent; + _totalPackets += specialPacketsSent; + _totalBytes += specialBytesSent; - _totalSpecialPackets += specialPacketsSent; - _totalSpecialBytes += specialBytesSent; + _totalSpecialPackets += specialPacketsSent; + _totalSpecialBytes += specialBytesSent; + } + + // calculate max number of packets that can be sent during this interval + int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); + int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + + // Re-send packets that were nacked by the client + while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { + const NLPacket* packet = nodeData->getNextNackedPacket(); + if (packet) { + DependencyManager::get()->sendUnreliablePacket(*packet, *node); + int numBytes = packet->getDataSize(); + _truePacketsSent++; + _trueBytesSent += numBytes; + _packetsSentThisInterval++; + + _totalPackets++; + _totalBytes += numBytes; + _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); } + } - // calculate max number of packets that can be sent during this interval - int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); - int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); + quint64 end = usecTimestampNow(); + int elapsedmsec = (end - start) / USECS_PER_MSEC; + OctreeServer::trackLoopTime(elapsedmsec); - // Re-send packets that were nacked by the client - while (nodeData->hasNextNackedPacket() && _packetsSentThisInterval < maxPacketsPerInterval) { - const NLPacket* packet = nodeData->getNextNackedPacket(); - if (packet) { - DependencyManager::get()->sendUnreliablePacket(*packet, *node); - int numBytes = packet->getDataSize(); - _truePacketsSent++; - _trueBytesSent += numBytes; - _packetsSentThisInterval++; + // if we've sent everything, then we want to remember that we've sent all + // the octree elements from the current view frustum + if (!hasSomethingToSend(nodeData)) { + nodeData->setViewSent(true); - _totalPackets++; - _totalBytes += numBytes; - _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); - } + // If this was a full scene then make sure we really send out a stats packet at this point so that + // the clients will know the scene is stable + if (isFullScene) { + nodeData->stats.sceneCompleted(); + handlePacketSend(node, nodeData, true); } - - quint64 end = usecTimestampNow(); - int elapsedmsec = (end - start) / USECS_PER_MSEC; - OctreeServer::trackLoopTime(elapsedmsec); - - // if after sending packets we've emptied our bag, then we want to remember that we've sent all - // the octree elements from the current view frustum - if (!hasSomethingToSend(nodeData)) { - nodeData->updateLastKnownViewFrustum(); - nodeData->setViewSent(true); - - // If this was a full scene then make sure we really send out a stats packet at this point so that - // the clients will know the scene is stable - if (isFullScene) { - nodeData->stats.sceneCompleted(); - handlePacketSend(node, nodeData, true); - } - } - - } // end if bag wasn't empty, and so we sent stuff... + } return _truePacketsSent; } -bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { - bool somethingToSend = false; - OctreeQueryNode* nodeData = static_cast(params.nodeData); - if (!nodeData->elementBag.isEmpty()) { - quint64 encodeStart = usecTimestampNow(); - quint64 lockWaitStart = encodeStart; - - _myServer->getOctree()->withReadLock([&]{ - OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart)); - - OctreeElementPointer subTree = nodeData->elementBag.extract(); - if (subTree) { - // NOTE: this is where the tree "contents" are actually packed - nodeData->stats.encodeStarted(); - _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); - nodeData->stats.encodeStopped(); - - somethingToSend = true; - } - }); - - OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); - } else { - OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME); - OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); - } - return somethingToSend; -} - void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // calculate max number of packets that can be sent during this interval int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); @@ -502,21 +440,12 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre int extraPackingAttempts = 0; // init params once outside the while loop - int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - int boundaryLevelAdjust = boundaryLevelAdjustClient + - (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - float octreeSizeScale = nodeData->getOctreeSizeScale(); - EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, - viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, - isFullScene, nodeData); + EncodeBitstreamParams params(WANT_EXISTS_BITS, nodeData); // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { _myServer->trackSend(dataID, dataEdited, _nodeUuid); }; nodeData->copyCurrentViewFrustum(params.viewFrustum); - if (viewFrustumChanged) { - nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); - } bool somethingToSend = true; // assume we have something bool hadSomething = hasSomethingToSend(nodeData); @@ -536,8 +465,8 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre extraPackingAttempts++; } - // If the bag had contents but is now empty then we know we've sent the entire scene. - bool completedScene = hadSomething && nodeData->elementBag.isEmpty(); + // If we had something to send, but now we don't, then we know we've sent the entire scene. + bool completedScene = hadSomething; if (completedScene || lastNodeDidntFit) { // we probably want to flush what has accumulated in nodeData but: // do we have more data to send? and is there room? diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 220952e209..91c0ec7adc 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -54,7 +54,7 @@ protected: virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); - virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); + virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) = 0; OctreePacketData _packetData; QWeakPointer _node; @@ -63,14 +63,12 @@ protected: private: /// Called before a packetDistributor pass to allow for pre-distribution processing - virtual void preDistributionProcessing() {}; + virtual void preDistributionProcessing() = 0; int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); - virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); } - virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); } - virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene); - virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); } + virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) = 0; + virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) = 0; int _truePacketsSent { 0 }; // available for debug stats int _trueBytesSent { 0 }; // available for debug stats diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 0dbd24fe9c..fad2c1f026 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -876,10 +876,6 @@ void OctreeServer::parsePayload() { } } -OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePointer& node) { - return std::unique_ptr(new OctreeSendThread(this, node)); -} - OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) { auto sendThread = newSendThread(node); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index e7efc731f2..b25e537d70 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -174,7 +174,7 @@ protected: void beginRunning(QByteArray replaceData); UniqueSendThread createSendThread(const SharedNodePointer& node); - virtual UniqueSendThread newSendThread(const SharedNodePointer& node); + virtual UniqueSendThread newSendThread(const SharedNodePointer& node) = 0; int _argc; const char** _argv; diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 7d0b538f6e..9f24036e92 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -451,11 +451,12 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect return SharedNodePointer(); } - QUuid hintNodeID; + QUuid existingNodeID; // in case this is a node that's failing to connect // double check we don't have the same node whose sockets match exactly already in the list limitedNodeList->eachNodeBreakable([&](const SharedNodePointer& node){ + if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) { // we have a node that already has these exact sockets - this can occur if a node // is failing to connect to the domain @@ -465,15 +466,20 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect auto existingNodeData = static_cast(node->getLinkedData()); if (existingNodeData->getUsername() == username) { - hintNodeID = node->getUUID(); + qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID(); + existingNodeID = node->getUUID(); return false; } } return true; }); + if (!existingNodeID.isNull()) { + limitedNodeList->killNodeWithUUID(existingNodeID); + } + // add the connecting node (or re-use the matched one from eachNodeBreakable above) - SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID); + SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection); // set the edit rights for this user newNode->setPermissions(userPerms); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b0d3d06994..b054b5cf8e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2903,7 +2903,7 @@ void DomainServer::updateReplicatedNodes() { } auto nodeList = DependencyManager::get(); - nodeList->eachMatchingNode([this](const SharedNodePointer& otherNode) -> bool { + nodeList->eachMatchingNode([](const SharedNodePointer& otherNode) -> bool { return otherNode->getType() == NodeType::Agent; }, [this](const SharedNodePointer& otherNode) { auto shouldReplicate = shouldReplicateNode(*otherNode); diff --git a/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx new file mode 100644 index 0000000000..d94ce30429 Binary files /dev/null and b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx differ diff --git a/interface/resources/qml/hifi/LetterboxMessage.qml b/interface/resources/qml/hifi/LetterboxMessage.qml index d88bf1b147..8a18d88842 100644 --- a/interface/resources/qml/hifi/LetterboxMessage.qml +++ b/interface/resources/qml/hifi/LetterboxMessage.qml @@ -30,6 +30,16 @@ Item { color: "black" opacity: 0.5 radius: popupRadius + + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + acceptedButtons: Qt.LeftButton; + propagateComposedEvents: false; + onClicked: { + letterbox.visible = false; + } + } } Rectangle { id: textContainer; @@ -38,6 +48,14 @@ Item { anchors.centerIn: parent radius: popupRadius color: "white" + + // Prevent dismissing the popup by clicking on the textContainer + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + propagateComposedEvents: false; + } + Item { id: contentContainer width: parent.width - 50 @@ -135,11 +153,4 @@ Item { } } } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - letterbox.visible = false; - } - } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 457fe84c3a..57c1163eb8 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -14,8 +14,8 @@ FocusScope { id: tabletMenu objectName: "tabletMenu" - width: 480 - height: 720 + width: parent.width + height: parent.height property var rootMenu: Menu { objectName:"rootMenu" } property var point: Qt.point(50, 50); diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 4fa29de9a3..4d36a57793 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -16,6 +16,8 @@ import "." Item { id: root anchors.fill: parent + width: parent.width + height: parent.height objectName: "tabletMenuHandlerItem" StackView { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuView.qml b/interface/resources/qml/hifi/tablet/TabletMenuView.qml index 2d4d31b9aa..b632a17e57 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuView.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuView.qml @@ -40,10 +40,10 @@ FocusScope { id: listView x: 0 y: 0 - width: 480 - height: 720 - contentWidth: 480 - contentHeight: 720 + width: parent.width + height: parent.height + contentWidth: parent.width + contentHeight: parent.height objectName: "menuList" topMargin: hifi.dimensions.menuPadding.y diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8e4578123d..6c2d79f54c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1100,10 +1100,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo auto audioScriptingInterface = DependencyManager::get(); auto myAvatarPosition = DependencyManager::get()->getMyAvatar()->getWorldPosition(); float distance = glm::distance(myAvatarPosition, position); - bool shouldMute = !audioClient->isMuted() && (distance < radius); - if (shouldMute) { - audioClient->toggleMute(); + if (distance < radius) { + audioClient->setMuted(true); audioScriptingInterface->environmentMuted(); } }); @@ -1528,7 +1527,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (state) { if (action == controller::toInt(controller::Action::TOGGLE_MUTE)) { - DependencyManager::get()->toggleMute(); + auto audioClient = DependencyManager::get(); + audioClient->setMuted(!audioClient->isMuted()); } else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) { cycleCamera(); } else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) { @@ -3476,7 +3476,8 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_M: if (isMeta) { - DependencyManager::get()->toggleMute(); + auto audioClient = DependencyManager::get(); + audioClient->setMuted(!audioClient->isMuted()); } break; @@ -5137,7 +5138,7 @@ void Application::update(float deltaTime) { if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) { if (_lastFaceTrackerUpdate > 0 && ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) { - audioClient->toggleMute(); + audioClient->setMuted(true); _lastFaceTrackerUpdate = 0; } } else { diff --git a/interface/src/Crashpad.cpp b/interface/src/Crashpad.cpp index 8ed3fc23bd..27da619af1 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/Crashpad.cpp @@ -31,10 +31,41 @@ using namespace crashpad; static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; +static std::wstring gIPCPipe; + extern QString qAppFileName(); // crashpad::AnnotationList* crashpadAnnotations { nullptr }; +#include + +LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + static const DWORD EXTERNAL_EXCEPTION_CODE{ 0xe06d7363 }; + static const DWORD HEAP_CORRUPTION_CODE{ 0xc0000374 }; + + auto exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + if (exceptionCode == EXTERNAL_EXCEPTION_CODE) { + return EXCEPTION_CONTINUE_SEARCH; + } + + if (exceptionCode == HEAP_CORRUPTION_CODE) { + qCritical() << "VectoredExceptionHandler: Heap corruption:" << QString::number(exceptionCode, 16); + + CrashpadClient client; + if (gIPCPipe.length()) { + bool rc = client.SetHandlerIPCPipe(gIPCPipe); + qCritical() << "SetHandlerIPCPipe = " << rc; + } else { + qCritical() << "No IPC Pipe was previously defined for crash handler."; + } + qCritical() << "Calling DumpAndCrash()"; + client.DumpAndCrash(pExceptionInfo); + return EXCEPTION_CONTINUE_SEARCH; + } + + return EXCEPTION_CONTINUE_SEARCH; +} + bool startCrashHandler() { if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { return false; @@ -76,7 +107,12 @@ bool startCrashHandler() { // Enable automated uploads. database->GetSettings()->SetUploadsEnabled(true); - return client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); + bool result = client.StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); + gIPCPipe = client.GetHandlerIPCPipe(); + + AddVectoredExceptionHandler(0, vectoredExceptionHandler); + + return result; } void setCrashAnnotation(std::string name, std::string value) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 83199f5ba7..4384635147 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -750,32 +750,32 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunction); connect(action, &QAction::triggered, qApp, []() { crash::pureVirtualCall(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashPureVirtualFunctionThreaded); - connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::pureVirtualCall(); }); }); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::pureVirtualCall).join(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFree); connect(action, &QAction::triggered, qApp, []() { crash::doubleFree(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashDoubleFreeThreaded); - connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doubleFree(); }); }); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doubleFree).join(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbort); connect(action, &QAction::triggered, qApp, []() { crash::doAbort(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashAbortThreaded); - connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::doAbort(); }); }); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::doAbort).join(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereference); connect(action, &QAction::triggered, qApp, []() { crash::nullDeref(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNullDereferenceThreaded); - connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::nullDeref(); }); }); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::nullDeref).join(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccess); connect(action, &QAction::triggered, qApp, []() { crash::outOfBoundsVectorCrash(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOutOfBoundsVectorAccessThreaded); - connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::outOfBoundsVectorCrash(); }); }); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::outOfBoundsVectorCrash).join(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFault); connect(action, &QAction::triggered, qApp, []() { crash::newFault(); }); action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); - connect(action, &QAction::triggered, qApp, []() { std::thread([]() { crash::newFault(); }); }); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); // Developer > Log... addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index be9b4280f7..387900b2ae 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -50,110 +50,162 @@ float Audio::loudnessToLevel(float loudness) { Audio::Audio() : _devices(_contextIsHMD) { auto client = DependencyManager::get().data(); - connect(client, &AudioClient::muteToggled, this, &Audio::onMutedChanged); - connect(client, &AudioClient::noiseReductionChanged, this, &Audio::onNoiseReductionChanged); + connect(client, &AudioClient::muteToggled, this, &Audio::setMuted); + connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction); connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged); - connect(client, &AudioClient::inputVolumeChanged, this, &Audio::onInputVolumeChanged); + connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); } bool Audio::startRecording(const QString& filepath) { auto client = DependencyManager::get().data(); - return client->startRecording(filepath); + return resultWithWriteLock([&] { + return client->startRecording(filepath); + }); } bool Audio::getRecording() { auto client = DependencyManager::get().data(); - return client->getRecording(); + return resultWithReadLock([&] { + return client->getRecording(); + }); } void Audio::stopRecording() { auto client = DependencyManager::get().data(); - client->stopRecording(); + withWriteLock([&] { + client->stopRecording(); + }); +} + +bool Audio::isMuted() const { + return resultWithReadLock([&] { + return _isMuted; + }); } void Audio::setMuted(bool isMuted) { - if (_isMuted != isMuted) { - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "toggleMute"); + bool changed = false; + withWriteLock([&] { + if (_isMuted != isMuted) { + _isMuted = isMuted; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setMuted", Q_ARG(bool, isMuted), Q_ARG(bool, false)); + changed = true; + } + }); + if (changed) { + emit mutedChanged(isMuted); } } -void Audio::onMutedChanged() { - bool isMuted = DependencyManager::get()->isMuted(); - if (_isMuted != isMuted) { - _isMuted = isMuted; - emit mutedChanged(_isMuted); - } +bool Audio::noiseReductionEnabled() const { + return resultWithReadLock([&] { + return _enableNoiseReduction; + }); } void Audio::enableNoiseReduction(bool enable) { - if (_enableNoiseReduction != enable) { - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable)); - enableNoiseReductionSetting.set(enable); + bool changed = false; + withWriteLock([&] { + if (_enableNoiseReduction != enable) { + _enableNoiseReduction = enable; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setNoiseReduction", Q_ARG(bool, enable), Q_ARG(bool, false)); + enableNoiseReductionSetting.set(enable); + changed = true; + } + }); + if (changed) { + emit noiseReductionChanged(enable); } } -void Audio::onNoiseReductionChanged() { - bool noiseReductionEnabled = DependencyManager::get()->isNoiseReductionEnabled(); - if (_enableNoiseReduction != noiseReductionEnabled) { - _enableNoiseReduction = noiseReductionEnabled; - emit noiseReductionChanged(_enableNoiseReduction); - } +float Audio::getInputVolume() const { + return resultWithReadLock([&] { + return _inputVolume; + }); } void Audio::setInputVolume(float volume) { // getInputVolume will not reflect changes synchronously, so clamp beforehand volume = glm::clamp(volume, 0.0f, 1.0f); - if (_inputVolume != volume) { - auto client = DependencyManager::get().data(); - QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume)); + bool changed = false; + withWriteLock([&] { + if (_inputVolume != volume) { + _inputVolume = volume; + auto client = DependencyManager::get().data(); + QMetaObject::invokeMethod(client, "setInputVolume", Q_ARG(float, volume), Q_ARG(bool, false)); + changed = true; + } + }); + if (changed) { + emit inputVolumeChanged(volume); } } -void Audio::onInputVolumeChanged(float volume) { - if (_inputVolume != volume) { - _inputVolume = volume; - emit inputVolumeChanged(_inputVolume); - } +float Audio::getInputLevel() const { + return resultWithReadLock([&] { + return _inputLevel; + }); } void Audio::onInputLoudnessChanged(float loudness) { float level = loudnessToLevel(loudness); - - if (_inputLevel != level) { - _inputLevel = level; - emit inputLevelChanged(_inputLevel); + bool changed = false; + withWriteLock([&] { + if (_inputLevel != level) { + _inputLevel = level; + changed = true; + } + }); + if (changed) { + emit inputLevelChanged(level); } } QString Audio::getContext() const { - return _contextIsHMD ? Audio::HMD : Audio::DESKTOP; + return resultWithReadLock([&] { + return _contextIsHMD ? Audio::HMD : Audio::DESKTOP; + }); } void Audio::onContextChanged() { + bool changed = false; bool isHMD = qApp->isHMDMode(); - if (_contextIsHMD != isHMD) { - _contextIsHMD = isHMD; - emit contextChanged(getContext()); + withWriteLock([&] { + if (_contextIsHMD != isHMD) { + _contextIsHMD = isHMD; + changed = true; + } + }); + if (changed) { + emit contextChanged(isHMD ? Audio::HMD : Audio::DESKTOP); } } void Audio::setReverb(bool enable) { - DependencyManager::get()->setReverb(enable); + withWriteLock([&] { + DependencyManager::get()->setReverb(enable); + }); } void Audio::setReverbOptions(const AudioEffectOptions* options) { - DependencyManager::get()->setReverbOptions(options); + withWriteLock([&] { + DependencyManager::get()->setReverbOptions(options); + }); } void Audio::setInputDevice(const QAudioDeviceInfo& device, bool isHMD) { - _devices.chooseInputDevice(device, isHMD); + withWriteLock([&] { + _devices.chooseInputDevice(device, isHMD); + }); } void Audio::setOutputDevice(const QAudioDeviceInfo& device, bool isHMD) { - _devices.chooseOutputDevice(device, isHMD); + withWriteLock([&] { + _devices.chooseOutputDevice(device, isHMD); + }); } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 0f0043510c..b4e63b80c5 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -17,10 +17,11 @@ #include "AudioEffectOptions.h" #include "SettingHandle.h" #include "AudioFileWav.h" +#include namespace scripting { -class Audio : public AudioScriptingInterface { +class Audio : public AudioScriptingInterface, protected ReadWriteLockable { Q_OBJECT SINGLETON_DEPENDENCY @@ -40,16 +41,13 @@ public: virtual ~Audio() {} - bool isMuted() const { return _isMuted; } - bool noiseReductionEnabled() const { return _enableNoiseReduction; } - float getInputVolume() const { return _inputVolume; } - float getInputLevel() const { return _inputLevel; } + bool isMuted() const; + bool noiseReductionEnabled() const; + float getInputVolume() const; + float getInputLevel() const; QString getContext() const; - void setMuted(bool muted); - void enableNoiseReduction(bool enable); void showMicMeter(bool show); - void setInputVolume(float volume); Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); @@ -72,9 +70,9 @@ public slots: void onContextChanged(); private slots: - void onMutedChanged(); - void onNoiseReductionChanged(); - void onInputVolumeChanged(float volume); + void setMuted(bool muted); + void enableNoiseReduction(bool enable); + void setInputVolume(float volume); void onInputLoudnessChanged(float loudness); protected: diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 34a3630b78..ee615cde20 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -188,7 +188,7 @@ void AudioDeviceList::onDeviceChanged(const QAudioDeviceInfo& device, bool isHMD for (auto i = 0; i < _devices.size(); ++i) { std::shared_ptr device = _devices[i]; - bool &isSelected = isHMD ? device->selectedHMD : device->selectedDesktop; + bool& isSelected = isHMD ? device->selectedHMD : device->selectedDesktop; if (isSelected && device->info != selectedDevice) { isSelected = false; } else if (device->info == selectedDevice) { @@ -259,7 +259,7 @@ void AudioDeviceList::onDevicesChanged(const QList& devices) { foreach(const QAudioDeviceInfo& deviceInfo, devices) { for (bool isHMD : {false, true}) { - auto &backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; + auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName; if (deviceInfo.deviceName() == backupSelectedDeviceName) { QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; selectedDevice = deviceInfo; @@ -278,7 +278,7 @@ void AudioDeviceList::onDevicesChanged(const QList& devices) { for (bool isHMD : {false, true}) { QAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice; - bool &isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; + bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop; if (!selectedDevice.isNull()) { isSelected = (device.info == selectedDevice); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index dd74358064..8a8dc61ba8 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -531,8 +531,8 @@ private slots: signals: /**jsdoc - * Triggered when you change the domain you're visiting. Warning: Is not emitted if you go to domain that - * isn't running. + * Triggered when you change the domain you're visiting. Warning: Is not emitted if you go to a domain + * that isn't running. * @function Window.domainChanged * @param {string} domainURL - The domain's URL. * @returns {Signal} diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index e2dae92a94..f643719a2e 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -757,7 +757,7 @@ void AudioClient::Gate::flush() { void AudioClient::handleNoisyMutePacket(QSharedPointer message) { if (!_muted) { - toggleMute(); + setMuted(true); // have the audio scripting interface emit a signal to say we were muted by the mixer emit mutedByMixer(); @@ -1384,15 +1384,21 @@ void AudioClient::sendMuteEnvironmentPacket() { } } -void AudioClient::toggleMute() { - _muted = !_muted; - emit muteToggled(); +void AudioClient::setMuted(bool muted, bool emitSignal) { + if (_muted != muted) { + _muted = muted; + if (emitSignal) { + emit muteToggled(_muted); + } + } } -void AudioClient::setNoiseReduction(bool enable) { +void AudioClient::setNoiseReduction(bool enable, bool emitSignal) { if (_isNoiseGateEnabled != enable) { _isNoiseGateEnabled = enable; - emit noiseReductionChanged(); + if (emitSignal) { + emit noiseReductionChanged(_isNoiseGateEnabled); + } } } @@ -2018,9 +2024,11 @@ void AudioClient::startThread() { moveToNewNamedThread(this, "Audio Thread", [this] { start(); }); } -void AudioClient::setInputVolume(float volume) { +void AudioClient::setInputVolume(float volume, bool emitSignal) { if (_audioInput && volume != (float)_audioInput->volume()) { _audioInput->setVolume(volume); - emit inputVolumeChanged(_audioInput->volume()); + if (emitSignal) { + emit inputVolumeChanged(_audioInput->volume()); + } } } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3bfbdb49ce..9ee7bcfeba 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -189,13 +189,13 @@ public slots: void reset(); void audioMixerKilled(); - void toggleMute(); + void setMuted(bool muted, bool emitSignal = true); bool isMuted() { return _muted; } virtual bool setIsStereoInput(bool stereo) override; virtual bool isStereoInput() override { return _isStereoInput; } - void setNoiseReduction(bool isNoiseGateEnabled); + void setNoiseReduction(bool isNoiseGateEnabled, bool emitSignal = true); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } bool getLocalEcho() { return _shouldEchoLocally; } @@ -218,7 +218,7 @@ public slots: bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName); float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; } - void setInputVolume(float volume); + void setInputVolume(float volume, bool emitSignal = true); void setReverb(bool reverb); void setReverbOptions(const AudioEffectOptions* options); @@ -229,8 +229,8 @@ public slots: signals: void inputVolumeChanged(float volume); - void muteToggled(); - void noiseReductionChanged(); + void muteToggled(bool muted); + void noiseReductionChanged(bool noiseReductionEnabled); void mutedByMixer(); void inputReceived(const QByteArray& inputSamples); void inputLoudnessChanged(float loudness); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f31ed4e238..f647082d73 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -278,7 +278,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) _webSurface->setMaxFps(DEFAULT_MAX_FPS); - QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [this](QQmlContext* surfaceContext) { + QObject::connect(_webSurface.data(), &OffscreenQmlSurface::rootContextCreated, [](QQmlContext* surfaceContext) { // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. surfaceContext->setContextProperty("desktop", QVariant()); // Let us interact with the keyboard diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index de98c1a47a..0557bbe5ad 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -120,7 +120,6 @@ public: void markAsChangedOnServer(); quint64 getLastChangedOnServer() const; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; virtual OctreeElement::AppendState appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f78168b05d..4638b46437 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -695,8 +695,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @typedef {object} Entities.EntityProperties-Material * @property {string} materialURL="" - URL to a {@link MaterialResource}. If you append ?name to the URL, the * material with that name in the {@link MaterialResource} will be applied to the entity.
- * Alternatively, set the property value to "userData" to use the {@link Entities.EntityProperties|userData} - * entity property to live edit the material resource values. + * Alternatively, set the property value to "materialData" to use the materialData property + * for the {@link MaterialResource} values. * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is * applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of * 0. @@ -710,6 +710,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * { x: 0, y: 0 }{ x: 1, y: 1 }. * @property {Vec2} materialMappingScale=1,1 - How much to scale the material within the parent's UV-space. * @property {number} materialMappingRot=0 - How much to rotate the material within the parent's UV-space, in degrees. + * @property {string} materialData="" - Used to store {@link MaterialResource} data as a JSON string. You can use + * JSON.parse() to parse the string into a JavaScript object which you can manipulate the properties of, and + * use JSON.stringify() to convert the object into a string to put in the property. * @example Color a sphere using a Material entity. * var entityID = Entities.addEntity({ * type: "Sphere", @@ -722,13 +725,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * var materialID = Entities.addEntity({ * type: "Material", * parentID: entityID, - * materialURL: "userData", + * materialURL: "materialData", * priority: 1, - * userData: JSON.stringify({ + * materialData: JSON.stringify({ + * materialVersion: 1, * materials: { * // Can only set albedo on a Shape entity. * // Value overrides entity's "color" property. - * albedo: [1.0, 0, 0] + * albedo: [1.0, 1.0, 0] // Yellow * } * }), * }); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 791c030fc8..a080801a0e 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -88,7 +88,6 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing - virtual bool getWantSVOfileVersions() const override { return true; } virtual PacketType expectedDataPacketType() const override { return PacketType::EntityData; } virtual bool handlesEditPacketType(PacketType packetType) const override; void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); @@ -107,11 +106,7 @@ public: virtual bool rootElementHasData() const override { return true; } - // the root at least needs to store the number of entities in the packet/buffer - virtual int minimumRequiredRootDataBytes() const override { return sizeof(uint16_t); } - virtual bool suppressEmptySubtrees() const override { return false; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const override; - virtual bool mustIncludeAllChildData() const override { return false; } virtual void update() override { update(true); } diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 1ae55bc333..cbcddfc57b 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -67,455 +67,6 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons } } -void EntityTreeElement::initializeExtraEncodeData(EncodeBitstreamParams& params) { - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - // Check to see if this element yet has encode data... if it doesn't create it - if (!extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData { new EntityTreeElementExtraEncodeData() }; - entityTreeElementExtraEncodeData->elementCompleted = (_entityItems.size() == 0); - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer child = getChildAtIndex(i); - if (!child) { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed - } else { - if (child->hasEntities()) { - entityTreeElementExtraEncodeData->childCompleted[i] = false; // HAS ENTITIES NEEDS ENCODING - } else { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // child doesn't have enities, it is completed - } - } - } - forEachEntity([&](EntityItemPointer entity) { - entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); - }); - - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } -} - -bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex]; - - // If we haven't completely sent the child yet, then we should include it - return !childCompleted; - } - - // I'm not sure this should ever happen, since we should have the extra encode data if we're considering - // the child data for this element - assert(false); - return false; -} - -bool EntityTreeElement::shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { - EntityTreeElementPointer childElement = getChildAtIndex(childIndex); - if (childElement->alreadyFullyEncoded(params)) { - return false; - } - - return true; // if we don't know otherwise than recurse! -} - -bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const { - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - // If we know that ALL subtrees below us have already been recursed, then we don't - // need to recurse this child. - return entityTreeElementExtraEncodeData->subtreeCompleted; - } - return false; -} - -void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - - if (extraEncodeData->contains(this)) { - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - if (childAppendState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->childCompleted[childIndex] = true; - } - } else { - assert(false); // this shouldn't happen! - } -} - - - - -void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) const { - const bool wantDebug = false; - - if (wantDebug) { - qCDebug(entities) << "EntityTreeElement::elementEncodeComplete() element:" << _cube; - } - - auto entityNodeData = static_cast(params.nodeData); - assert(entityNodeData); - - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes - assert(extraEncodeData->contains(this)); - - EntityTreeElementExtraEncodeDataPointer thisExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[this]); - - // Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion() - // which means, it's possible that our parent element hasn't finished encoding OUR data... so - // in this case, our children may be complete, and we should clean up their encode data... - // but not necessarily cleanup our own encode data... - // - // If we're really complete here's what must be true... - // 1) our own data must be complete - // 2) the data for all our immediate children must be complete. - // However, the following might also be the case... - // 1) it's ok for our child trees to not yet be fully encoded/complete... - // SO LONG AS... the our child's node is in the bag ready for encoding - - bool someChildTreeNotComplete = false; - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer childElement = getChildAtIndex(i); - if (childElement) { - - // why would this ever fail??? - // If we've encoding this element before... but we're coming back a second time in an attempt to - // encode our parent... this might happen. - if (extraEncodeData->contains(childElement.get())) { - EntityTreeElementExtraEncodeDataPointer childExtraEncodeData - = std::static_pointer_cast((*extraEncodeData)[childElement.get()]); - - if (wantDebug) { - qCDebug(entities) << "checking child: " << childElement->_cube; - qCDebug(entities) << " childElement->isLeaf():" << childElement->isLeaf(); - qCDebug(entities) << " childExtraEncodeData->elementCompleted:" << childExtraEncodeData->elementCompleted; - qCDebug(entities) << " childExtraEncodeData->subtreeCompleted:" << childExtraEncodeData->subtreeCompleted; - } - - if (childElement->isLeaf() && childExtraEncodeData->elementCompleted) { - if (wantDebug) { - qCDebug(entities) << " CHILD IS LEAF -- AND CHILD ELEMENT DATA COMPLETED!!!"; - } - childExtraEncodeData->subtreeCompleted = true; - } - - if (!childExtraEncodeData->elementCompleted || !childExtraEncodeData->subtreeCompleted) { - someChildTreeNotComplete = true; - } - } - } - } - - if (wantDebug) { - qCDebug(entities) << "for this element: " << _cube; - qCDebug(entities) << " WAS elementCompleted:" << thisExtraEncodeData->elementCompleted; - qCDebug(entities) << " WAS subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - } - - thisExtraEncodeData->subtreeCompleted = !someChildTreeNotComplete; - - if (wantDebug) { - qCDebug(entities) << " NOW elementCompleted:" << thisExtraEncodeData->elementCompleted; - qCDebug(entities) << " NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - - if (thisExtraEncodeData->subtreeCompleted) { - qCDebug(entities) << " YEAH!!!!! >>>>>>>>>>>>>> NOW subtreeCompleted:" << thisExtraEncodeData->subtreeCompleted; - } - } -} - -OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData* packetData, - EncodeBitstreamParams& params) const { - - OctreeElement::AppendState appendElementState = OctreeElement::COMPLETED; // assume the best... - - auto entityNodeData = static_cast(params.nodeData); - Q_ASSERT_X(entityNodeData, "EntityTreeElement::appendElementData", "expected params.nodeData not to be null"); - - // first, check the params.extraEncodeData to see if there's any partial re-encode data for this element - OctreeElementExtraEncodeData* extraEncodeData = &entityNodeData->extraEncodeData; - - EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData = NULL; - bool hadElementExtraData = false; - if (extraEncodeData && extraEncodeData->contains(this)) { - entityTreeElementExtraEncodeData = - std::static_pointer_cast((*extraEncodeData)[this]); - hadElementExtraData = true; - } else { - // if there wasn't one already, then create one - entityTreeElementExtraEncodeData.reset(new EntityTreeElementExtraEncodeData()); - entityTreeElementExtraEncodeData->elementCompleted = !hasContent(); - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - EntityTreeElementPointer child = getChildAtIndex(i); - if (!child) { - entityTreeElementExtraEncodeData->childCompleted[i] = true; // if no child exists, it is completed - } else { - if (child->hasEntities()) { - entityTreeElementExtraEncodeData->childCompleted[i] = false; - } else { - // if the child doesn't have enities, it is completed - entityTreeElementExtraEncodeData->childCompleted[i] = true; - } - } - } - forEachEntity([&](EntityItemPointer entity) { - entityTreeElementExtraEncodeData->entities.insert(entity->getEntityItemID(), entity->getEntityProperties(params)); - }); - } - - //assert(extraEncodeData); - //assert(extraEncodeData->contains(this)); - //entityTreeElementExtraEncodeData = std::static_pointer_cast((*extraEncodeData)[this]); - - LevelDetails elementLevel = packetData->startLevel(); - - // write our entities out... first determine which of the entities are in view based on our params - uint16_t numberOfEntities = 0; - uint16_t actualNumberOfEntities = 0; - int numberOfEntitiesOffset = 0; - withReadLock([&] { - QVector indexesOfEntitiesToInclude; - - // It's possible that our element has been previous completed. In this case we'll simply not include any of our - // entities for encoding. This is needed because we encode the element data at the "parent" level, and so we - // need to handle the case where our sibling elements need encoding but we don't. - if (!entityTreeElementExtraEncodeData->elementCompleted) { - - - // we have an EntityNodeData instance - // so we should assume that means we might have JSON filters to check - auto jsonFilters = entityNodeData->getJSONParameters(); - - - for (uint16_t i = 0; i < _entityItems.size(); i++) { - EntityItemPointer entity = _entityItems[i]; - bool includeThisEntity = true; - - if (!params.forceSendScene && entity->getLastChangedOnServer() < entityNodeData->getLastTimeBagEmpty()) { - includeThisEntity = false; - } - - // if this entity has been updated since our last full send and there are json filters, check them - if (includeThisEntity && !jsonFilters.isEmpty()) { - - // if params include JSON filters, check if this entity matches - bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters); - - if (entityMatchesFilters) { - // make sure this entity is in the set of entities sent last frame - entityNodeData->insertSentFilteredEntity(entity->getID()); - } else if (entityNodeData->sentFilteredEntity(entity->getID())) { - // this entity matched in the previous frame - we send it still so the client realizes it just - // fell outside of their filter - entityNodeData->removeSentFilteredEntity(entity->getID()); - } else if (!entityNodeData->isEntityFlaggedAsExtra(entity->getID())) { - // we don't send this entity because - // (1) it didn't match our filter - // (2) it didn't match our filter last frame - // (3) it isn't one the JSON query flags told us we should still include - includeThisEntity = false; - } - } - - if (includeThisEntity && hadElementExtraData) { - includeThisEntity = entityTreeElementExtraEncodeData->entities.contains(entity->getEntityItemID()); - } - - // we only check the bounds against our frustum and LOD if the query has asked us to check against the frustum - // which can sometimes not be the case when JSON filters are sent - if (entityNodeData->getUsesFrustum() && (includeThisEntity || params.recurseEverything)) { - - // we want to use the maximum possible box for this, so that we don't have to worry about the nuance of - // simulation changing what's visible. consider the case where the entity contains an angular velocity - // the entity may not be in view and then in view a frame later, let the client side handle it's view - // frustum culling on rendering. - bool success; - AACube entityCube = entity->getQueryAACube(success); - if (!success || !params.viewFrustum.cubeIntersectsKeyhole(entityCube)) { - includeThisEntity = false; // out of view, don't include it - } else { - // Check the size of the entity, it's possible that a "too small to see" entity is included in a - // larger octree cell because of its position (for example if it crosses the boundary of a cell it - // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen - // before we consider including it. - success = true; - // we can't cull a parent-entity by its dimensions because the child may be larger. we need to - // avoid sending details about a child but not the parent. the parent's queryAACube should have - // been adjusted to encompass the queryAACube of the child. - AABox entityBounds = entity->hasChildren() ? AABox(entityCube) : entity->getAABox(success); - if (!success) { - // if this entity is a child of an avatar, the entity-server wont be able to determine its - // AABox. If this happens, fall back to the queryAACube. - entityBounds = AABox(entityCube); - } - auto renderAccuracy = calculateRenderAccuracy(params.viewFrustum.getPosition(), - entityBounds, - params.octreeElementSizeScale, - params.boundaryLevelAdjust); - if (renderAccuracy <= 0.0f) { - includeThisEntity = false; // too small, don't include it - - #ifdef WANT_LOD_DEBUGGING - qCDebug(entities) << "skipping entity - TOO SMALL - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "..bounds:" << entityBounds << "\n" - << "....cell:" << getAACube(); - #endif - } - } - } - - if (includeThisEntity) { - #ifdef WANT_LOD_DEBUGGING - qCDebug(entities) << "including entity - \n" - << "......id:" << entity->getID() << "\n" - << "....name:" << entity->getName() << "\n" - << "....cell:" << getAACube(); - #endif - indexesOfEntitiesToInclude << i; - numberOfEntities++; - } else { - // if the extra data included this entity, and we've decided to not include the entity, then - // we can treat it as if it was completed. - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); - } - } - } - - numberOfEntitiesOffset = packetData->getUncompressedByteOffset(); - bool successAppendEntityCount = packetData->appendValue(numberOfEntities); - - if (successAppendEntityCount) { - foreach(uint16_t i, indexesOfEntitiesToInclude) { - EntityItemPointer entity = _entityItems[i]; - LevelDetails entityLevel = packetData->startLevel(); - OctreeElement::AppendState appendEntityState = entity->appendEntityData(packetData, - params, entityTreeElementExtraEncodeData); - - // If none of this entity data was able to be appended, then discard it - // and don't include it in our entity count - if (appendEntityState == OctreeElement::NONE) { - packetData->discardLevel(entityLevel); - } else { - // If either ALL or some of it got appended, then end the level (commit it) - // and include the entity in our final count of entities - packetData->endLevel(entityLevel); - actualNumberOfEntities++; - - // If the entity item got completely appended, then we can remove it from the extra encode data - if (appendEntityState == OctreeElement::COMPLETED) { - entityTreeElementExtraEncodeData->entities.remove(entity->getEntityItemID()); - } - } - - // If any part of the entity items didn't fit, then the element is considered partial - // NOTE: if the entity item didn't fit or only partially fit, then the entity item should have - // added itself to the extra encode data. - if (appendEntityState != OctreeElement::COMPLETED) { - appendElementState = OctreeElement::PARTIAL; - } - } - } else { - // we we couldn't add the entity count, then we couldn't add anything for this element and we're in a NONE state - appendElementState = OctreeElement::NONE; - } - }); - - // If we were provided with extraEncodeData, and we allocated and/or got entityTreeElementExtraEncodeData - // then we need to do some additional processing, namely make sure our extraEncodeData is up to date for - // this octree element. - if (extraEncodeData && entityTreeElementExtraEncodeData) { - - // After processing, if we are PARTIAL or COMPLETED then we need to re-include our extra data. - // Only our parent can remove our extra data in these cases and only after it knows that all of its - // children have been encoded. - // - // FIXME -- this comment seems wrong.... - // - // If we weren't able to encode ANY data about ourselves, then we go ahead and remove our element data - // since that will signal that the entire element needs to be encoded on the next attempt - if (appendElementState == OctreeElement::NONE) { - - if (!entityTreeElementExtraEncodeData->elementCompleted && entityTreeElementExtraEncodeData->entities.size() == 0) { - // TODO: we used to delete the extra encode data here. But changing the logic around - // this is now a dead code branch. Clean this up! - } else { - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } - } else { - - // If we weren't previously completed, check to see if we are - if (!entityTreeElementExtraEncodeData->elementCompleted) { - // If all of our items have been encoded, then we are complete as an element. - if (entityTreeElementExtraEncodeData->entities.size() == 0) { - entityTreeElementExtraEncodeData->elementCompleted = true; - } - } - - // TODO: some of these inserts might be redundant!!! - extraEncodeData->insert(this, entityTreeElementExtraEncodeData); - } - } - - // Determine if no entities at all were able to fit - bool noEntitiesFit = (numberOfEntities > 0 && actualNumberOfEntities == 0); - - // If we wrote fewer entities than we expected, update the number of entities in our packet - bool successUpdateEntityCount = true; - if (numberOfEntities != actualNumberOfEntities) { - successUpdateEntityCount = packetData->updatePriorBytes(numberOfEntitiesOffset, - (const unsigned char*)&actualNumberOfEntities, sizeof(actualNumberOfEntities)); - } - - // If we weren't able to update our entity count, or we couldn't fit any entities, then - // we should discard our element and return a result of NONE - if (!successUpdateEntityCount) { - packetData->discardLevel(elementLevel); - appendElementState = OctreeElement::NONE; - } else { - if (noEntitiesFit) { - //appendElementState = OctreeElement::PARTIAL; - packetData->discardLevel(elementLevel); - appendElementState = OctreeElement::NONE; - } else { - packetData->endLevel(elementLevel); - } - } - return appendElementState; -} - bool EntityTreeElement::containsEntityBounds(EntityItemPointer entity) const { bool success; auto queryCube = entity->getQueryAACube(success); diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index a56af5d03f..76e1e40812 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -121,17 +121,6 @@ public: virtual bool requiresSplit() const override { return false; } virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const override; - virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) override; - virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const override; - virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const override; - virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const override; - virtual void elementEncodeComplete(EncodeBitstreamParams& params) const override; - - bool alreadyFullyEncoded(EncodeBitstreamParams& params) const; - - /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual OctreeElement::AppendState appendElementData(OctreePacketData* packetData, - EncodeBitstreamParams& params) const override; /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 3f7fc5f799..f0fbb20f98 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -186,7 +186,6 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_IS_SPOTLIGHT; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 00196d7b23..92a1c25970 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -128,7 +128,6 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 375453e0e9..84f9acf5f5 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -26,7 +26,6 @@ class LineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 6c040296a3..489ba5772c 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -89,8 +89,6 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_MATERIAL_URL; diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index 969eb577ff..30743850dd 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -32,7 +32,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index bec7bc1906..be62664ff9 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -144,7 +144,6 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); @@ -721,4 +720,4 @@ bool ModelEntityItem::isAnimatingSomething() const { _animationProperties.getRunning() && (_animationProperties.getFPS() != 0.0f); }); -} \ No newline at end of file +} diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index c2109ba51f..327606ae2f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -30,7 +30,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 7d27011c56..d9ef5e2178 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -503,8 +503,6 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 498d13058e..420c570e8d 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -216,8 +216,6 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index 2dc8befe97..871c451c50 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -26,7 +26,6 @@ class PolyLineEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index 84ce83d3a1..ed3372818a 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -183,8 +183,6 @@ int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* dat return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_VOXEL_VOLUME_SIZE; diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 0ddfe3e8cc..4dfe7b9535 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -26,7 +26,6 @@ class PolyVoxEntityItem : public EntityItem { virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 99f18d2dac..520d892682 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -188,8 +188,6 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SHAPE; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 7030a95562..97080d3ca2 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -98,8 +98,6 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_TEXT; diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 06b377ee14..efdc84bcd8 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -30,7 +30,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 548bca3225..f3159ba3f8 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -83,8 +83,6 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_SOURCE_URL; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index dab7cd5e22..1179f22ded 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -29,7 +29,6 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 4ae020f966..b07d0597bc 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -191,8 +191,6 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, return bytesRead; } - -// TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 2c6b01fc69..3a9c7cb1e6 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -33,7 +33,6 @@ public: virtual bool setProperties(const EntityItemProperties& properties) override; virtual bool setSubClassProperties(const EntityItemProperties& properties) override; - // TODO: eventually only include properties changed since the params.nodeData->getLastTimeBagEmpty() time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index f0cbfc914a..823602d939 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -194,7 +194,7 @@ std::pair> NetworkMaterialResource } else if (key == "emissiveMap") { auto value = materialJSON.value(key); if (value.isString()) { - material->setEmissiveMap(value.toString()); + material->setEmissiveMap(baseUrl.resolved(value.toString())); } } else if (key == "albedoMap") { auto value = materialJSON.value(key); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index b6a18b117d..94eff46bda 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -34,15 +34,16 @@ const QString GET_PLACE = "/api/v1/places/%1"; * * @namespace location * @property {Uuid} domainID - A UUID uniquely identifying the domain you're visiting. Is {@link Uuid|Uuid.NULL} if you're not - * connected to the domain. + * connected to the domain or are in a serverless domain. * Read-only. * @property {Uuid} domainId - Synonym for domainId. Read-only. Deprecated: This property * is deprecated and will soon be removed. * @property {string} hostname - The name of the domain for your current metaverse address (e.g., "AvatarIsland", - * localhost, or an IP address). + * localhost, or an IP address). Is blank if you're in a serverless domain. * Read-only. * @property {string} href - Your current metaverse address (e.g., "hifi://avatarisland/15,-10,26/0,0,0,1") - * regardless of whether or not you're connected to the domain. + * regardless of whether or not you're connected to the domain. Starts with "file:///" if you're in a + * serverless domain. * Read-only. * @property {boolean} isConnected - true if you're connected to the domain in your current href * metaverse address, otherwise false. diff --git a/libraries/networking/src/HMACAuth.cpp b/libraries/networking/src/HMACAuth.cpp new file mode 100644 index 0000000000..42b5c48d93 --- /dev/null +++ b/libraries/networking/src/HMACAuth.cpp @@ -0,0 +1,94 @@ +// +// HMACAuth.cpp +// libraries/networking/src +// +// Created by Simon Walton on 3/19/2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "HMACAuth.h" + +#include + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 +HMACAuth::HMACAuth(AuthMethod authMethod) + : _hmacContext(HMAC_CTX_new()) + , _authMethod(authMethod) { } + +HMACAuth::~HMACAuth() +{ + HMAC_CTX_free(_hmacContext); +} + +#else + +HMACAuth::HMACAuth(AuthMethod authMethod) + : _hmacContext(new HMAC_CTX()) + , _authMethod(authMethod) { + HMAC_CTX_init(_hmacContext); +} + +HMACAuth::~HMACAuth() { + HMAC_CTX_cleanup(_hmacContext); + delete _hmacContext; +} +#endif + +bool HMACAuth::setKey(const char* keyValue, int keyLen) { + const EVP_MD* sslStruct = nullptr; + + switch (_authMethod) { + case MD5: + sslStruct = EVP_md5(); + break; + + case SHA1: + sslStruct = EVP_sha1(); + break; + + case SHA224: + sslStruct = EVP_sha224(); + break; + + case SHA256: + sslStruct = EVP_sha256(); + break; + + case RIPEMD160: + sslStruct = EVP_ripemd160(); + break; + + default: + return false; + } + + QMutexLocker lock(&_lock); + return (bool) HMAC_Init_ex(_hmacContext, keyValue, keyLen, sslStruct, nullptr); +} + +bool HMACAuth::setKey(const QUuid& uidKey) { + const QByteArray rfcBytes(uidKey.toRfc4122()); + return setKey(rfcBytes.constData(), rfcBytes.length()); +} + +bool HMACAuth::addData(const char* data, int dataLen) { + QMutexLocker lock(&_lock); + return (bool) HMAC_Update(_hmacContext, reinterpret_cast(data), dataLen); +} + +HMACAuth::HMACHash HMACAuth::result() { + HMACHash hashValue(EVP_MAX_MD_SIZE); + unsigned int hashLen; + QMutexLocker lock(&_lock); + HMAC_Final(_hmacContext, &hashValue[0], &hashLen); + hashValue.resize((size_t) hashLen); + // Clear state for possible reuse. + HMAC_Init_ex(_hmacContext, nullptr, 0, nullptr, nullptr); + return hashValue; +} diff --git a/libraries/networking/src/HMACAuth.h b/libraries/networking/src/HMACAuth.h new file mode 100644 index 0000000000..0bf7a86ec1 --- /dev/null +++ b/libraries/networking/src/HMACAuth.h @@ -0,0 +1,40 @@ +// +// HMACAuth.h +// libraries/networking/src +// +// Created by Simon Walton on 3/19/2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_HMACAuth_h +#define hifi_HMACAuth_h + +#include +#include +#include + +class QUuid; + +class HMACAuth { +public: + enum AuthMethod { MD5, SHA1, SHA224, SHA256, RIPEMD160 }; + using HMACHash = std::vector; + + explicit HMACAuth(AuthMethod authMethod = MD5); + ~HMACAuth(); + + bool setKey(const char* keyValue, int keyLen); + bool setKey(const QUuid& uidKey); + bool addData(const char* data, int dataLen); + HMACHash result(); + +private: + QMutex _lock; + struct hmac_ctx_st* _hmacContext; + AuthMethod _authMethod; +}; + +#endif // hifi_HMACAuth_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index fa934b5539..d7eb472d65 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -36,6 +36,7 @@ #include "HifiSockAddr.h" #include "NetworkLogging.h" #include "udt/Packet.h" +#include "HMACAuth.h" static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.LocalPort", 0); @@ -314,7 +315,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe if (verifiedPacket && !ignoreVerification) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); - QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, sourceNode->getConnectionSecret()); + QByteArray expectedHash = NLPacket::hashForPacketAndHMAC(packet, sourceNode->getAuthenticateHash()); // check if the md5 hash in the header matches the hash we would expect if (packetHeaderHash != expectedHash) { @@ -354,15 +355,15 @@ void LimitedNodeList::collectPacketStats(const NLPacket& packet) { _numCollectedBytes += packet.getDataSize(); } -void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret) { +void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) { if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) { packet.writeSourceID(getSessionUUID()); } - if (!connectionSecret.isNull() + if (hmacAuth && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { - packet.writeVerificationHashGivenSecret(connectionSecret); + packet.writeVerificationHash(*hmacAuth); } } @@ -378,17 +379,17 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& emit dataSent(destinationNode.getType(), packet.getDataSize()); destinationNode.recordBytesSent(packet.getDataSize()); - return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret()); + return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), &destinationNode.getAuthenticateHash()); } qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, - const QUuid& connectionSecret) { + HMACAuth* hmacAuth) { Q_ASSERT(!packet.isPartOfMessage()); Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", "Trying to send a reliable packet unreliably."); collectPacketStats(packet); - fillPacketHeader(packet, connectionSecret); + fillPacketHeader(packet, hmacAuth); return _nodeSocket.writePacket(packet, sockAddr); } @@ -401,7 +402,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& emit dataSent(destinationNode.getType(), packet->getDataSize()); destinationNode.recordBytesSent(packet->getDataSize()); - return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret()); + return sendPacket(std::move(packet), *activeSocket, &destinationNode.getAuthenticateHash()); } else { qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending"; return ERROR_SENDING_PACKET_BYTES; @@ -409,18 +410,18 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& } qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, - const QUuid& connectionSecret) { + HMACAuth* hmacAuth) { Q_ASSERT(!packet->isPartOfMessage()); if (packet->isReliable()) { collectPacketStats(*packet); - fillPacketHeader(*packet, connectionSecret); + fillPacketHeader(*packet, hmacAuth); auto size = packet->getDataSize(); _nodeSocket.writePacket(std::move(packet), sockAddr); return size; } else { - return sendUnreliablePacket(*packet, sockAddr, connectionSecret); + return sendUnreliablePacket(*packet, sockAddr, hmacAuth); } } @@ -429,13 +430,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi if (activeSocket) { qint64 bytesSent = 0; - auto connectionSecret = destinationNode.getConnectionSecret(); + auto& connectionHash = destinationNode.getAuthenticateHash(); // close the last packet in the list packetList.closeCurrentPacket(); while (!packetList._packets.empty()) { - bytesSent += sendPacket(packetList.takeFront(), *activeSocket, connectionSecret); + bytesSent += sendPacket(packetList.takeFront(), *activeSocket, + &connectionHash); } emit dataSent(destinationNode.getType(), bytesSent); @@ -448,14 +450,14 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi } qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, - const QUuid& connectionSecret) { + HMACAuth* hmacAuth) { qint64 bytesSent = 0; // close the last packet in the list packetList.closeCurrentPacket(); while (!packetList._packets.empty()) { - bytesSent += sendPacket(packetList.takeFront(), sockAddr, connectionSecret); + bytesSent += sendPacket(packetList.takeFront(), sockAddr, hmacAuth); } return bytesSent; @@ -483,7 +485,7 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, for (std::unique_ptr& packet : packetList->_packets) { NLPacket* nlPacket = static_cast(packet.get()); collectPacketStats(*nlPacket); - fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret()); + fillPacketHeader(*nlPacket, &destinationNode.getAuthenticateHash()); } return _nodeSocket.writePacketList(std::move(packetList), *activeSocket); @@ -506,7 +508,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& auto& destinationSockAddr = (overridenSockAddr.isNull()) ? *destinationNode.getActiveSocket() : overridenSockAddr; - return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret()); + return sendPacket(std::move(packet), destinationSockAddr, &destinationNode.getAuthenticateHash()); } int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer message, SharedNodePointer sendingNode) { @@ -569,9 +571,10 @@ void LimitedNodeList::reset() { // we need to make sure any socket connections are gone so wait on that here _nodeSocket.clearConnections(); + _connectionIDs.clear(); } -bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { +bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID) { QReadLocker readLocker(&_nodeMutex); NodeHash::iterator it = _nodeHash.find(nodeUUID); @@ -585,7 +588,7 @@ bool LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { _nodeHash.unsafe_erase(it); } - handleNodeKill(matchingNode); + handleNodeKill(matchingNode, newConnectionID); return true; } @@ -600,7 +603,7 @@ void LimitedNodeList::processKillNode(ReceivedMessage& message) { killNodeWithUUID(nodeUUID); } -void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { +void LimitedNodeList::handleNodeKill(const SharedNodePointer& node, ConnectionID nextConnectionID) { qCDebug(networking) << "Killed" << *node; node->stopPingTimer(); emit nodeKilled(node); @@ -608,6 +611,15 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { if (auto activeSocket = node->getActiveSocket()) { _nodeSocket.cleanupConnection(*activeSocket); } + + auto it = _connectionIDs.find(node->getUUID()); + if (it != _connectionIDs.end()) { + if (nextConnectionID == NULL_CONNECTION_ID) { + it->second++; + } else { + it->second = nextConnectionID; + } + } } SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, @@ -629,6 +641,11 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t return matchingNode; } else { + auto it = _connectionIDs.find(uuid); + if (it == _connectionIDs.end()) { + _connectionIDs[uuid] = INITIAL_CONNECTION_ID; + } + // we didn't have this node, so add them Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); newNode->setIsReplicated(isReplicated); @@ -703,13 +720,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t } } -std::unique_ptr LimitedNodeList::constructPingPacket(PingType_t pingType) { - int packetSize = sizeof(PingType_t) + sizeof(quint64); +std::unique_ptr LimitedNodeList::constructPingPacket(const QUuid& nodeId, PingType_t pingType) { + int packetSize = sizeof(PingType_t) + sizeof(quint64) + sizeof(int64_t); auto pingPacket = NLPacket::create(PacketType::Ping, packetSize); - pingPacket->writePrimitive(pingType); pingPacket->writePrimitive(usecTimestampNow()); + pingPacket->writePrimitive(_connectionIDs[nodeId]); return pingPacket; } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 7165b3dd63..d4517bdcec 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -66,6 +66,10 @@ const QHostAddress DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME = QHostAddress::Lo const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username"; +using ConnectionID = int64_t; +const ConnectionID NULL_CONNECTION_ID { -1 }; +const ConnectionID INITIAL_CONNECTION_ID { 0 }; + typedef std::pair UUIDNodePair; typedef tbb::concurrent_unordered_map NodeHash; @@ -128,22 +132,20 @@ public: virtual QUuid getDomainUUID() const { assert(false); return QUuid(); } virtual HifiSockAddr getDomainSockAddr() const { assert(false); return HifiSockAddr(); } - // use sendUnreliablePacket to send an unrelaible packet (that you do not need to move) + // use sendUnreliablePacket to send an unreliable packet (that you do not need to move) // either to a node (via its active socket) or to a manual sockaddr qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode); - qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, - const QUuid& connectionSecret = QUuid()); + qint64 sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr); // use sendPacket to send a moved unreliable or reliable NL packet to a node's active socket or manual sockaddr qint64 sendPacket(std::unique_ptr packet, const Node& destinationNode); - qint64 sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, - const QUuid& connectionSecret = QUuid()); + qint64 sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, HMACAuth* hmacAuth = nullptr); // use sendUnreliableUnorderedPacketList to unreliably send separate packets from the packet list // either to a node's active socket or to a manual sockaddr qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const Node& destinationNode); qint64 sendUnreliableUnorderedPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, - const QUuid& connectionSecret = QUuid()); + HMACAuth* hmacAuth = nullptr); // use sendPacketList to send reliable packet lists (ordered or unordered) to a node's active socket // or to a manual sock addr @@ -180,7 +182,7 @@ public: void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond); void resetPacketStats(); - std::unique_ptr constructPingPacket(PingType_t pingType = PingType::Agnostic); + std::unique_ptr constructPingPacket(const QUuid& nodeId, PingType_t pingType = PingType::Agnostic); std::unique_ptr constructPingReplyPacket(ReceivedMessage& message); static std::unique_ptr constructICEPingPacket(PingType_t pingType, const QUuid& iceID); @@ -319,7 +321,7 @@ public slots: void startSTUNPublicSocketUpdate(); virtual void sendSTUNRequest(); - bool killNodeWithUUID(const QUuid& nodeUUID); + bool killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID = NULL_CONNECTION_ID); signals: void dataSent(quint8 channelType, int bytes); @@ -364,14 +366,14 @@ protected: qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr, const QUuid& connectionSecret = QUuid()); void collectPacketStats(const NLPacket& packet); - void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret = QUuid()); + void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr); void setLocalSocket(const HifiSockAddr& sockAddr); bool packetSourceAndHashMatchAndTrackBandwidth(const udt::Packet& packet, Node* sourceNode = nullptr); void processSTUNResponse(std::unique_ptr packet); - void handleNodeKill(const SharedNodePointer& node); + void handleNodeKill(const SharedNodePointer& node, ConnectionID newConnectionID = NULL_CONNECTION_ID); void stopInitialSTUNUpdate(bool success); @@ -418,6 +420,7 @@ protected: } } + std::unordered_map _connectionIDs; private slots: void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp); diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 5c5077691b..93274843a6 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -11,6 +11,8 @@ #include "NLPacket.h" +#include "HMACAuth.h" + int NLPacket::localHeaderSize(PacketType type) { bool nonSourced = PacketTypeEnum::getNonSourcedPackets().contains(type); bool nonVerified = PacketTypeEnum::getNonVerifiedPackets().contains(type); @@ -149,18 +151,12 @@ QByteArray NLPacket::verificationHashInHeader(const udt::Packet& packet) { return QByteArray(packet.getData() + offset, NUM_BYTES_MD5_HASH); } -QByteArray NLPacket::hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret) { - QCryptographicHash hash(QCryptographicHash::Md5); - +QByteArray NLPacket::hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash) { int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; - - // add the packet payload and the connection UUID hash.addData(packet.getData() + offset, packet.getDataSize() - offset); - hash.addData(connectionSecret.toRfc4122()); - - // return the hash - return hash.result(); + auto hashResult { hash.result() }; + return QByteArray((const char*) hashResult.data(), (int) hashResult.size()); } void NLPacket::writeTypeAndVersion() { @@ -212,13 +208,14 @@ void NLPacket::writeSourceID(const QUuid& sourceID) const { _sourceID = sourceID; } -void NLPacket::writeVerificationHashGivenSecret(const QUuid& connectionSecret) const { +void NLPacket::writeVerificationHash(HMACAuth& hmacAuth) const { Q_ASSERT(!PacketTypeEnum::getNonSourcedPackets().contains(_type) && !PacketTypeEnum::getNonVerifiedPackets().contains(_type)); auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; - QByteArray verificationHash = hashForPacketAndSecret(*this, connectionSecret); + + QByteArray verificationHash = hashForPacketAndHMAC(*this, hmacAuth); memcpy(_packet.get() + offset, verificationHash.data(), verificationHash.size()); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index f49cc47645..302598f77c 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -18,6 +18,8 @@ #include "udt/Packet.h" +class HMACAuth; + class NLPacket : public udt::Packet { Q_OBJECT public: @@ -71,7 +73,7 @@ public: static QUuid sourceIDInHeader(const udt::Packet& packet); static QByteArray verificationHashInHeader(const udt::Packet& packet); - static QByteArray hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret); + static QByteArray hashForPacketAndHMAC(const udt::Packet& packet, HMACAuth& hash); PacketType getType() const { return _type; } void setType(PacketType type); @@ -82,7 +84,7 @@ public: const QUuid& getSourceID() const { return _sourceID; } void writeSourceID(const QUuid& sourceID) const; - void writeVerificationHashGivenSecret(const QUuid& connectionSecret) const; + void writeVerificationHash(HMACAuth& hmacAuth) const; protected: diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index bd895c8ef1..132d27d311 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -86,10 +86,10 @@ NodeType_t NodeType::fromString(QString type) { Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, QObject* parent) : + const HifiSockAddr& localSocket, QObject* parent) : NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), - _pingMs(-1), // "Uninitialized" + _authenticateHash(new HMACAuth), _pingMs(-1), // "Uninitialized" _clockSkewUsec(0), _mutex(), _clockSkewMovingPercentile(30, 0.8f) // moving 80th percentile of 30 samples @@ -108,6 +108,7 @@ void Node::setType(char type) { _symmetricSocket.setObjectName(typeString); } + void Node::updateClockSkewUsec(qint64 clockSkewSample) { _clockSkewMovingPercentile.updatePercentile(clockSkewSample); _clockSkewUsec = (quint64)_clockSkewMovingPercentile.getValueAtPercentile(); @@ -192,3 +193,12 @@ QDebug operator<<(QDebug debug, const Node& node) { debug.nospace() << node.getPublicSocket() << "/" << node.getLocalSocket(); return debug.nospace(); } + +void Node::setConnectionSecret(const QUuid& connectionSecret) { + if (_connectionSecret == connectionSecret) { + return; + } + + _connectionSecret = connectionSecret; + _authenticateHash->setKey(_connectionSecret); +} diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 93b6a649d4..5b3b559582 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -33,6 +33,7 @@ #include "SimpleMovingAverage.h" #include "MovingPercentile.h" #include "NodePermissions.h" +#include "HMACAuth.h" class Node : public NetworkPeer { Q_OBJECT @@ -55,7 +56,8 @@ public: void setIsUpstream(bool isUpstream) { _isUpstream = isUpstream; } const QUuid& getConnectionSecret() const { return _connectionSecret; } - void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } + void setConnectionSecret(const QUuid& connectionSecret); + HMACAuth& getAuthenticateHash() const { return *_authenticateHash; } NodeData* getLinkedData() const { return _linkedData.get(); } void setLinkedData(std::unique_ptr linkedData) { _linkedData = std::move(linkedData); } @@ -97,6 +99,7 @@ private: NodeType_t _type; QUuid _connectionSecret; + std::unique_ptr _authenticateHash; std::unique_ptr _linkedData; bool _isReplicated { false }; int _pingMs; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index ddc4df1226..fdb503eee5 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -214,6 +214,20 @@ void NodeList::processPingPacket(QSharedPointer message, Shared sendingNode->setSymmetricSocket(senderSockAddr); } } + + int64_t connectionID; + + message->readPrimitive(&connectionID); + + auto it = _connectionIDs.find(sendingNode->getUUID()); + if (it != _connectionIDs.end()) { + if (connectionID > it->second) { + qDebug() << "Received a ping packet with a larger connection id (" << connectionID << ">" << it->second << ") from " + << sendingNode->getUUID(); + killNodeWithUUID(sendingNode->getUUID(), connectionID); + } + } + } void NodeList::processPingReplyPacket(QSharedPointer message, SharedNodePointer sendingNode) { @@ -705,16 +719,18 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { if (node->getConnectionAttempts() > 0 && node->getConnectionAttempts() % NUM_DEBUG_CONNECTION_ATTEMPTS == 0) { qCDebug(networking) << "No response to UDP hole punch pings for node" << node->getUUID() << "in last second."; } + + auto nodeID = node->getUUID(); // send the ping packet to the local and public sockets for this node - auto localPingPacket = constructPingPacket(PingType::Local); + auto localPingPacket = constructPingPacket(nodeID, PingType::Local); sendPacket(std::move(localPingPacket), *node, node->getLocalSocket()); - auto publicPingPacket = constructPingPacket(PingType::Public); + auto publicPingPacket = constructPingPacket(nodeID, PingType::Public); sendPacket(std::move(publicPingPacket), *node, node->getPublicSocket()); if (!node->getSymmetricSocket().isNull()) { - auto symmetricPingPacket = constructPingPacket(PingType::Symmetric); + auto symmetricPingPacket = constructPingPacket(nodeID, PingType::Symmetric); sendPacket(std::move(symmetricPingPacket), *node, node->getSymmetricSocket()); } @@ -784,7 +800,7 @@ void NodeList::sendKeepAlivePings() { auto type = node->getType(); return !node->isUpstream() && _nodeTypesOfInterest.contains(type) && !NodeType::isDownstream(type); }, [&](const SharedNodePointer& node) { - sendPacket(constructPingPacket(), *node); + sendPacket(constructPingPacket(node->getUUID()), *node); }); } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index b49b954b99..765f01db0e 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -24,6 +24,8 @@ int packetTypeMetaTypeId = qRegisterMetaType(); PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { + case PacketType::StunResponse: + return 17; case PacketType::DomainList: return static_cast(DomainListVersion::GetMachineFingerprintFromUUIDSupport); case PacketType::EntityAdd: @@ -40,8 +42,21 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AvatarMixerPacketVersion::FBXReaderNodeReparenting); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); + // ICE packets + case PacketType::ICEServerPeerInformation: + return 17; + case PacketType::ICEServerHeartbeatACK: + return 17; + case PacketType::ICEServerQuery: + return 17; case PacketType::ICEServerHeartbeat: return 18; // ICE Server Heartbeat signing + case PacketType::ICEPing: + return static_cast(IcePingVersion::SendICEPeerID); + case PacketType::ICEPingReply: + return 17; + case PacketType::ICEServerHeartbeatDenied: + return 17; case PacketType::AssetMappingOperation: case PacketType::AssetMappingOperationReply: return static_cast(AssetServerPacketVersion::RedirectedMappings); @@ -71,12 +86,12 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::MicrophoneAudioWithEcho: case PacketType::AudioStreamStats: return static_cast(AudioVersion::HighDynamicRangeVolume); - case PacketType::ICEPing: - return static_cast(IcePingVersion::SendICEPeerID); case PacketType::DomainSettings: return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height + case PacketType::Ping: + return static_cast(PingVersion::IncludeConnectionID); default: - return 17; + return 18; } } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e0d3bcdb97..e6b133c158 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -323,4 +323,8 @@ enum class IcePingVersion : PacketVersion { SendICEPeerID = 18 }; +enum class PingVersion : PacketVersion { + IncludeConnectionID = 18 +}; + #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index e34ba34c8f..35db43e5e7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -45,7 +45,6 @@ #include "Octree.h" #include "OctreeConstants.h" -#include "OctreeElementBag.h" #include "OctreeLogging.h" #include "OctreeQueryNode.h" #include "OctreeUtils.h" @@ -57,7 +56,6 @@ Octree::Octree(bool shouldReaverage) : _rootElement(NULL), _isDirty(true), _shouldReaverage(shouldReaverage), - _stopImport(false), _isViewing(false), _isServer(false) { @@ -463,131 +461,6 @@ void Octree::readBitstreamToTree(const unsigned char * bitstream, uint64_t buffe // skip bitstream to new startPoint bitstreamAt += theseBytesRead; bytesRead += theseBytesRead; - - if (args.wantImportProgress) { - emit importProgress((100 * (bitstreamAt - bitstream)) / bufferSizeBytes); - } - } -} - -void Octree::deleteOctreeElementAt(float x, float y, float z, float s) { - unsigned char* octalCode = pointToOctalCode(x,y,z,s); - deleteOctalCodeFromTree(octalCode); - delete[] octalCode; // cleanup memory -} - -class DeleteOctalCodeFromTreeArgs { -public: - bool collapseEmptyTrees; - const unsigned char* codeBuffer; - int lengthOfCode; - bool deleteLastChild; - bool pathChanged; -}; - -// Note: uses the codeColorBuffer format, but the color's are ignored, because -// this only finds and deletes the element from the tree. -void Octree::deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) { - // recurse the tree while decoding the codeBuffer, once you find the element in question, recurse - // back and implement color reaveraging, and marking of lastChanged - DeleteOctalCodeFromTreeArgs args; - args.collapseEmptyTrees = collapseEmptyTrees; - args.codeBuffer = codeBuffer; - args.lengthOfCode = numberOfThreeBitSectionsInCode(codeBuffer); - args.deleteLastChild = false; - args.pathChanged = false; - - withWriteLock([&] { - deleteOctalCodeFromTreeRecursion(_rootElement, &args); - }); -} - -void Octree::deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData) { - DeleteOctalCodeFromTreeArgs* args = (DeleteOctalCodeFromTreeArgs*)extraData; - - int lengthOfElementCode = numberOfThreeBitSectionsInCode(element->getOctalCode()); - - // Since we traverse the tree in code order, we know that if our code - // matches, then we've reached our target element. - if (lengthOfElementCode == args->lengthOfCode) { - // we've reached our target, depending on how we're called we may be able to operate on it - // it here, we need to recurse up, and delete it there. So we handle these cases the same to keep - // the logic consistent. - args->deleteLastChild = true; - return; - } - - // Ok, we know we haven't reached our target element yet, so keep looking - int childIndex = branchIndexWithDescendant(element->getOctalCode(), args->codeBuffer); - OctreeElementPointer childElement = element->getChildAtIndex(childIndex); - - // If there is no child at the target location, and the current parent element is a colored leaf, - // then it means we were asked to delete a child out of a larger leaf voxel. - // We support this by breaking up the parent voxel into smaller pieces. - if (!childElement && element->requiresSplit()) { - // we need to break up ancestors until we get to the right level - OctreeElementPointer ancestorElement = element; - while (true) { - int index = branchIndexWithDescendant(ancestorElement->getOctalCode(), args->codeBuffer); - - // we end up with all the children, even the one we want to delete - ancestorElement->splitChildren(); - - int lengthOfAncestorElement = numberOfThreeBitSectionsInCode(ancestorElement->getOctalCode()); - - // If we've reached the parent of the target, then stop breaking up children - if (lengthOfAncestorElement == (args->lengthOfCode - 1)) { - - // since we created all the children when we split, we need to delete this target one - ancestorElement->deleteChildAtIndex(index); - break; - } - ancestorElement = ancestorElement->getChildAtIndex(index); - } - _isDirty = true; - args->pathChanged = true; - - // ends recursion, unwinds up stack - return; - } - - // if we don't have a child and we reach this point, then we actually know that the parent - // isn't a colored leaf, and the child branch doesn't exist, so there's nothing to do below and - // we can safely return, ending the recursion and unwinding - if (!childElement) { - return; - } - - // If we got this far then we have a child for the branch we're looking for, but we're not there yet - // recurse till we get there - deleteOctalCodeFromTreeRecursion(childElement, args); - - // If the lower level determined it needs to be deleted, then we should delete now. - if (args->deleteLastChild) { - element->deleteChildAtIndex(childIndex); // note: this will track dirtiness and lastChanged for this element - - // track our tree dirtiness - _isDirty = true; - - // track that path has changed - args->pathChanged = true; - - // If we're in collapseEmptyTrees mode, and this was the last child of this element, then we also want - // to delete this element. This will collapse the empty tree above us. - if (args->collapseEmptyTrees && element->getChildCount() == 0) { - // Can't delete the root this way. - if (element == _rootElement) { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } else { - args->deleteLastChild = false; // reset so that further up the unwinding chain we don't do anything - } - } - - // If the lower level did some work, then we need to let this element know, so it can - // do any bookkeeping it wants to, like color re-averaging, time stamp marking, etc - if (args->pathChanged) { - element->handleSubtreeChanged(shared_from_this()); } } @@ -856,720 +729,6 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc return args.element; } - - -int Octree::encodeTreeBitstream(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params) { - - // How many bytes have we written so far at this level; - int bytesWritten = 0; - - // you can't call this without a valid element - if (!element) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with element=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesWritten; - } - - // you can't call this without a valid nodeData - auto octreeQueryNode = static_cast(params.nodeData); - if (!octreeQueryNode) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; - return bytesWritten; - } - - // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything && !element->isInView(params.viewFrustum)) { - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesWritten; - } - - // write the octal code - bool roomForOctalCode = false; // assume the worst - int codeLength = 1; // assume root - if (params.chopLevels) { - unsigned char* newCode = chopOctalCode(element->getOctalCode(), params.chopLevels); - roomForOctalCode = packetData->startSubTree(newCode); - - if (newCode) { - codeLength = numberOfThreeBitSectionsInCode(newCode); - delete[] newCode; - } else { - codeLength = 1; - } - } else { - roomForOctalCode = packetData->startSubTree(element->getOctalCode()); - codeLength = (int)bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(element->getOctalCode())); - } - - // If the octalcode couldn't fit, then we can return, because no nodes below us will fit... - if (!roomForOctalCode) { - bag.insert(element); - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - return bytesWritten; - } - - bytesWritten += codeLength; // keep track of byte count - - int currentEncodeLevel = 0; - - // record some stats, this is the one element that we won't record below in the recursion function, so we need to - // track it here - octreeQueryNode->stats.traversed(element); - - ViewFrustum::intersection parentLocationThisView = ViewFrustum::INTERSECT; // assume parent is in view, but not fully - - int childBytesWritten = encodeTreeBitstreamRecursion(element, packetData, bag, params, - currentEncodeLevel, parentLocationThisView); - - // if childBytesWritten == 1 then something went wrong... that's not possible - assert(childBytesWritten != 1); - - // if childBytesWritten == 2, then it can only mean that the lower level trees don't exist or for some - // reason couldn't be written... so reset them here... This isn't true for the non-color included case - if (suppressEmptySubtrees() && childBytesWritten == 2) { - childBytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT... - } - - // if we wrote child bytes, then return our result of all bytes written - if (childBytesWritten) { - bytesWritten += childBytesWritten; - } else { - // otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code - bytesWritten = 0; - //params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - if (bytesWritten == 0) { - packetData->discardSubTree(); - } else { - packetData->endSubTree(); - } - - return bytesWritten; -} - -int Octree::encodeTreeBitstreamRecursion(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::intersection& parentLocationThisView) const { - - - const bool wantDebug = false; - - // The append state of this level/element. - OctreeElement::AppendState elementAppendState = OctreeElement::COMPLETED; // assume the best - - // How many bytes have we written so far at this level; - int bytesAtThisLevel = 0; - - // you can't call this without a valid element - if (!element) { - qCDebug(octree, "WARNING! encodeTreeBitstreamRecursion() called with element=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE; - return bytesAtThisLevel; - } - - // you can't call this without a valid nodeData - auto octreeQueryNode = static_cast(params.nodeData); - if (!octreeQueryNode) { - qCDebug(octree, "WARNING! encodeTreeBitstream() called with nodeData=NULL"); - params.stopReason = EncodeBitstreamParams::NULL_NODE_DATA; - return bytesAtThisLevel; - } - - - // Keep track of how deep we've encoded. - currentEncodeLevel++; - - params.maxLevelReached = std::max(currentEncodeLevel, params.maxLevelReached); - - // If we've reached our max Search Level, then stop searching. - if (currentEncodeLevel >= params.maxEncodeLevel) { - params.stopReason = EncodeBitstreamParams::TOO_DEEP; - return bytesAtThisLevel; - } - - ViewFrustum::intersection nodeLocationThisView = ViewFrustum::INSIDE; // assume we're inside - if (octreeQueryNode->getUsesFrustum() && !params.recurseEverything) { - float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - - // If we're too far away for our render level, then just return - if (element->distanceToCamera(params.viewFrustum) >= boundaryDistance) { - octreeQueryNode->stats.skippedDistance(element); - params.stopReason = EncodeBitstreamParams::LOD_SKIP; - return bytesAtThisLevel; - } - - // if the parent isn't known to be INSIDE, then it must be INTERSECT, and we should double check to see - // if we are INSIDE, INTERSECT, or OUTSIDE - if (parentLocationThisView != ViewFrustum::INSIDE) { - assert(parentLocationThisView != ViewFrustum::OUTSIDE); // we shouldn't be here if our parent was OUTSIDE! - nodeLocationThisView = element->computeViewIntersection(params.viewFrustum); - } - - // If we're at a element that is out of view, then we can return, because no nodes below us will be in view! - // although technically, we really shouldn't ever be here, because our callers shouldn't be calling us if - // we're out of view - if (nodeLocationThisView == ViewFrustum::OUTSIDE) { - octreeQueryNode->stats.skippedOutOfView(element); - params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW; - return bytesAtThisLevel; - } - - // Ok, we are in view, but if we're in delta mode, then we also want to make sure we weren't already in view - // because we don't send nodes from the previously know in view frustum. - bool wasInView = false; - - if (params.deltaView) { - ViewFrustum::intersection location = element->computeViewIntersection(params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (element->isLeaf()) { - wasInView = location != ViewFrustum::OUTSIDE; - } else { - wasInView = location == ViewFrustum::INSIDE; - } - - // If we were in view, double check that we didn't switch LOD visibility... namely, the was in view doesn't - // tell us if it was so small we wouldn't have rendered it. Which may be the case. And we may have moved closer - // to it, and so therefore it may now be visible from an LOD perspective, in which case we don't consider it - // as "was in view"... - if (wasInView) { - float boundaryDistance = boundaryDistanceForRenderLevel(element->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - if (element->distanceToCamera(params.lastViewFrustum) >= boundaryDistance) { - // This would have been invisible... but now should be visible (we wouldn't be here otherwise)... - wasInView = false; - } - } - } - - // If we were previously in the view, then we normally will return out of here and stop recursing. But - // if we're in deltaView mode, and this element has changed since it was last sent, then we do - // need to send it. - if (wasInView && !(params.deltaView && element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))) { - octreeQueryNode->stats.skippedWasInView(element); - params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW; - return bytesAtThisLevel; - } - } - - // If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed, - // then we can also bail early and save bits - if (!params.forceSendScene && !params.deltaView && - !element->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE)) { - - octreeQueryNode->stats.skippedNoChange(element); - - params.stopReason = EncodeBitstreamParams::NO_CHANGE; - return bytesAtThisLevel; - } - - bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more! - - // At any given point in writing the bitstream, the largest minimum we might need to flesh out the current level - // is 1 byte for child colors + 3*NUMBER_OF_CHILDREN bytes for the actual colors + 1 byte for child trees. - // There could be sub trees below this point, which might take many more bytes, but that's ok, because we can - // always mark our subtrees as not existing and stop the packet at this point, then start up with a new packet - // for the remaining sub trees. - unsigned char childrenExistInTreeBits = 0; - unsigned char childrenExistInPacketBits = 0; - unsigned char childrenDataBits = 0; - - // Make our local buffer large enough to handle writing at this level in case we need to. - LevelDetails thisLevelKey = packetData->startLevel(); - int requiredBytes = sizeof(childrenDataBits) + sizeof(childrenExistInPacketBits); - if (params.includeExistsBits) { - requiredBytes += sizeof(childrenExistInTreeBits); - } - - // If this datatype allows root elements to include data, and this is the root, then ask the tree for the - // minimum bytes needed for root data and reserve those also - if (element == _rootElement && rootElementHasData()) { - requiredBytes += minimumRequiredRootDataBytes(); - } - - bool continueThisLevel = packetData->reserveBytes(requiredBytes); - - // If we can't reserve our minimum bytes then we can discard this level and return as if none of this level fits - if (!continueThisLevel) { - packetData->discardLevel(thisLevelKey); - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bag.insert(element); - return bytesAtThisLevel; - } - - int inViewCount = 0; - int inViewNotLeafCount = 0; - int inViewWithColorCount = 0; - - OctreeElementPointer sortedChildren[NUMBER_OF_CHILDREN] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; - float distancesToChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - int indexOfChildren[NUMBER_OF_CHILDREN] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - - // if the caller wants to include childExistsBits, then include them - if (params.includeExistsBits && childElement) { - childrenExistInTreeBits += (1 << (7 - i)); - } - - sortedChildren[i] = childElement; - indexOfChildren[i] = i; - distancesToChildren[i] = 0.0f; - - // track stats - // must check childElement here, because it could be we got here with no childElement - if (childElement) { - octreeQueryNode->stats.traversed(childElement); - } - } - - // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - OctreeElementPointer childElement = sortedChildren[i]; - int originalIndex = indexOfChildren[i]; - - bool childIsInView = (childElement && - (params.recurseEverything || !octreeQueryNode->getUsesFrustum() || - (nodeLocationThisView == ViewFrustum::INSIDE) || // parent was fully in view, we can assume ALL children are - (nodeLocationThisView == ViewFrustum::INTERSECT && - childElement->isInView(params.viewFrustum)) // the parent intersects and the child is in view - )); - - if (!childIsInView) { - // must check childElement here, because it could be we got here because there was no childElement - if (childElement) { - octreeQueryNode->stats.skippedOutOfView(childElement); - } - } else { - // Before we consider this further, let's see if it's in our LOD scope... - float boundaryDistance = params.recurseEverything || !octreeQueryNode->getUsesFrustum() ? 1 : - boundaryDistanceForRenderLevel(childElement->getLevel() + params.boundaryLevelAdjust, - params.octreeElementSizeScale); - - if (!(distancesToChildren[i] < boundaryDistance)) { - // don't need to check childElement here, because we can't get here with no childElement - octreeQueryNode->stats.skippedDistance(childElement); - } else { - inViewCount++; - - // track children in view as existing and not a leaf, if they're a leaf, - // we don't care about recursing deeper on them, and we don't consider their - // subtree to exist - if (!(childElement && childElement->isLeaf())) { - childrenExistInPacketBits += (1 << (7 - originalIndex)); - inViewNotLeafCount++; - } - - bool childIsOccluded = false; // assume it's not occluded - - bool shouldRender = params.recurseEverything || !octreeQueryNode->getUsesFrustum() || - childElement->calculateShouldRender(params.viewFrustum, - params.octreeElementSizeScale, params.boundaryLevelAdjust); - - // track some stats - // don't need to check childElement here, because we can't get here with no childElement - if (!shouldRender && childElement->isLeaf()) { - octreeQueryNode->stats.skippedDistance(childElement); - } - // don't need to check childElement here, because we can't get here with no childElement - if (childIsOccluded) { - octreeQueryNode->stats.skippedOccluded(childElement); - } - - // track children with actual color, only if the child wasn't previously in view! - if (shouldRender && !childIsOccluded) { - bool childWasInView = false; - - if (childElement && params.deltaView) { - ViewFrustum::intersection location = childElement->computeViewIntersection(params.lastViewFrustum); - - // If we're a leaf, then either intersect or inside is considered "formerly in view" - if (childElement->isLeaf()) { - childWasInView = location != ViewFrustum::OUTSIDE; - } else { - childWasInView = location == ViewFrustum::INSIDE; - } - } - - // If our child wasn't in view (or we're ignoring wasInView) then we add it to our sending items. - // Or if we were previously in the view, but this element has changed since it was last sent, then we do - // need to send it. - if (!childWasInView || - (params.deltaView && - childElement->hasChangedSince(octreeQueryNode->getLastTimeBagEmpty() - CHANGE_FUDGE))){ - - childrenDataBits += (1 << (7 - originalIndex)); - inViewWithColorCount++; - } else { - // otherwise just track stats of the items we discarded - // don't need to check childElement here, because we can't get here with no childElement - if (childWasInView) { - octreeQueryNode->stats.skippedWasInView(childElement); - } else { - octreeQueryNode->stats.skippedNoChange(childElement); - } - } - } - } - } - } - - // NOTE: the childrenDataBits indicates that there is an array of child element data included in this packet. - // We will write this bit mask but we may come back later and update the bits that are actually included - packetData->releaseReservedBytes(sizeof(childrenDataBits)); - continueThisLevel = packetData->appendBitMask(childrenDataBits); - - int childDataBitsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenDataBits)); - unsigned char actualChildrenDataBits = 0; - - assert(continueThisLevel); // since we used reserved bits, this really shouldn't fail - bytesAtThisLevel += sizeof(childrenDataBits); // keep track of byte count - - octreeQueryNode->stats.colorBitsWritten(); // really data bits not just color bits - - // NOW might be a good time to give our tree subclass and this element a chance to set up and check any extra encode data - element->initializeExtraEncodeData(params); - - // write the child element data... - // NOTE: the format of the bitstream is generally this: - // [octalcode] - // [bitmask for existence of child data] - // N x [child data] - // [bitmask for existence of child elements in tree] - // [bitmask for existence of child elements in buffer] - // N x [ ... tree for children ...] - // - // This section of the code, is writing the "N x [child data]" portion of this bitstream - for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { - if (oneAtBit(childrenDataBits, i)) { - OctreeElementPointer childElement = element->getChildAtIndex(i); - - // the childrenDataBits were set up by the in view/LOD logic, it may contain children that we've already - // processed and sent the data bits for. Let our tree subclass determine if it really wants to send the - // data for this child at this point - if (childElement && element->shouldIncludeChildData(i, params)) { - - int bytesBeforeChild = packetData->getUncompressedSize(); - - // a childElement may "partially" write it's data. for example, the model server where the entire - // contents of the element may be larger than can fit in a single MTU/packetData. In this case, - // we want to allow the appendElementData() to respond that it produced partial data, which should be - // written, but that the childElement needs to be reprocessed in an additional pass or passes - // to be completed. - LevelDetails childDataLevelKey = packetData->startLevel(); - - OctreeElement::AppendState childAppendState = childElement->appendElementData(packetData, params); - - // allow our tree subclass to do any additional bookkeeping it needs to do with encoded data state - element->updateEncodedData(i, childAppendState, params); - - // Continue this level so long as some part of this child element was appended. - bool childFit = (childAppendState != OctreeElement::NONE); - - // some datatypes (like Voxels) assume that all child data will fit, if it doesn't fit - // the data type wants to bail on this element level completely - if (!childFit && mustIncludeAllChildData()) { - continueThisLevel = false; - break; - } - - // If the child was partially or fully appended, then mark the actualChildrenDataBits as including - // this child data - if (childFit) { - actualChildrenDataBits += (1 << (7 - i)); - continueThisLevel = packetData->endLevel(childDataLevelKey); - } else { - packetData->discardLevel(childDataLevelKey); - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - // If this child was partially appended, then consider this element to be partially appended - if (childAppendState == OctreeElement::PARTIAL) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - int bytesAfterChild = packetData->getUncompressedSize(); - - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - - // don't need to check childElement here, because we can't get here with no childElement - if (childAppendState != OctreeElement::NONE) { - octreeQueryNode->stats.colorSent(childElement); - } - } - } - } - - if (!mustIncludeAllChildData() && !continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: reached end of child element data loop with continueThisLevel=FALSE"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - - if (continueThisLevel && actualChildrenDataBits != childrenDataBits) { - // repair the child data mask - continueThisLevel = packetData->updatePriorBitMask(childDataBitsPlaceHolder, actualChildrenDataBits); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childDataBitsPlaceHolder"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // if the caller wants to include childExistsBits, then include them even if not in view, put them before the - // childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits - if (continueThisLevel && params.includeExistsBits) { - packetData->releaseReservedBytes(sizeof(childrenExistInTreeBits)); - continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count - - octreeQueryNode->stats.existsBitsWritten(); - } else { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInTreeBits"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // write the child exist bits - if (continueThisLevel) { - packetData->releaseReservedBytes(sizeof(childrenExistInPacketBits)); - continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits); - if (continueThisLevel) { - bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count - - octreeQueryNode->stats.existsInPacketBitsWritten(); - } else { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to append childrenExistInPacketBits"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // We only need to keep digging, if there is at least one child that is inView, and not a leaf. - keepDiggingDeeper = (inViewNotLeafCount > 0); - - // - // NOTE: the format of the bitstream is generally this: - // [octalcode] - // [bitmask for existence of child data] - // N x [child data] - // [bitmask for existence of child elements in tree] - // [bitmask for existence of child elements in buffer] - // N x [ ... tree for children ...] - // - // This section of the code, is writing the "N x [ ... tree for children ...]" portion of this bitstream - // - if (continueThisLevel && keepDiggingDeeper) { - - // at this point, we need to iterate the children who are in view, even if not colored - // and we need to determine if there's a deeper tree below them that we care about. - // - // Since this recursive function assumes we're already writing, we know we've already written our - // childrenExistInPacketBits. But... we don't really know how big the child tree will be. And we don't know if - // we'll have room in our buffer to actually write all these child trees. What we kinda would like to do is - // write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they - // write something, we keep them in the bits, if they don't, we take them out. - // - // we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was! - int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits)); - - // we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the - // final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes, - // and then later reshuffle these sections of our output buffer back into normal order. This allows us to make - // a single recursive pass in distance sorted order, but retain standard order in our encoded packet - - // for each child element in Distance sorted order..., check to see if they exist, are colored, and in view, and if so - // add them to our distance ordered array of children - for (int indexByDistance = 0; indexByDistance < NUMBER_OF_CHILDREN; indexByDistance++) { - OctreeElementPointer childElement = sortedChildren[indexByDistance]; - int originalIndex = indexOfChildren[indexByDistance]; - - if (oneAtBit(childrenExistInPacketBits, originalIndex)) { - - int thisLevel = currentEncodeLevel; - - int childTreeBytesOut = 0; - - // NOTE: some octree styles (like models and particles) will store content in parent elements, and child - // elements. In this case, if we stop recursion when we include any data (the colorbits should really be - // called databits), then we wouldn't send the children. So those types of Octree's should tell us to keep - // recursing, by returning TRUE in recurseChildrenWithData(). - - if (params.recurseEverything || !octreeQueryNode->getUsesFrustum() - || recurseChildrenWithData() || !oneAtBit(childrenDataBits, originalIndex)) { - - // Allow the datatype a chance to determine if it really wants to recurse this tree. Usually this - // will be true. But if the tree has already been encoded, we will skip this. - if (element->shouldRecurseChildTree(originalIndex, params)) { - childTreeBytesOut = encodeTreeBitstreamRecursion(childElement, packetData, bag, params, - thisLevel, nodeLocationThisView); - } else { - childTreeBytesOut = 0; - } - } - - // if the child wrote 0 bytes, it means that nothing below exists or was in view, or we ran out of space, - // basically, the children below don't contain any info. - - // if the child tree wrote 1 byte??? something must have gone wrong... because it must have at least the color - // byte and the child exist byte. - // - assert(childTreeBytesOut != 1); - - // if the child tree wrote just 2 bytes, then it means: it had no colors and no child nodes, because... - // if it had colors it would write 1 byte for the color mask, - // and at least a color's worth of bytes for the element of colors. - // if it had child trees (with something in them) then it would have the 1 byte for child mask - // and some number of bytes of lower children... - // so, if the child returns 2 bytes out, we can actually consider that an empty tree also!! - // - // we can make this act like no bytes out, by just resetting the bytes out in this case - if (suppressEmptySubtrees() && !params.includeExistsBits && childTreeBytesOut == 2) { - childTreeBytesOut = 0; // this is the degenerate case of a tree with no colors and no child trees - - } - - bytesAtThisLevel += childTreeBytesOut; - - // If we had previously started writing, and if the child DIDN'T write any bytes, - // then we want to remove their bit from the childExistsPlaceHolder bitmask - if (childTreeBytesOut == 0) { - - // remove this child's bit... - childrenExistInPacketBits -= (1 << (7 - originalIndex)); - - // repair the child exists mask - continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits); - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Failed to update childExistsPlaceHolder"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - - // If this is the last of the child exists bits, then we're actually be rolling out the entire tree - if (childrenExistInPacketBits == 0) { - octreeQueryNode->stats.childBitsRemoved(params.includeExistsBits); - } - - if (!continueThisLevel) { - if (wantDebug) { - qCDebug(octree) << " WARNING line:" << __LINE__; - qCDebug(octree) << " breaking the child recursion loop with continueThisLevel=false!!!"; - qCDebug(octree) << " AFTER attempting to updatePriorBitMask() for empty sub tree...."; - qCDebug(octree) << " IS THIS ACCEPTABLE!!!!"; - } - break; // can't continue... - } - - // Note: no need to move the pointer, cause we already stored this - } // end if (childTreeBytesOut == 0) - } // end if (oneAtBit(childrenExistInPacketBits, originalIndex)) - } // end for - } // end keepDiggingDeeper - - // If we made it this far, then we've written all of our child data... if this element is the root - // element, then we also allow the root element to write out it's data... - if (continueThisLevel && element == _rootElement && rootElementHasData()) { - int bytesBeforeChild = packetData->getUncompressedSize(); - - // release the bytes we reserved... - packetData->releaseReservedBytes(minimumRequiredRootDataBytes()); - - LevelDetails rootDataLevelKey = packetData->startLevel(); - OctreeElement::AppendState rootAppendState = element->appendElementData(packetData, params); - - bool partOfRootFit = (rootAppendState != OctreeElement::NONE); - bool allOfRootFit = (rootAppendState == OctreeElement::COMPLETED); - - if (partOfRootFit) { - continueThisLevel = packetData->endLevel(rootDataLevelKey); - if (!continueThisLevel) { - qCDebug(octree) << " UNEXPECTED ROOT ELEMENT -- could not packetData->endLevel(rootDataLevelKey) -- line:" << __LINE__; - } - } else { - packetData->discardLevel(rootDataLevelKey); - } - - if (!allOfRootFit) { - elementAppendState = OctreeElement::PARTIAL; - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - } - - // do we really ever NOT want to continue this level??? - //continueThisLevel = (rootAppendState == OctreeElement::COMPLETED); - - int bytesAfterChild = packetData->getUncompressedSize(); - - if (continueThisLevel) { - bytesAtThisLevel += (bytesAfterChild - bytesBeforeChild); // keep track of byte count for this child - - octreeQueryNode->stats.colorSent(element); - } - - if (!continueThisLevel) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in packing ROOT data"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // if we were unable to fit this level in our packet, then rewind and add it to the element bag for - // sending later... - if (continueThisLevel) { - continueThisLevel = packetData->endLevel(thisLevelKey); - } else { - packetData->discardLevel(thisLevelKey); - - if (!mustIncludeAllChildData()) { - qCDebug(octree) << "WARNING UNEXPECTED CASE: Something failed in attempting to pack this element"; - qCDebug(octree) << "This is not expected!!!! -- continueThisLevel=FALSE...."; - } - } - - // This happens if the element could not be written at all. In the case of Octree's that support partial - // element data, continueThisLevel will be true. So this only happens if the full element needs to be - // added back to the element bag. - if (!continueThisLevel) { - if (!mustIncludeAllChildData()) { - qCDebug(octree) << "WARNING UNEXPECTED CASE - Something failed in attempting to pack this element."; - qCDebug(octree) << " If the datatype requires all child data, then this might happen. Otherwise" ; - qCDebug(octree) << " this is an unexpected case and we should research a potential logic error." ; - } - - bag.insert(element); - - // don't need to check element here, because we can't get here with no element - octreeQueryNode->stats.didntFit(element); - - params.stopReason = EncodeBitstreamParams::DIDNT_FIT; - bytesAtThisLevel = 0; // didn't fit - } else { - - // assuming we made it here with continueThisLevel == true, we STILL might want - // to add our element back to the bag for additional encoding, specifically if - // the appendState is PARTIAL, in this case, we re-add our element to the bag - // and assume that the appendElementData() has stored any required state data - // in the params extraEncodeData - if (elementAppendState == OctreeElement::PARTIAL) { - bag.insert(element); - } - } - - // If our element is completed let the element know so it can do any cleanup it of extra wants - if (elementAppendState == OctreeElement::COMPLETED) { - element->elementEncodeComplete(params); - } - - return bytesAtThisLevel; -} - bool Octree::readFromFile(const char* fileName) { QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); @@ -1588,14 +747,10 @@ bool Octree::readFromFile(const char* fileName) { QFileInfo fileInfo(qFileName); uint64_t fileLength = fileInfo.size(); - emit importSize(1.0f, 1.0f, 1.0f); - emit importProgress(0); - qCDebug(octree) << "Loading file" << qFileName << "..."; bool success = readFromStream(fileLength, fileInputStream); - emit importProgress(100); file.close(); return success; @@ -1836,7 +991,3 @@ bool Octree::countOctreeElementsOperation(const OctreeElementPointer& element, v (*(uint64_t*)extraData)++; return true; // keep going } - -void Octree::cancelImport() { - _stopImport = true; -} diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index c4c4508138..a2ad834e18 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -49,126 +49,62 @@ public: // Callback function, for recuseTreeWithOperation using RecurseOctreeOperation = std::function; -typedef enum {GRADIENT, RANDOM, NATURAL} creationMode; typedef QHash CubeList; const bool NO_EXISTS_BITS = false; const bool WANT_EXISTS_BITS = true; -const bool COLLAPSE_EMPTY_TREE = true; -const bool DONT_COLLAPSE = false; -const int DONT_CHOP = 0; const int NO_BOUNDARY_ADJUST = 0; const int LOW_RES_MOVING_ADJUST = 1; -#define IGNORE_COVERAGE_MAP NULL - class EncodeBitstreamParams { public: ViewFrustum viewFrustum; - ViewFrustum lastViewFrustum; - int maxEncodeLevel; - int maxLevelReached; bool includeExistsBits; - int chopLevels; - bool deltaView; - bool recurseEverything { false }; - int boundaryLevelAdjust; - float octreeElementSizeScale; - bool forceSendScene; NodeData* nodeData; // output hints from the encode process typedef enum { UNKNOWN, DIDNT_FIT, - NULL_NODE, - NULL_NODE_DATA, - TOO_DEEP, - LOD_SKIP, - OUT_OF_VIEW, - WAS_IN_VIEW, - NO_CHANGE, - OCCLUDED, FINISHED } reason; reason stopReason; - EncodeBitstreamParams( - int maxEncodeLevel = INT_MAX, - bool includeExistsBits = WANT_EXISTS_BITS, - int chopLevels = 0, - bool useDeltaView = false, - int boundaryLevelAdjust = NO_BOUNDARY_ADJUST, - float octreeElementSizeScale = DEFAULT_OCTREE_SIZE_SCALE, - bool forceSendScene = true, - NodeData* nodeData = nullptr) : - maxEncodeLevel(maxEncodeLevel), - maxLevelReached(0), + EncodeBitstreamParams(bool includeExistsBits = WANT_EXISTS_BITS, + NodeData* nodeData = nullptr) : includeExistsBits(includeExistsBits), - chopLevels(chopLevels), - deltaView(useDeltaView), - boundaryLevelAdjust(boundaryLevelAdjust), - octreeElementSizeScale(octreeElementSizeScale), - forceSendScene(forceSendScene), nodeData(nodeData), stopReason(UNKNOWN) { - lastViewFrustum.invalidate(); } void displayStopReason() { printf("StopReason: "); switch (stopReason) { - default: case UNKNOWN: qDebug("UNKNOWN"); break; - case DIDNT_FIT: qDebug("DIDNT_FIT"); break; - case NULL_NODE: qDebug("NULL_NODE"); break; - case TOO_DEEP: qDebug("TOO_DEEP"); break; - case LOD_SKIP: qDebug("LOD_SKIP"); break; - case OUT_OF_VIEW: qDebug("OUT_OF_VIEW"); break; - case WAS_IN_VIEW: qDebug("WAS_IN_VIEW"); break; - case NO_CHANGE: qDebug("NO_CHANGE"); break; - case OCCLUDED: qDebug("OCCLUDED"); break; + case FINISHED: qDebug("FINISHED"); break; } } QString getStopReason() { switch (stopReason) { - default: case UNKNOWN: return QString("UNKNOWN"); break; - case DIDNT_FIT: return QString("DIDNT_FIT"); break; - case NULL_NODE: return QString("NULL_NODE"); break; - case TOO_DEEP: return QString("TOO_DEEP"); break; - case LOD_SKIP: return QString("LOD_SKIP"); break; - case OUT_OF_VIEW: return QString("OUT_OF_VIEW"); break; - case WAS_IN_VIEW: return QString("WAS_IN_VIEW"); break; - case NO_CHANGE: return QString("NO_CHANGE"); break; - case OCCLUDED: return QString("OCCLUDED"); break; + case FINISHED: return QString("FINISHED"); break; } } std::function trackSend { [](const QUuid&, quint64){} }; }; -class ReadElementBufferToTreeArgs { -public: - const unsigned char* buffer; - int length; - bool destructive; - bool pathChanged; -}; - class ReadBitstreamToTreeParams { public: bool includeExistsBits; OctreeElementPointer destinationElement; QUuid sourceUUID; SharedNodePointer sourceNode; - bool wantImportProgress; - PacketVersion bitstreamVersion; int elementsPerPacket = 0; int entitiesPerPacket = 0; @@ -176,15 +112,11 @@ public: bool includeExistsBits = WANT_EXISTS_BITS, OctreeElementPointer destinationElement = NULL, QUuid sourceUUID = QUuid(), - SharedNodePointer sourceNode = SharedNodePointer(), - bool wantImportProgress = false, - PacketVersion bitstreamVersion = 0) : + SharedNodePointer sourceNode = SharedNodePointer()) : includeExistsBits(includeExistsBits), destinationElement(destinationElement), sourceUUID(sourceUUID), - sourceNode(sourceNode), - wantImportProgress(wantImportProgress), - bitstreamVersion(bitstreamVersion) + sourceNode(sourceNode) {} }; @@ -199,7 +131,6 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing - virtual bool getWantSVOfileVersions() const { return false; } virtual PacketType expectedDataPacketType() const { return PacketType::Unknown; } virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); } virtual bool handlesEditPacketType(PacketType packetType) const { return false; } @@ -209,12 +140,8 @@ public: virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } - virtual bool recurseChildrenWithData() const { return true; } virtual bool rootElementHasData() const { return false; } - virtual int minimumRequiredRootDataBytes() const { return 0; } - virtual bool suppressEmptySubtrees() const { return true; } virtual void releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncodeData) const { } - virtual bool mustIncludeAllChildData() const { return true; } virtual void update() { } // nothing to do by default @@ -223,11 +150,8 @@ public: virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); - void deleteOctalCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE); void reaverageOctreeElements(OctreeElementPointer startElement = OctreeElementPointer()); - void deleteOctreeElementAt(float x, float y, float z, float s); - /// Find the voxel at position x,y,z,s /// \return pointer to the OctreeElement or NULL if none at x,y,z,s. OctreeElementPointer getOctreeElementAt(float x, float y, float z, float s) const; @@ -250,8 +174,6 @@ public: void recurseTreeWithOperator(RecurseOctreeOperator* operatorObject); - int encodeTreeBitstream(const OctreeElementPointer& element, OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params) ; bool isDirty() const { return _isDirty; } void clearDirtyBit() { _isDirty = false; } @@ -344,22 +266,10 @@ public: void incrementPersistDataVersion() { _persistDataVersion++; } -signals: - void importSize(float x, float y, float z); - void importProgress(int progress); - -public slots: - void cancelImport(); - protected: void deleteOctalCodeFromTreeRecursion(const OctreeElementPointer& element, void* extraData); - int encodeTreeBitstreamRecursion(const OctreeElementPointer& element, - OctreePacketData* packetData, OctreeElementBag& bag, - EncodeBitstreamParams& params, int& currentEncodeLevel, - const ViewFrustum::intersection& parentLocationThisView) const; - static bool countOctreeElementsOperation(const OctreeElementPointer& element, void* extraData); OctreeElementPointer nodeForOctalCode(const OctreeElementPointer& ancestorElement, const unsigned char* needleCode, OctreeElementPointer* parentOfFoundElement) const; @@ -374,7 +284,6 @@ protected: bool _isDirty; bool _shouldReaverage; - bool _stopImport; bool _isViewing; bool _isServer; diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index ef45e8b7ba..a666ba0426 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -457,33 +457,6 @@ ViewFrustum::intersection OctreeElement::computeViewIntersection(const ViewFrust return viewFrustum.calculateCubeKeyholeIntersection(_cube); } -// There are two types of nodes for which we want to "render" -// 1) Leaves that are in the LOD -// 2) Non-leaves are more complicated though... usually you don't want to render them, but if their children -// wouldn't be rendered, then you do want to render them. But sometimes they have some children that ARE -// in the LOD, and others that are not. In this case we want to render the parent, and none of the children. -// -// Since, if we know the camera position and orientation, we can know which of the corners is the "furthest" -// corner. We can use we can use this corner as our "voxel position" to do our distance calculations off of. -// By doing this, we don't need to test each child voxel's position vs the LOD boundary -bool OctreeElement::calculateShouldRender(const ViewFrustum& viewFrustum, float voxelScaleSize, int boundaryLevelAdjust) const { - bool shouldRender = false; - - if (hasContent()) { - float furthestDistance = furthestDistanceToCamera(viewFrustum); - float childBoundary = boundaryDistanceForRenderLevel(getLevel() + 1 + boundaryLevelAdjust, voxelScaleSize); - bool inChildBoundary = (furthestDistance <= childBoundary); - if (hasDetailedContent() && inChildBoundary) { - shouldRender = true; - } else { - float boundary = childBoundary * 2.0f; // the boundary is always twice the distance of the child boundary - bool inBoundary = (furthestDistance <= boundary); - shouldRender = inBoundary && !inChildBoundary; - } - } - return shouldRender; -} - // Calculates the distance to the furthest point of the voxel to the camera // does as much math as possible in voxel scale and then scales up to TREE_SCALE at end float OctreeElement::furthestDistanceToCamera(const ViewFrustum& viewFrustum) const { diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 514039713b..b7857c3e6c 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -85,16 +85,6 @@ public: typedef enum { COMPLETED, PARTIAL, NONE } AppendState; virtual void debugExtraEncodeData(EncodeBitstreamParams& params) const { } - virtual void initializeExtraEncodeData(EncodeBitstreamParams& params) { } - virtual bool shouldIncludeChildData(int childIndex, EncodeBitstreamParams& params) const { return true; } - virtual bool shouldRecurseChildTree(int childIndex, EncodeBitstreamParams& params) const { return true; } - - virtual void updateEncodedData(int childIndex, AppendState childAppendState, EncodeBitstreamParams& params) const { } - virtual void elementEncodeComplete(EncodeBitstreamParams& params) const { } - - /// Override to serialize the state of this element. This is used for persistance and for transmission across the network. - virtual AppendState appendElementData(OctreePacketData* packetData, EncodeBitstreamParams& params) const - { return COMPLETED; } /// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading /// from the network. @@ -139,9 +129,6 @@ public: float distanceToCamera(const ViewFrustum& viewFrustum) const; float furthestDistanceToCamera(const ViewFrustum& viewFrustum) const; - bool calculateShouldRender(const ViewFrustum& viewFrustum, - float voxelSizeScale = DEFAULT_OCTREE_SIZE_SCALE, int boundaryLevelAdjust = 0) const; - // points are assumed to be in Voxel Coordinates (not TREE_SCALE'd) float distanceSquareToPoint(const glm::vec3& point) const; // when you don't need the actual distance, use this. float distanceToPoint(const glm::vec3& point) const; diff --git a/libraries/octree/src/OctreeElementBag.cpp b/libraries/octree/src/OctreeElementBag.cpp deleted file mode 100644 index afd2d5cdc3..0000000000 --- a/libraries/octree/src/OctreeElementBag.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// OctreeElementBag.cpp -// libraries/octree/src -// -// Created by Brad Hefta-Gaub on 4/25/2013. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OctreeElementBag.h" -#include - -void OctreeElementBag::deleteAll() { - _bagElements = Bag(); -} - -/// does the bag contain elements? -/// if all of the contained elements are expired, they will not report as empty, and -/// a single last item will be returned by extract as a null pointer -bool OctreeElementBag::isEmpty() { - return _bagElements.empty(); -} - -void OctreeElementBag::insert(const OctreeElementPointer& element) { - _bagElements[element.get()] = element; -} - -OctreeElementPointer OctreeElementBag::extract() { - OctreeElementPointer result; - - // Find the first element still alive - Bag::iterator it = _bagElements.begin(); - while (it != _bagElements.end() && !result) { - result = it->second.lock(); - it = _bagElements.erase(it); - } - return result; -} diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h index 34c49f1e60..a79648f596 100644 --- a/libraries/octree/src/OctreeElementBag.h +++ b/libraries/octree/src/OctreeElementBag.h @@ -16,30 +16,8 @@ #ifndef hifi_OctreeElementBag_h #define hifi_OctreeElementBag_h -#include - #include "OctreeElement.h" -class OctreeElementBag { - using Bag = std::unordered_map; - -public: - void insert(const OctreeElementPointer& element); // put a element into the bag - - OctreeElementPointer extract(); /// pull a element out of the bag (could come in any order) and if all of the - /// elements have expired, a single null pointer will be returned - - bool isEmpty(); /// does the bag contain elements, - /// if all of the contained elements are expired, they will not report as empty, and - /// a single last item will be returned by extract as a null pointer - - void deleteAll(); - size_t size() const { return _bagElements.size(); } - -private: - Bag _bagElements; -}; - class OctreeElementExtraEncodeDataBase { public: OctreeElementExtraEncodeDataBase() {} diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 65b30dd197..0808e817ed 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -117,7 +117,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, - sourceUUID, sourceNode, false, message.getVersion()); + sourceUUID, sourceNode); quint64 startUncompress, startLock = usecTimestampNow(); quint64 startReadBitsteam, endReadBitsteam; // FIXME STUTTER - there may be an opportunity to bump this lock outside of the diff --git a/libraries/octree/src/OctreeQueryNode.cpp b/libraries/octree/src/OctreeQueryNode.cpp index f0c9027493..16542b697e 100644 --- a/libraries/octree/src/OctreeQueryNode.cpp +++ b/libraries/octree/src/OctreeQueryNode.cpp @@ -144,11 +144,6 @@ void OctreeQueryNode::copyCurrentViewFrustum(ViewFrustum& viewOut) const { viewOut = _currentViewFrustum; } -void OctreeQueryNode::copyLastKnownViewFrustum(ViewFrustum& viewOut) const { - QMutexLocker viewLocker(&_viewMutex); - viewOut = _lastKnownViewFrustum; -} - bool OctreeQueryNode::updateCurrentViewFrustum() { // if shutting down, return immediately if (_isShuttingDown) { @@ -229,70 +224,6 @@ void OctreeQueryNode::setViewSent(bool viewSent) { } } -void OctreeQueryNode::updateLastKnownViewFrustum() { - // if shutting down, return immediately - if (_isShuttingDown) { - return; - } - - { - QMutexLocker viewLocker(&_viewMutex); - bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum); - - if (frustumChanges) { - // save our currentViewFrustum into our lastKnownViewFrustum - _lastKnownViewFrustum = _currentViewFrustum; - } - } - - // save that we know the view has been sent. - setLastTimeBagEmpty(); -} - - -bool OctreeQueryNode::moveShouldDump() const { - // if shutting down, return immediately - if (_isShuttingDown) { - return false; - } - - QMutexLocker viewLocker(&_viewMutex); - glm::vec3 oldPosition = _lastKnownViewFrustum.getPosition(); - glm::vec3 newPosition = _currentViewFrustum.getPosition(); - - // theoretically we could make this slightly larger but relative to avatar scale. - const float MAXIMUM_MOVE_WITHOUT_DUMP = 0.0f; - return glm::distance(newPosition, oldPosition) > MAXIMUM_MOVE_WITHOUT_DUMP; -} - -void OctreeQueryNode::dumpOutOfView() { - // if shutting down, return immediately - if (_isShuttingDown) { - return; - } - - int stillInView = 0; - int outOfView = 0; - OctreeElementBag tempBag; - ViewFrustum viewCopy; - copyCurrentViewFrustum(viewCopy); - while (OctreeElementPointer elementToCheck = elementBag.extract()) { - if (elementToCheck->isInView(viewCopy)) { - tempBag.insert(elementToCheck); - stillInView++; - } else { - outOfView++; - } - } - if (stillInView > 0) { - while (OctreeElementPointer elementToKeepInBag = tempBag.extract()) { - if (elementToKeepInBag->isInView(viewCopy)) { - elementBag.insert(elementToKeepInBag); - } - } - } -} - void OctreeQueryNode::packetSent(const NLPacket& packet) { _sentPacketHistory.packetSent(_sequenceNumber, packet); _sequenceNumber++; diff --git a/libraries/octree/src/OctreeQueryNode.h b/libraries/octree/src/OctreeQueryNode.h index fd89a89949..640a7c7ddc 100644 --- a/libraries/octree/src/OctreeQueryNode.h +++ b/libraries/octree/src/OctreeQueryNode.h @@ -46,23 +46,14 @@ public: bool shouldSuppressDuplicatePacket(); unsigned int getAvailable() const { return _octreePacket->bytesAvailableForWrite(); } - int getMaxSearchLevel() const { return _maxSearchLevel; } - void resetMaxSearchLevel() { _maxSearchLevel = 1; } - void incrementMaxSearchLevel() { _maxSearchLevel++; } - int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; } - void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; } - - OctreeElementBag elementBag; OctreeElementExtraEncodeData extraEncodeData; void copyCurrentViewFrustum(ViewFrustum& viewOut) const; - void copyLastKnownViewFrustum(ViewFrustum& viewOut) const; // These are not classic setters because they are calculating and maintaining state // which is set asynchronously through the network receive bool updateCurrentViewFrustum(); - void updateLastKnownViewFrustum(); bool getViewSent() const { return _viewSent; } void setViewSent(bool viewSent); @@ -70,24 +61,13 @@ public: bool getViewFrustumChanging() const { return _viewFrustumChanging; } bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; } - bool moveShouldDump() const; - - quint64 getLastTimeBagEmpty() const { return _lastTimeBagEmpty; } - void setLastTimeBagEmpty() { _lastTimeBagEmpty = _sceneSendStartTime; } - bool hasLodChanged() const { return _lodChanged; } OctreeSceneStats stats; - void dumpOutOfView(); - - quint64 getLastRootTimestamp() const { return _lastRootTimestamp; } - void setLastRootTimestamp(quint64 timestamp) { _lastRootTimestamp = timestamp; } unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; } int getDuplicatePacketCount() const { return _duplicatePacketCount; } - void sceneStart(quint64 sceneSendStartTime) { _sceneSendStartTime = sceneSendStartTime; } - void nodeKilled(); bool isShuttingDown() const { return _isShuttingDown; } @@ -118,18 +98,11 @@ private: int _duplicatePacketCount { 0 }; quint64 _firstSuppressedPacket { usecTimestampNow() }; - int _maxSearchLevel { 1 }; - int _maxLevelReachedInLastSearch { 1 }; - mutable QMutex _viewMutex { QMutex::Recursive }; ViewFrustum _currentViewFrustum; - ViewFrustum _lastKnownViewFrustum; - quint64 _lastTimeBagEmpty { 0 }; bool _viewFrustumChanging { false }; bool _viewFrustumJustStoppedChanging { true }; - OctreeSendThread* _octreeSendThread { nullptr }; - // watch for LOD changes int _lastClientBoundaryLevelAdjust { 0 }; float _lastClientOctreeSizeScale { DEFAULT_OCTREE_SIZE_SCALE }; @@ -138,16 +111,12 @@ private: OCTREE_PACKET_SEQUENCE _sequenceNumber { 0 }; - quint64 _lastRootTimestamp { 0 }; - PacketType _myPacketType { PacketType::Unknown }; bool _isShuttingDown { false }; SentPacketHistory _sentPacketHistory; QQueue _nackedSequenceNumbers; - quint64 _sceneSendStartTime = 0; - std::array _lastOctreePayload; QJsonObject _lastCheckJSONParameters; diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 117f3e0385..b2efdfd595 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -284,10 +284,6 @@ void OctreeSceneStats::didntFit(const OctreeElementPointer& element) { } } -void OctreeSceneStats::colorBitsWritten() { - _colorBitsWritten++; -} - void OctreeSceneStats::existsBitsWritten() { _existsBitsWritten++; } diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 78b4dfd26f..e1228609a6 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -79,9 +79,6 @@ public: /// Track that a element was due to be sent, but didn't fit in the packet and was moved to next packet void didntFit(const OctreeElementPointer& element); - /// Track that the color bitmask was was sent as part of computation of a scene - void colorBitsWritten(); - /// Track that the exists in tree bitmask was was sent as part of computation of a scene void existsBitsWritten(); diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index ae4338be6f..b1ceab4149 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -248,22 +248,6 @@ void setOctalCodeSectionValue(unsigned char* octalCode, int section, char sectio } } -unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels) { - int codeLength = numberOfThreeBitSectionsInCode(originalOctalCode); - unsigned char* newCode = NULL; - if (codeLength > chopLevels) { - int newLength = codeLength - chopLevels; - newCode = new unsigned char[newLength+1]; - *newCode = newLength; // set the length byte - - for (int section = chopLevels; section < codeLength; section++) { - char sectionValue = getOctalCodeSectionValue(originalOctalCode, section); - setOctalCodeSectionValue(newCode, section - chopLevels, sectionValue); - } - } - return newCode; -} - bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild) { if (!possibleAncestor || !possibleDescendent) { return false; diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 89c5e6d74e..63cbc58cfa 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -40,8 +40,6 @@ const int UNKNOWN_OCTCODE_LENGTH = -2; /// \param int maxBytes number of bytes that octalCode is expected to be, -1 if unknown int numberOfThreeBitSectionsInCode(const unsigned char* octalCode, int maxBytes = UNKNOWN_OCTCODE_LENGTH); -unsigned char* chopOctalCode(const unsigned char* originalOctalCode, int chopLevels); - const int CHECK_NODE_ONLY = -1; bool isAncestorOf(const unsigned char* possibleAncestor, const unsigned char* possibleDescendent, int descendentsChild = CHECK_NODE_ONLY); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6da3592999..6d2b1f129b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1303,7 +1303,7 @@ Script.scriptEnding.connect(function () { Settings.setValue(SETTING_EDIT_PREFIX + MENU_CREATE_ENTITIES_GRABBABLE, Menu.isOptionChecked(MENU_CREATE_ENTITIES_GRABBABLE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LARGE, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LARGE)); Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_SMALL, Menu.isOptionChecked(MENU_ALLOW_SELECTION_SMALL)); - Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)); + Settings.setValue(SETTING_EDIT_PREFIX + MENU_ALLOW_SELECTION_LIGHTS, Menu.isOptionChecked(MENU_ALLOW_SELECTION_LIGHTS)); progressDialog.cleanup(); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index a7186f55bd..783b91f5f0 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -15,7 +15,7 @@ Script.include(Script.resolvePath("../libraries/controllers.js")); Script.include(Script.resolvePath("../libraries/Xform.js")); var Y_AXIS = {x: 0, y: 1, z: 0}; -var DEFAULT_DPI = 34; +var DEFAULT_DPI = 31; var DEFAULT_WIDTH = 0.4375; var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees var SENSOR_TO_ROOM_MATRIX = -2; @@ -31,12 +31,12 @@ var DELAY_FOR_30HZ = 33; // milliseconds // will need to be recaclulated if dimensions of fbx model change. -var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; +var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; -var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; +var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx"; // returns object with two fields: // * position - position in front of the user @@ -44,14 +44,28 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home- function calcSpawnInfo(hand, landscape) { var finalPosition; + var LEFT_HAND = Controller.Standard.LeftHand; + var sensorToWorldScale = MyAvatar.sensorToWorldScale; var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; - var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; - var dominantHandRotation = MyAvatar.getDominantHand() === "right" ? -20 : 20; - var offsetRotation = Quat.fromPitchYawRollDegrees(0, dominantHandRotation, 0); - var forward = Vec3.multiplyQbyV(offsetRotation, Quat.getForward(Quat.cancelOutRollAndPitch(headRot))); - var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale; - finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); - var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); + var headRot = Quat.cancelOutRollAndPitch((HMD.active && Camera.mode === "first person") ? + HMD.orientation : Camera.orientation); + + var right = Quat.getRight(headRot); + var forward = Quat.getForward(headRot); + var up = Quat.getUp(headRot); + + var FORWARD_OFFSET = 0.5 * sensorToWorldScale; + var UP_OFFSET = -0.16 * sensorToWorldScale; + var RIGHT_OFFSET = ((hand === LEFT_HAND) ? -0.18 : 0.18) * sensorToWorldScale; + + var forwardPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); + var lateralPosition = Vec3.sum(forwardPosition, Vec3.multiply(RIGHT_OFFSET, right)); + finalPosition = Vec3.sum(lateralPosition, Vec3.multiply(UP_OFFSET, up)); + + var MY_EYES = { x: 0.0, y: 0.15, z: 0.0 }; + var lookAtEndPosition = Vec3.sum(Vec3.multiply(RIGHT_OFFSET, right), Vec3.multiply(FORWARD_OFFSET, forward)); + var orientation = Quat.lookAt(MY_EYES, lookAtEndPosition, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); + return { position: finalPosition, rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180) @@ -119,11 +133,11 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var RAYPICK_OFFSET = 0.0001; // Sufficient for raypick to reliably intersect tablet screen before tablet model. + var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 0.004; - var screenWidth = 0.82 * tabletWidth; - var screenHeight = 0.81 * tabletHeight; + var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; + var screenWidth = 0.9275 * tabletWidth; + var screenHeight = 0.8983 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, @@ -139,7 +153,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003; + var HOME_BUTTON_Y_OFFSET = (tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; this.homeButtonID = Overlays.addOverlay("circle3d", { @@ -277,8 +291,8 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; - var screenWidth = 0.82 * tabletWidth; - var screenHeight = 0.81 * tabletHeight; + var screenWidth = 0.9275 * tabletWidth; + var screenHeight = 0.8983 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { rotation: Quat.multiply(cameraOrientation, ROT_LANDSCAPE_WINDOW), dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 6afde85c29..7e9e1d7e6a 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -373,7 +373,6 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) { return; } - var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; var sensorScaleOffsetOverride = 1; var SENSOR_TO_ROOM_MATRIX = 65534; @@ -383,8 +382,8 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) } // will need to be recaclulated if dimensions of fbx model change. - var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; - var DEFAULT_DPI = 34; + var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; + var DEFAULT_DPI = 31; var DEFAULT_WIDTH = 0.4375; // scale factor of natural tablet dimensions. @@ -402,9 +401,10 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) // update webOverlay var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride; - var screenWidth = 0.82 * tabletWidth; - var screenHeight = 0.81 * tabletHeight; + var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; + print(WEB_ENTITY_Y_OFFSET); + var screenWidth = 0.9275 * tabletWidth; + var screenHeight = 0.8983 * tabletHeight; var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; Overlays.editOverlay(HMD.tabletScreenID, { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, @@ -413,7 +413,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update homeButton - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) - 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; Overlays.editOverlay(HMD.homeButtonID, { diff --git a/server-console/package-lock.json b/server-console/package-lock.json index e25fd3cded..4311fde51a 100644 --- a/server-console/package-lock.json +++ b/server-console/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.2.tgz", + "integrity": "sha512-A6Uv1anbsCvrRDtaUXS2xZ5tlzD+Kg7yMRlSLFDy3z0r7KlGXDzL14vELXIAgpk2aJbU3XeZZQRcEkLkowT92g==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -40,12 +46,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, "array-find-index": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz", @@ -148,12 +148,6 @@ "lru-cache": "4.0.1" } }, - "balanced-match": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz", - "integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=", - "dev": true - }, "base64-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", @@ -217,16 +211,6 @@ "hoek": "2.16.3" } }, - "brace-expansion": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", - "integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=", - "dev": true, - "requires": { - "balanced-match": "0.3.0", - "concat-map": "0.0.1" - } - }, "buffers": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", @@ -515,29 +499,70 @@ "jsbn": "0.1.0" } }, - "electron-download": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-2.1.1.tgz", - "integrity": "sha1-AH07HyrTco0nzP5PhJayY/kTijE=", + "electron": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/electron/-/electron-1.8.4.tgz", + "integrity": "sha512-2f1cx0G3riMFODXFftF5AHXy+oHfhpntZHTDN66Hxtl09gmEr42B3piNEod9MEmw72f75LX2JfeYceqq1PF8cA==", "dev": true, "requires": { - "debug": "2.2.0", - "home-path": "1.0.3", + "@types/node": "8.10.2", + "electron-download": "3.3.0", + "extract-zip": "1.5.0" + } + }, + "electron-download": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-3.3.0.tgz", + "integrity": "sha1-LP1U1pZsAZxNSa1l++Zcyc3vaMg=", + "dev": true, + "requires": { + "debug": "2.6.9", + "fs-extra": "0.30.0", + "home-path": "1.0.5", "minimist": "1.2.0", - "mkdirp": "0.5.1", - "mv": "2.1.1", - "nugget": "1.6.2", - "path-exists": "1.0.0", - "rc": "1.1.6" + "nugget": "2.0.1", + "path-exists": "2.1.0", + "rc": "1.1.6", + "semver": "5.5.0", + "sumchecker": "1.3.1" }, "dependencies": { "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" + } + }, + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "2.2.3", + "klaw": "1.3.1", + "path-is-absolute": "1.0.0", + "rimraf": "2.6.2" + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "sumchecker": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-1.3.1.tgz", + "integrity": "sha1-ebs7RFbdBPGOvbwNcDodHa7FEF0=", + "dev": true, + "requires": { + "debug": "2.6.9", + "es6-promise": "4.2.4" } } } @@ -579,9 +604,9 @@ } }, "electron-packager": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-11.0.0.tgz", - "integrity": "sha512-ufyYMe3Gt6IEZm9RuG+KK3Nh+V2jZHWg9gihp8wylUNtleQihECIXtQdpPJxH9740XFERVPraNEaa7cZvDzpyw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-12.0.0.tgz", + "integrity": "sha1-uC0k14ovIUA7v9FmpbFWmJTVzQw=", "dev": true, "requires": { "asar": "0.14.2", @@ -590,20 +615,25 @@ "electron-osx-sign": "0.4.8", "extract-zip": "1.5.0", "fs-extra": "5.0.0", + "galactus": "0.2.0", "get-package-info": "1.0.0", - "mz": "2.7.0", "nodeify": "1.0.1", "parse-author": "2.0.0", "pify": "3.0.0", "plist": "2.1.0", - "pruner": "0.0.7", - "rcedit": "0.9.0", + "rcedit": "1.0.0", "resolve": "1.5.0", "sanitize-filename": "1.6.1", "semver": "5.5.0", - "yargs-parser": "8.1.0" + "yargs-parser": "9.0.2" }, "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -726,6 +756,12 @@ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, + "rcedit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.0.0.tgz", + "integrity": "sha512-W7DNa34x/3OgWyDHsI172AG/Lr/lZ+PkavFkHj0QhhkBRcV9QTmRJE1tDKrWkx8XHPSBsmZkNv9OKue6pncLFQ==", + "dev": true + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -746,19 +782,18 @@ "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", "dev": true + }, + "yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + } } } }, - "electron-prebuilt": { - "version": "0.37.5", - "resolved": "https://registry.npmjs.org/electron-prebuilt/-/electron-prebuilt-0.37.5.tgz", - "integrity": "sha1-OkGJgod4FdOnrB+bLi9KcPQg/3A=", - "dev": true, - "requires": { - "electron-download": "2.1.1", - "extract-zip": "1.5.0" - } - }, "end-of-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", @@ -787,6 +822,12 @@ "is-arrayish": "0.2.1" } }, + "es6-promise": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -873,6 +914,62 @@ } } }, + "flora-colossus": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-0.0.2.tgz", + "integrity": "sha1-fRvimh8X+k8isb1hSC+Gw04HuQE=", + "dev": true, + "requires": { + "debug": "3.1.0", + "fs-extra": "4.0.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, + "optional": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -904,6 +1001,63 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "galactus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-0.2.0.tgz", + "integrity": "sha1-w9Y7pVAkZv5A6mfMaJCFs90kqPw=", + "dev": true, + "requires": { + "debug": "3.1.0", + "flora-colossus": "0.0.2", + "fs-extra": "4.0.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true, + "optional": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "generate-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", @@ -1107,9 +1261,9 @@ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" }, "home-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.3.tgz", - "integrity": "sha1-ns5Z/sPwMubRC1Q0/uJk30wt4y8=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.5.tgz", + "integrity": "sha1-eIspgVsS1Tus9XVkhHbm+QQdEz8=", "dev": true }, "hosted-git-info": { @@ -1461,15 +1615,6 @@ "mime-db": "1.22.0" } }, - "minimatch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", - "integrity": "sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=", - "dev": true, - "requires": { - "brace-expansion": "1.1.3" - } - }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -1723,61 +1868,9 @@ } }, "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "dev": true, - "requires": { - "mkdirp": "0.5.1", - "ncp": "2.0.0", - "rimraf": "2.4.5" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "1.0.4", - "inherits": "2.0.1", - "minimatch": "3.0.0", - "once": "1.3.3", - "path-is-absolute": "1.0.0" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "dev": true, - "requires": { - "glob": "6.0.4" - } - } - } - }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "1.3.0", - "object-assign": "4.0.1", - "thenify-all": "1.6.0" - } - }, - "ncp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "node-notifier": { @@ -1843,27 +1936,27 @@ } }, "nugget": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz", - "integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-2.0.1.tgz", + "integrity": "sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA=", "dev": true, "requires": { - "debug": "2.2.0", + "debug": "2.6.9", "minimist": "1.2.0", "pretty-bytes": "1.0.4", "progress-stream": "1.2.0", "request": "2.71.0", - "single-line-log": "0.4.1", + "single-line-log": "1.1.2", "throttleit": "0.0.2" }, "dependencies": { "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" } }, "throttleit": { @@ -1966,10 +2059,13 @@ } }, "path-exists": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", - "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } }, "path-is-absolute": { "version": "1.0.0", @@ -2070,46 +2166,6 @@ "is-promise": "1.0.1" } }, - "pruner": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/pruner/-/pruner-0.0.7.tgz", - "integrity": "sha1-NF+8s+gHARY6HXrfVrrCKaWh5ME=", - "dev": true, - "requires": { - "fs-extra": "4.0.3" - }, - "dependencies": { - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "4.1.3", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11" - }, - "dependencies": { - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true, - "optional": true - } - } - } - } - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -2153,12 +2209,6 @@ "strip-json-comments": "1.0.4" } }, - "rcedit": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.9.0.tgz", - "integrity": "sha1-ORDfVzRTmeKwMl9KUZAH+J5V7xw=", - "dev": true - }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -2288,10 +2338,13 @@ "dev": true }, "single-line-log": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", - "integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-1.1.2.tgz", + "integrity": "sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q=", + "dev": true, + "requires": { + "string-width": "1.0.1" + } }, "sntp": { "version": "1.0.9", @@ -2476,24 +2529,6 @@ } } }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "1.3.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "dev": true, - "requires": { - "thenify": "3.3.0" - } - }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -2694,23 +2729,6 @@ "y18n": "3.2.1" } }, - "yargs-parser": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", - "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", - "dev": true, - "requires": { - "camelcase": "4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, "yauzl": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", diff --git a/server-console/package.json b/server-console/package.json index 0b13eeb2a8..2428d2574e 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -8,8 +8,8 @@ "" ], "devDependencies": { - "electron-packager": "^11.0.0", - "electron-prebuilt": "0.37.5" + "electron-packager": "^12.0.0", + "electron": "1.8.4" }, "repository": { "type": "git", @@ -25,6 +25,7 @@ "dependencies": { "always-tail": "0.2.0", "cheerio": "^0.19.0", + "electron-log": "1.1.1", "extend": "^3.0.0", "fs-extra": "^1.0.0", "node-notifier": "^5.2.1", @@ -32,7 +33,6 @@ "request": "^2.67.0", "request-progress": "1.0.2", "tar-fs": "^1.12.0", - "yargs": "^3.30.0", - "electron-log": "1.1.1" + "yargs": "^3.30.0" } } diff --git a/server-console/src/downloader.js b/server-console/src/downloader.js index f7e67f03ce..7dc9223802 100644 --- a/server-console/src/downloader.js +++ b/server-console/src/downloader.js @@ -2,7 +2,7 @@ function ready() { console.log("Ready"); const electron = require('electron'); - const remote = require('remote'); + const remote = electron.remote; window.$ = require('./vendor/jquery/jquery-2.1.4.min.js'); $(".state").hide(); diff --git a/server-console/src/main.js b/server-console/src/main.js index efa04a8512..b08db6222f 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -8,9 +8,9 @@ const nativeImage = electron.nativeImage; const notifier = require('node-notifier'); const util = require('util'); const dialog = electron.dialog; -const Menu = require('menu'); -const Tray = require('tray'); -const shell = require('shell'); +const Menu = electron.Menu; +const Tray = electron.Tray; +const shell = electron.shell; const os = require('os'); const childProcess = require('child_process'); const path = require('path'); diff --git a/server-console/src/modules/hf-process.js b/server-console/src/modules/hf-process.js index 767befec7b..797ee38a0d 100644 --- a/server-console/src/modules/hf-process.js +++ b/server-console/src/modules/hf-process.js @@ -226,7 +226,7 @@ Process.prototype = extend(Process.prototype, { } }); } else { - var signal = force ? 'SIGKILL' : null; + var signal = force ? 'SIGKILL' : 'SIGTERM'; this.child.kill(signal); } diff --git a/tests/entities/src/main.cpp b/tests/entities/src/main.cpp index bf79f9d3e9..43a5d2e48d 100644 --- a/tests/entities/src/main.cpp +++ b/tests/entities/src/main.cpp @@ -160,7 +160,6 @@ int main(int argc, char** argv) { QByteArray packet = file.readAll(); EntityItemPointer item = ShapeEntityItem::boxFactory(EntityItemID(), EntityItemProperties()); ReadBitstreamToTreeParams params; - params.bitstreamVersion = 33; auto start = usecTimestampNow(); for (int i = 0; i < 1000; ++i) {