abstract DiffTraversal out of EntityTreeSendThread

This commit is contained in:
Andrew Meadows 2017-08-09 13:14:36 -07:00
parent 5fba4cb68c
commit 0758b60afc
6 changed files with 366 additions and 276 deletions

View file

@ -14,7 +14,7 @@
const float DO_NOT_SEND = -1.0e-6f; const float DO_NOT_SEND = -1.0e-6f;
void ConicalView::set(const ViewFrustum& viewFrustum) { 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. // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum.
_position = viewFrustum.getPosition(); _position = viewFrustum.getPosition();
_direction = viewFrustum.getDirection(); _direction = viewFrustum.getDirection();
@ -72,109 +72,3 @@ float PrioritizedEntity::updatePriority(const ConicalView& conicalView) {
} }
return _priority; 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;
}

View file

@ -15,8 +15,7 @@
#include <queue> #include <queue>
#include <AACube.h> #include <AACube.h>
#include <EntityTreeElement.h>
#include "EntityTreeElement.h"
const float SQRT_TWO_OVER_TWO = 0.7071067811865f; const float SQRT_TWO_OVER_TWO = 0.7071067811865f;
const float DEFAULT_VIEW_RADIUS = 10.0f; const float DEFAULT_VIEW_RADIUS = 10.0f;
@ -56,31 +55,6 @@ private:
float _priority; 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>, PrioritizedEntity::Compare >; using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector<PrioritizedEntity>, PrioritizedEntity::Compare >;
#endif // hifi_EntityPriorityQueue_h #endif // hifi_EntityPriorityQueue_h

View file

@ -10,19 +10,12 @@
// //
#include "EntityTreeSendThread.h" #include "EntityTreeSendThread.h"
#include <iostream> // adebug
#include <EntityNodeData.h> #include <EntityNodeData.h>
#include <EntityTypes.h> #include <EntityTypes.h>
#include "EntityServer.h" #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() { void EntityTreeSendThread::preDistributionProcessing() {
auto node = _node.toStrongRef(); auto node = _node.toStrongRef();
auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
@ -90,14 +83,14 @@ void EntityTreeSendThread::preDistributionProcessing() {
void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) { bool viewFrustumChanged, bool isFullScene) {
// BEGIN EXPERIMENTAL DIFFERENTIAL TRAVERSAL // 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()) { 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) { if (viewFrustumChanged) {
ViewFrustum viewFrustum; ViewFrustum viewFrustum;
nodeData->copyCurrentViewFrustum(viewFrustum); nodeData->copyCurrentViewFrustum(viewFrustum);
@ -105,28 +98,14 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
startNewTraversal(viewFrustum, root); startNewTraversal(viewFrustum, root);
} }
} }
if (!_traversalPath.empty()) { if (!_traversal.finished()) {
uint64_t startTime = usecTimestampNow(); uint64_t startTime = usecTimestampNow();
uint64_t now = startTime;
VisibleElement next; const uint64_t TIME_BUDGET = 100000; // usec
getNextVisibleElement(next); _traversal.traverse(TIME_BUDGET);
while (next.element) {
if (next.element->hasContent()) {
_scanNextElementCallback(next);
}
// TODO: pick a reasonable budget for each partial traversal uint64_t dt = usecTimestampNow() - startTime;
const uint64_t PARTIAL_TRAVERSAL_TIME_BUDGET = 100000; // usec std::cout << "adebug traversal complete " << " Q.size = " << _sendQueue.size() << " dt = " << dt << std::endl; // adebug
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
} }
if (!_sendQueue.empty()) { if (!_sendQueue.empty()) {
// print what needs to be sent // print what needs to be sent
@ -134,11 +113,9 @@ void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
PrioritizedEntity entry = _sendQueue.top(); PrioritizedEntity entry = _sendQueue.top();
EntityItemPointer entity = entry.getEntity(); EntityItemPointer entity = entry.getEntity();
if (entity) { if (entity) {
std::cout << "adebug '" << entity->getName().toStdString() << "'" std::cout << "adebug send '" << entity->getName().toStdString() << "'" << " : " << entry.getPriority() << std::endl; // adebug
<< " : " << entry.getPriority() << std::endl; // adebug
} }
_sendQueue.pop(); _sendQueue.pop();
std::cout << "adebug" << std::endl; // adebug
} }
} }
// END EXPERIMENTAL DIFFERENTIAL TRAVERSAL // END EXPERIMENTAL DIFFERENTIAL TRAVERSAL
@ -195,39 +172,29 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
return hasNewChild || hasNewDescendants; 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: // there are three types of traversal:
// //
// (1) FirstTime = at login --> find everything in view // (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 // (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: // The "scanCallback" we provide to the traversal depends on the type:
//
// _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 _conicalView is updated here as a cached view approximation used by the lambdas for efficient // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient
// computation of entity sorting priorities. // computation of entity sorting priorities.
// //
if (_startOfCompletedTraversal == 0) { _conicalView.set(_traversal.getCurrentView());
// first time
_currentView = viewFrustum;
_conicalView.set(_currentView);
_getNextVisibleElementCallback = [&](VisibleElement& next) { switch (type) {
_traversalPath.back().getNextVisibleElementFirstTime(next, _currentView); case DiffTraversal::First:
}; _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) {
_scanNextElementCallback = [&](VisibleElement& next) {
next.element->forEachEntity([&](EntityItemPointer entity) { next.element->forEachEntity([&](EntityItemPointer entity) {
bool success = false; bool success = false;
AACube cube = entity->getQueryAACube(success); AACube cube = entity->getQueryAACube(success);
if (success) { if (success) {
if (_currentView.cubeIntersectsKeyhole(cube)) { if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {
float priority = _conicalView.computePriority(cube); float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority)); _sendQueue.push(PrioritizedEntity(entity, priority));
} }
@ -236,21 +203,18 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent
_sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY)); _sendQueue.push(PrioritizedEntity(entity, WHEN_IN_DOUBT_PRIORITY));
} }
}); });
}; });
} else if (_currentView.isVerySimilar(viewFrustum)) { break;
// again case DiffTraversal::Repeat:
_getNextVisibleElementCallback = [&](VisibleElement& next) { _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) {
_traversalPath.back().getNextVisibleElementAgain(next, _currentView, _startOfCompletedTraversal); if (next.element->getLastChangedContent() > _traversal.getStartOfCompletedTraversal()) {
}; uint64_t timestamp = _traversal.getStartOfCompletedTraversal();
_scanNextElementCallback = [&](VisibleElement& next) {
if (next.element->getLastChangedContent() > _startOfCompletedTraversal) {
next.element->forEachEntity([&](EntityItemPointer entity) { next.element->forEachEntity([&](EntityItemPointer entity) {
if (entity->getLastEdited() > _startOfCompletedTraversal) { if (entity->getLastEdited() > timestamp) {
bool success = false; bool success = false;
AACube cube = entity->getQueryAACube(success); AACube cube = entity->getQueryAACube(success);
if (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); float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority)); _sendQueue.push(PrioritizedEntity(entity, priority));
} }
@ -261,26 +225,20 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent
} }
}); });
} }
}; });
} else { break;
// differential case DiffTraversal::Differential:
_currentView = viewFrustum; _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) {
_conicalView.set(_currentView); // NOTE: for Differential case: next.intersection is against completedView not currentView
uint64_t timestamp = _traversal.getStartOfCompletedTraversal();
_getNextVisibleElementCallback = [&](VisibleElement& next) { if (next.element->getLastChangedContent() > timestamp || next.intersection != ViewFrustum::INSIDE) {
_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) {
next.element->forEachEntity([&](EntityItemPointer entity) { next.element->forEachEntity([&](EntityItemPointer entity) {
bool success = false; bool success = false;
AACube cube = entity->getQueryAACube(success); AACube cube = entity->getQueryAACube(success);
if (success) { if (success) {
if (_currentView.cubeIntersectsKeyhole(cube) && if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube) &&
(entity->getLastEdited() > _startOfCompletedTraversal || (entity->getLastEdited() > timestamp ||
!_completedView.cubeIntersectsKeyhole(cube))) { !_traversal.getCompletedView().cubeIntersectsKeyhole(cube))) {
float priority = _conicalView.computePriority(cube); float priority = _conicalView.computePriority(cube);
_sendQueue.push(PrioritizedEntity(entity, priority)); _sendQueue.push(PrioritizedEntity(entity, priority));
} }
@ -290,55 +248,8 @@ void EntityTreeSendThread::startNewTraversal(const ViewFrustum& viewFrustum, Ent
} }
}); });
} }
}; });
} break;
_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));
}
}
} }
} }
// DEBUG method: delete later
void EntityTreeSendThread::dump() const {
for (size_t i = 0; i < _traversalPath.size(); ++i) {
std::cout << (int32_t)(_traversalPath[i].getNextIndex()) << "-->";
}
}

