From 0758b60afc855d77ac328d10a798d4ebd37d94b3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 9 Aug 2017 13:14:36 -0700 Subject: [PATCH] abstract DiffTraversal out of EntityTreeSendThread --- .../src/entities/EntityPriorityQueue.cpp | 108 +------- .../src/entities/EntityPriorityQueue.h | 28 +- .../src/entities/EntityTreeSendThread.cpp | 173 +++---------- .../src/entities/EntityTreeSendThread.h | 16 +- libraries/entities/src/DiffTraversal.cpp | 239 ++++++++++++++++++ libraries/entities/src/DiffTraversal.h | 78 ++++++ 6 files changed, 366 insertions(+), 276 deletions(-) create mode 100644 libraries/entities/src/DiffTraversal.cpp create mode 100644 libraries/entities/src/DiffTraversal.h diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp index 94082593bc..77b46afa24 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.cpp +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -14,7 +14,7 @@ const float DO_NOT_SEND = -1.0e-6f; void ConicalView::set(const ViewFrustum& viewFrustum) { - // The ConicalView has two parts: a central sphere (same as ViewFrustm) and a circular cone that bounds the frustum part. + // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part. // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. _position = viewFrustum.getPosition(); _direction = viewFrustum.getDirection(); @@ -72,109 +72,3 @@ float PrioritizedEntity::updatePriority(const ConicalView& conicalView) { } return _priority; } - -TraversalWaypoint::TraversalWaypoint(EntityTreeElementPointer& element) : _nextIndex(0) { - assert(element); - _weakElement = element; -} - -void TraversalWaypoint::getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view) { - // TODO: add LOD culling of elements - // NOTE: no need to set next.intersection in the "FirstTime" context - if (_nextIndex == -1) { - // only get here for the root TraversalWaypoint at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - next.element = _weakElement.lock(); - return; - } else if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { - next.element = nextElement; - return; - } - } - } - } - next.element.reset(); -} - -void TraversalWaypoint::getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { - // TODO: add LOD culling of elements - if (_nextIndex == -1) { - // only get here for the root TraversalWaypoint at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed - if (element->getLastChangedContent() > lastTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement && nextElement->getLastChanged() > lastTime) { - ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); - if (intersection != ViewFrustum::OUTSIDE) { - next.element = nextElement; - next.intersection = intersection; - return; - } - } - } - } - } - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; -} - -void TraversalWaypoint::getNextVisibleElementDifferential(VisibleElement& next, - const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { - // TODO: add LOD culling of elements - if (_nextIndex == -1) { - // only get here for the root TraversalWaypoint at the very beginning of traversal - // safe to assume this element intersects view - ++_nextIndex; - EntityTreeElementPointer element = _weakElement.lock(); - // root case is special: its intersection is always INTERSECT - // and we can skip it if the content hasn't changed - if (element->getLastChangedContent() > lastTime) { - next.element = element; - next.intersection = ViewFrustum::INTERSECT; - return; - } - } - if (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer element = _weakElement.lock(); - if (element) { - while (_nextIndex < NUMBER_OF_CHILDREN) { - EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); - ++_nextIndex; - if (nextElement) { - AACube cube = nextElement->getAACube(); - // NOTE: for differential case next.intersection is against the _completedView - ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); - if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && - !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { - next.element = nextElement; - next.intersection = intersection; - return; - } - } - } - } - } - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; -} diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h index 8eb28dffda..215c5262bf 100644 --- a/assignment-client/src/entities/EntityPriorityQueue.h +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -15,8 +15,7 @@ #include #include - -#include "EntityTreeElement.h" +#include const float SQRT_TWO_OVER_TWO = 0.7071067811865f; const float DEFAULT_VIEW_RADIUS = 10.0f; @@ -56,31 +55,6 @@ private: float _priority; }; -// VisibleElement is a struct identifying an element and how it intersected the view. -// The intersection is used to optimize culling entities from the sendQueue. -class VisibleElement { -public: - EntityTreeElementPointer element; - ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; -}; - -// TraversalWaypoint is an bookmark in a "path" of waypoints during a traversal. -class TraversalWaypoint { -public: - TraversalWaypoint(EntityTreeElementPointer& element); - - void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); - void getNextVisibleElementAgain(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); - void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); - - int8_t getNextIndex() const { return _nextIndex; } - void initRootNextIndex() { _nextIndex = -1; } - -protected: - EntityTreeElementWeakPointer _weakElement; - int8_t _nextIndex; -}; - using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; #endif // hifi_EntityPriorityQueue_h diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 7d6baaca3b..031d7ac3fb 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -10,19 +10,12 @@ // #include "EntityTreeSendThread.h" -#include // adebug #include #include #include "EntityServer.h" -EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) - : OctreeSendThread(myServer, node) { - const int32_t MIN_PATH_DEPTH = 16; - _traversalPath.reserve(MIN_PATH_DEPTH); -} - void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -90,14 +83,14 @@ void EntityTreeSendThread::preDistributionProcessing() { void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL - { - // DEBUG HACK: trigger traversal (Again) every so often - const uint64_t TRAVERSE_AGAIN_PERIOD = 2 * USECS_PER_SECOND; - if (!viewFrustumChanged && usecTimestampNow() > _startOfCompletedTraversal + TRAVERSE_AGAIN_PERIOD) { - viewFrustumChanged = true; - } - } if (nodeData->getUsesFrustum()) { + { + // DEBUG HACK: trigger traversal (Again) every so often + const uint64_t TRAVERSE_AGAIN_PERIOD = 4 * USECS_PER_SECOND; + if (!viewFrustumChanged && usecTimestampNow() > _traversal.getStartOfCompletedTraversal() + TRAVERSE_AGAIN_PERIOD) { + viewFrustumChanged = true; + } + } if (viewFrustumChanged) { ViewFrustum viewFrustum; nodeData->copyCurrentViewFrustum(viewFrustum); @@ -105,28 +98,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O startNewTraversal(viewFrustum, root); } } - if (!_traversalPath.empty()) { + if (!_traversal.finished()) { uint64_t startTime = usecTimestampNow(); - uint64_t now = startTime; - VisibleElement next; - getNextVisibleElement(next); - while (next.element) { - if (next.element->hasContent()) { - _scanNextElementCallback(next); - } + const uint64_t TIME_BUDGET = 100000; // usec + _traversal.traverse(TIME_BUDGET); - // TODO: pick a reasonable budget for each partial traversal - const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 100000; // usec - now = usecTimestampNow(); - if (now - startTime > PARTIAL_TRAVERSAL_TIME_BUDGET) { - break; - } - getNextVisibleElement(next); - } - - uint64_t dt = now - startTime; - std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug + uint64_t dt = usecTimestampNow() - startTime; + std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug } if (!_sendQueue.empty()) { // print what needs to be sent @@ -134,11 +113,9 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O PrioritizedEntity entry = _sendQueue.top(); EntityItemPointer entity = entry.getEntity(); if (entity) { - std::cout << "adebug '" << entity->getName().toStdString() << "'" - << " : " << entry.getPriority() << std::endl; // adebug + std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug } _sendQueue.pop(); - std::cout << "adebug" << std::endl; // adebug } } // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL @@ -195,39 +172,29 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } -void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { +void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root) { + DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root); // there are three types of traversal: // // (1) FirstTime = at login --> find everything in view - // (2) Again = view hasn't changed --> find what has changed since last complete traversal + // (2) Repeat = view hasn't changed --> find what has changed since last complete traversal // (3) Differential = view has changed --> find what has changed or in new view but not old // - // For each traversal type we define two callback lambdas: - // - // _getNextVisibleElementCallback = identifies elements that need to be traversed,i - // updates VisibleElement ref argument with pointer-to-element and view-intersection - // (INSIDE, INTERSECT, or OUTSIDE) - // - // _scanNextElementCallback = identifies entities that need to be appended to _sendQueue + // The "scanCallback" we provide to the traversal depends on the type: // // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient // computation of entity sorting priorities. // - if (_startOfCompletedTraversal == 0) { - // first time - _currentView = viewFrustum; - _conicalView.set(_currentView); + _conicalView.set(_traversal.getCurrentView()); - _getNextVisibleElementCallback = [&](VisibleElement& next) { - _traversalPath.back().getNextVisibleElementFirstTime(next, _currentView); - }; - - _scanNextElementCallback = [&](VisibleElement& next) { + switch (type) { + case DiffTraversal::First: + _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { next.element->forEachEntity([&](EntityItemPointer entity) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_currentView.cubeIntersectsKeyhole(cube)) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } @@ -236,21 +203,18 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); } }); - }; - } else if (_currentView.isVerySimilar(viewFrustum)) { - // again - _getNextVisibleElementCallback = [&](VisibleElement& next) { - _traversalPath.back().getNextVisibleElementAgain(next, _currentView, _startOfCompletedTraversal); - }; - - _scanNextElementCallback = [&](VisibleElement& next) { - if (next.element->getLastChangedContent() > _startOfCompletedTraversal) { + }); + break; + case DiffTraversal::Repeat: + _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { + if (next.element->getLastChangedContent() > _traversal.getStartOfCompletedTraversal()) { + uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); next.element->forEachEntity([&](EntityItemPointer entity) { - if (entity->getLastEdited() > _startOfCompletedTraversal) { + if (entity->getLastEdited() > timestamp) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (next.intersection == ViewFrustum::INSIDE || _currentView.cubeIntersectsKeyhole(cube)) { + if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } @@ -261,26 +225,20 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent } }); } - }; - } else { - // differential - _currentView = viewFrustum; - _conicalView.set(_currentView); - - _getNextVisibleElementCallback = [&](VisibleElement& next) { - _traversalPath.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); - }; - - _scanNextElementCallback = [&](VisibleElement& next) { - // NOTE: for differential case next.intersection is against _completedView not _currentView - if (next.element->getLastChangedContent() > _startOfCompletedTraversal || next.intersection != ViewFrustum::INSIDE) { + }); + break; + case DiffTraversal::Differential: + _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) { + // NOTE: for Differential case: next.intersection is against completedView not currentView + uint64_t timestamp = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) { next.element->forEachEntity([&](EntityItemPointer entity) { bool success = false; AACube cube = entity->getQueryAACube(success); if (success) { - if (_currentView.cubeIntersectsKeyhole(cube) && - (entity->getLastEdited() > _startOfCompletedTraversal || - !_completedView.cubeIntersectsKeyhole(cube))) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube) && + (entity->getLastEdited() > timestamp || + !_traversal.getCompletedView().cubeIntersectsKeyhole(cube))) { float priority = _conicalView.computePriority(cube); _sendQueue.push(PrioritizedEntity(entity, priority)); } @@ -290,55 +248,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent } }); } - }; - } - - _traversalPath.clear(); - assert(root); - _traversalPath.push_back(TraversalWaypoint(root)); - // set root fork's index such that root element returned at getNextElement() - _traversalPath.back().initRootNextIndex(); - - _startOfCurrentTraversal = usecTimestampNow(); -} - -void EntityTreeSendThread::getNextVisibleElement(VisibleElement& next) { - if (_traversalPath.empty()) { - next.element.reset(); - next.intersection = ViewFrustum::OUTSIDE; - return; - } - _getNextVisibleElementCallback(next); - if (next.element) { - int8_t nextIndex = _traversalPath.back().getNextIndex(); - if (nextIndex > 0) { - // next.element needs to be added to the path - _traversalPath.push_back(TraversalWaypoint(next.element)); - } - } else { - // we're done at this level - while (!next.element) { - // pop one level - _traversalPath.pop_back(); - if (_traversalPath.empty()) { - // we've traversed the entire tree - _completedView = _currentView; - _startOfCompletedTraversal = _startOfCurrentTraversal; - return; - } - // keep looking for next - _getNextVisibleElementCallback(next); - if (next.element) { - // we've descended one level so add it to the path - _traversalPath.push_back(TraversalWaypoint(next.element)); - } - } + }); + break; } } -// DEBUG method: delete later -void EntityTreeSendThread::dump() const { - for (size_t i = 0; i < _traversalPath.size(); ++i) { - std::cout << (int32_t)(_traversalPath[i].getNextIndex()) << "-->"; - } -} diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index 9a1a57b41c..5cb2c4c76d 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -14,6 +14,8 @@ #include "../octree/OctreeSendThread.h" +#include + #include "EntityPriorityQueue.h" class EntityNodeData; @@ -22,7 +24,7 @@ class EntityItem; class EntityTreeSendThread : public OctreeSendThread { public: - EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); + EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { } protected: void preDistributionProcessing() override; @@ -35,18 +37,10 @@ private: bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); - void getNextVisibleElement(VisibleElement& element); - void dump() const; // DEBUG method, delete later + DiffTraversal _traversal; EntityPriorityQueue _sendQueue; - ViewFrustum _currentView; - ViewFrustum _completedView; - ConicalView _conicalView; // optimized view for fast priority calculations - std::vector _traversalPath; - std::function _getNextVisibleElementCallback { nullptr }; - std::function _scanNextElementCallback { nullptr }; - uint64_t _startOfCompletedTraversal { 0 }; - uint64_t _startOfCurrentTraversal { 0 }; + ConicalView _conicalView; // cached optimized view for fast priority calculations }; #endif // hifi_EntityTreeSendThread_h diff --git a/libraries/entities/src/DiffTraversal.cpp b/libraries/entities/src/DiffTraversal.cpp new file mode 100644 index 0000000000..b1fddcf5fb --- /dev/null +++ b/libraries/entities/src/DiffTraversal.cpp @@ -0,0 +1,239 @@ +// +// DiffTraversal.cpp +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 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 "DiffTraversal.h" + +DiffTraversal::Waypoint::Waypoint(EntityTreeElementPointer& element) : _nextIndex(0) { + assert(element); + _weakElement = element; +} + +void DiffTraversal::Waypoint::getNextVisibleElementFirstTime(DiffTraversal::VisibleElement& next, const ViewFrustum& view) { + // TODO: add LOD culling of elements + // NOTE: no need to set next.intersection in the "FirstTime" context + if (_nextIndex == -1) { + // only get here for the root Waypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + next.element = _weakElement.lock(); + return; + } else if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && view.cubeIntersectsKeyhole(nextElement->getAACube())) { + next.element = nextElement; + return; + } + } + } + } + next.element.reset(); +} + +void DiffTraversal::Waypoint::getNextVisibleElementRepeat(DiffTraversal::VisibleElement& next, const ViewFrustum& view, uint64_t lastTime) { + // TODO: add LOD culling of elements + if (_nextIndex == -1) { + // only get here for the root Waypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement && nextElement->getLastChanged() > lastTime) { + ViewFrustum::intersection intersection = view.calculateCubeKeyholeIntersection(nextElement->getAACube()); + if (intersection != ViewFrustum::OUTSIDE) { + next.element = nextElement; + next.intersection = intersection; + return; + } + } + } + } + } + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; +} + +DiffTraversal::DiffTraversal() { + const int32_t MIN_PATH_DEPTH = 16; + _path.reserve(MIN_PATH_DEPTH); +} + +void DiffTraversal::Waypoint::getNextVisibleElementDifferential(DiffTraversal::VisibleElement& next, + const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime) { + // TODO: add LOD culling of elements + if (_nextIndex == -1) { + // only get here for the root Waypoint at the very beginning of traversal + // safe to assume this element intersects view + ++_nextIndex; + EntityTreeElementPointer element = _weakElement.lock(); + // root case is special: its intersection is always INTERSECT + // and we can skip it if the content hasn't changed + if (element->getLastChangedContent() > lastTime) { + next.element = element; + next.intersection = ViewFrustum::INTERSECT; + return; + } + } + if (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer element = _weakElement.lock(); + if (element) { + while (_nextIndex < NUMBER_OF_CHILDREN) { + EntityTreeElementPointer nextElement = element->getChildAtIndex(_nextIndex); + ++_nextIndex; + if (nextElement) { + AACube cube = nextElement->getAACube(); + // NOTE: for differential case next.intersection is against the _completedView + ViewFrustum::intersection intersection = lastView.calculateCubeKeyholeIntersection(cube); + if ( lastView.calculateCubeKeyholeIntersection(cube) != ViewFrustum::OUTSIDE && + !(intersection == ViewFrustum::INSIDE && nextElement->getLastChanged() < lastTime)) { + next.element = nextElement; + next.intersection = intersection; + return; + } + } + } + } + } + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; +} + +DiffTraversal::Type DiffTraversal::prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root) { + // there are three types of traversal: + // + // (1) First = fresh view --> find all elements in view + // (2) Repeat = view hasn't changed --> find elements changed since last complete traversal + // (3) Differential = view has changed --> find elements changed or in new view but not old + // + // For each traversal type we define assign the appropriate _getNextVisibleElementCallback + // + // _getNextVisibleElementCallback = identifies elements that need to be traversed, + // updates VisibleElement ref argument with pointer-to-element and view-intersection + // (INSIDE, INTERSECT, or OUTSIDE) + // + // External code should update the _scanElementCallback after calling prepareNewTraversal + // + + Type type = Type::First; + if (_startOfCompletedTraversal == 0) { + // first time + _currentView = viewFrustum; + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementFirstTime(next, _currentView); + }; + } else if (_currentView.isVerySimilar(viewFrustum)) { + // again + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementRepeat(next, _currentView, _startOfCompletedTraversal); + }; + type = Type::Repeat; + } else { + // differential + _currentView = viewFrustum; + _getNextVisibleElementCallback = [&](DiffTraversal::VisibleElement& next) { + _path.back().getNextVisibleElementDifferential(next, _currentView, _completedView, _startOfCompletedTraversal); + }; + type = Type::Differential; + } + + assert(root); + _path.clear(); + _path.push_back(DiffTraversal::Waypoint(root)); + // set root fork's index such that root element returned at getNextElement() + _path.back().initRootNextIndex(); + _startOfCurrentTraversal = usecTimestampNow(); + + return type; +} + +void DiffTraversal::getNextVisibleElement(DiffTraversal::VisibleElement& next) { + if (_path.empty()) { + next.element.reset(); + next.intersection = ViewFrustum::OUTSIDE; + return; + } + _getNextVisibleElementCallback(next); + if (next.element) { + int8_t nextIndex = _path.back().getNextIndex(); + if (nextIndex > 0) { + // next.element needs to be added to the path + _path.push_back(DiffTraversal::Waypoint(next.element)); + } + } else { + // we're done at this level + while (!next.element) { + // pop one level + _path.pop_back(); + if (_path.empty()) { + // we've traversed the entire tree + _completedView = _currentView; + _startOfCompletedTraversal = _startOfCurrentTraversal; + return; + } + // keep looking for next + _getNextVisibleElementCallback(next); + if (next.element) { + // we've descended one level so add it to the path + _path.push_back(DiffTraversal::Waypoint(next.element)); + } + } + } +} + +void DiffTraversal::setScanCallback(std::function cb) { + if (!cb) { + _scanElementCallback = [](DiffTraversal::VisibleElement& a){}; + } else { + _scanElementCallback = cb; + } +} + +// DEBUG method: delete later +std::ostream& operator<<(std::ostream& s, const DiffTraversal& traversal) { + for (size_t i = 0; i < traversal._path.size(); ++i) { + s << (int32_t)(traversal._path[i].getNextIndex()); + if (i < traversal._path.size() - 1) { + s << "-->"; + } + } + return s; +} + +void DiffTraversal::traverse(uint64_t timeBudget) { + uint64_t expiry = usecTimestampNow() + timeBudget; + DiffTraversal::VisibleElement next; + getNextVisibleElement(next); + while (next.element) { + if (next.element->hasContent()) { + _scanElementCallback(next); + } + if (usecTimestampNow() > expiry) { + break; + } + getNextVisibleElement(next); + } +} diff --git a/libraries/entities/src/DiffTraversal.h b/libraries/entities/src/DiffTraversal.h new file mode 100644 index 0000000000..508d1e9c6b --- /dev/null +++ b/libraries/entities/src/DiffTraversal.h @@ -0,0 +1,78 @@ +// +// DiffTraversal.h +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 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_DiffTraversal_h +#define hifi_DiffTraversal_h + +#include // DEBUG + +#include + +#include "EntityTreeElement.h" + +// DiffTraversal traverses the tree and applies _scanElementCallback on elements it finds +class DiffTraversal { +public: + // VisibleElement is a struct identifying an element and how it intersected the view. + // The intersection is used to optimize culling entities from the sendQueue. + class VisibleElement { + public: + EntityTreeElementPointer element; + ViewFrustum::intersection intersection { ViewFrustum::OUTSIDE }; + }; + + // Waypoint is an bookmark in a "path" of waypoints during a traversal. + class Waypoint { + public: + Waypoint(EntityTreeElementPointer& element); + + void getNextVisibleElementFirstTime(VisibleElement& next, const ViewFrustum& view); + void getNextVisibleElementRepeat(VisibleElement& next, const ViewFrustum& view, uint64_t lastTime); + void getNextVisibleElementDifferential(VisibleElement& next, const ViewFrustum& view, const ViewFrustum& lastView, uint64_t lastTime); + + int8_t getNextIndex() const { return _nextIndex; } + void initRootNextIndex() { _nextIndex = -1; } + + protected: + EntityTreeElementWeakPointer _weakElement; + int8_t _nextIndex; + }; + + typedef enum { First, Repeat, Differential } Type; + + DiffTraversal(); + + DiffTraversal::Type prepareNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); + + const ViewFrustum& getCurrentView() const { return _currentView; } + const ViewFrustum& getCompletedView() const { return _completedView; } + + uint64_t getStartOfCompletedTraversal() const { return _startOfCompletedTraversal; } + bool finished() const { return _path.empty(); } + + void setScanCallback(std::function cb); + void traverse(uint64_t timeBudget); + + friend std::ostream& operator<<(std::ostream& s, const DiffTraversal& traversal); // DEBUG + +private: + void getNextVisibleElement(VisibleElement& next); + + ViewFrustum _currentView; + ViewFrustum _completedView; + std::vector _path; + std::function _getNextVisibleElementCallback { nullptr }; + std::function _scanElementCallback { [](VisibleElement& e){} }; + uint64_t _startOfCompletedTraversal { 0 }; + uint64_t _startOfCurrentTraversal { 0 }; +}; + +#endif // hifi_EntityPriorityQueue_h