View file

@ -14,6 +14,8 @@
#include "../octree/OctreeSendThread.h" #include "../octree/OctreeSendThread.h"
#include <DiffTraversal.h>
#include "EntityPriorityQueue.h" #include "EntityPriorityQueue.h"
class EntityNodeData; class EntityNodeData;
@ -22,7 +24,7 @@ class EntityItem;
class EntityTreeSendThread : public OctreeSendThread { class EntityTreeSendThread : public OctreeSendThread {
public: public:
EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) { }
protected: protected:
void preDistributionProcessing() override; void preDistributionProcessing() override;
@ -35,18 +37,10 @@ private:
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData); bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root); void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root);
void getNextVisibleElement(VisibleElement& element);
void dump() const; // DEBUG method, delete later
DiffTraversal _traversal;
EntityPriorityQueue _sendQueue; EntityPriorityQueue _sendQueue;
ViewFrustum _currentView; ConicalView _conicalView; // cached optimized view for fast priority calculations
ViewFrustum _completedView;
ConicalView _conicalView; // optimized view for fast priority calculations
std::vector<TraversalWaypoint> _traversalPath;
std::function<void (VisibleElement&)> _getNextVisibleElementCallback { nullptr };
std::function<void (VisibleElement&)> _scanNextElementCallback { nullptr };
uint64_t _startOfCompletedTraversal { 0 };
uint64_t _startOfCurrentTraversal { 0 };
}; };
#endif // hifi_EntityTreeSendThread_h #endif // hifi_EntityTreeSendThread_h

View file

@ -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<void (DiffTraversal::VisibleElement&)> 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);
}
}

View file

@ -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 <ostream> // DEBUG
#include <ViewFrustum.h>
#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<void (VisibleElement&)> 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<Waypoint> _path;
std::function<void (VisibleElement&)> _getNextVisibleElementCallback { nullptr };
std::function<void (VisibleElement&)> _scanElementCallback { [](VisibleElement& e){} };
uint64_t _startOfCompletedTraversal { 0 };
uint64_t _startOfCurrentTraversal { 0 };
};
#endif // hifi_EntityPriorityQueue_h