429 lines
15 KiB
C++
429 lines
15 KiB
C++
#pragma once
|
|
|
|
#include <CesiumUtility/Assert.h>
|
|
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
namespace CesiumUtility {
|
|
|
|
template <typename T> class IntrusivePointer;
|
|
|
|
/**
|
|
* @brief Associates state (arbitrary data) with each node during partial,
|
|
* depth-first traversal of a tree. Then, during a later traversal of a
|
|
* potentially different subset of the same tree, the state previously
|
|
* associated with each node can be looked up.
|
|
*
|
|
* In order to operate efficiently, this class makes some assumptions. Violation
|
|
* of these assumptions can lead to undefined behavior.
|
|
*
|
|
* 1. Nodes are identified by pointer. It is _not_ required that all nodes
|
|
* previously traversed remain valid in a later traversal. However, if a new
|
|
* node instance is created at the same memory address as a previous one, it
|
|
* will be considered the same node.
|
|
* 2. The entire tree is not necessarily traversed each time. However, if any
|
|
* children of a node are traversed, then _all_ children of the node must be
|
|
* traversed.
|
|
* 3. The order of traversal of children must be the same every time.
|
|
* 4. A node that previously had no children may gain them. A node that
|
|
* previously had children may lose all of them. However, partial updates of the
|
|
* children of a node are not allowed.
|
|
*
|
|
* @tparam TNodePointer The type of each node in the tree.
|
|
* @tparam TState The state to associate with each node.
|
|
*/
|
|
template <typename TNodePointer, typename TState> class TreeTraversalState {
|
|
public:
|
|
/**
|
|
* @brief The instance type of the node.
|
|
*/
|
|
using TNodeInstance = std::remove_cvref_t<
|
|
typename std::pointer_traits<TNodePointer>::element_type>;
|
|
|
|
/**
|
|
* @brief A raw pointer to a node.
|
|
*/
|
|
using TRawNodePointer = TNodeInstance*;
|
|
|
|
/**
|
|
* @brief A raw pointer to a const node.
|
|
*/
|
|
using TRawConstNodePointer = const TNodeInstance*;
|
|
|
|
/**
|
|
* @brief Gets the total number of nodes that were visited in the previous
|
|
* traversal.
|
|
*/
|
|
size_t getNodeCountInPreviousTraversal() const {
|
|
return this->_previousTraversal.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Gets the total number of nodes that have been visited so far in the
|
|
* current traversal.
|
|
*/
|
|
size_t getNodeCountInCurrentTraversal() const {
|
|
return this->_currentTraversal.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Begins a new traversal of the tree. The "current" and "previous"
|
|
* traversals are swapped, and then the new "current" traversal is cleared.
|
|
*/
|
|
void beginTraversal() {
|
|
// If this assertion fails, it indicates a traversal is already in progress.
|
|
CESIUM_ASSERT(this->_parentIndices.empty());
|
|
|
|
this->_previousTraversal.swap(this->_currentTraversal);
|
|
this->_currentTraversal.clear();
|
|
this->_previousTraversalNextNodeIndex = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Begins traversing a node in the tree. This node becomes the
|
|
* "current" node.
|
|
*
|
|
* When `beginNode` is called for node A, and then for node B, without an
|
|
* intervening call to `finishNode`, that indicates that B is a child of A.
|
|
*
|
|
* @param pNode The node traversed.
|
|
*/
|
|
void beginNode(const TNodePointer& pNode) {
|
|
int64_t currentTraversalIndex = int64_t(this->_currentTraversal.size());
|
|
int64_t previousTraversalIndex = this->_previousTraversalNextNodeIndex;
|
|
|
|
if (previousTraversalIndex >= 0 &&
|
|
size_t(previousTraversalIndex) < this->_previousTraversal.size()) {
|
|
const TraversalData& previousData =
|
|
this->_previousTraversal[size_t(previousTraversalIndex)];
|
|
if (previousData.pNode == pNode) {
|
|
++this->_previousTraversalNextNodeIndex;
|
|
} else {
|
|
previousTraversalIndex = -1;
|
|
}
|
|
} else {
|
|
previousTraversalIndex = -1;
|
|
}
|
|
|
|
this->_parentIndices.emplace_back(TraversalIndices{
|
|
.previous = previousTraversalIndex,
|
|
.current = currentTraversalIndex});
|
|
|
|
this->_currentTraversal.emplace_back(TraversalData{pNode, -1, TState()});
|
|
}
|
|
|
|
/**
|
|
* @brief Gets the current node in the traversal.
|
|
*
|
|
* When {@link beginNode} is called, the node passed to it becomes the
|
|
* current one. When {@link finishNode} is called, the parent node of the
|
|
* one passed to it becomes the current one.
|
|
*
|
|
* @return The current node, or nullptr if no traversal is in progress.
|
|
*/
|
|
TNodePointer getCurrentNode() const noexcept {
|
|
if (this->_parentIndices.empty())
|
|
return nullptr;
|
|
|
|
return this->currentData().pNode;
|
|
}
|
|
|
|
/**
|
|
* @brief Determines if the current node was visited in the previous
|
|
* traversal.
|
|
*
|
|
* @return true if the current node was visited in the previous traversal;
|
|
* otherwise, false.
|
|
*/
|
|
bool wasCurrentNodePreviouslyTraversed() const noexcept {
|
|
return this->previousState() != nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Gets the state of the current node on the previous traversal. The
|
|
* current node is the one for which {@link beginNode} was most recently
|
|
* called.
|
|
*
|
|
* @return The state on the previous traversal, or `nullptr` if the current
|
|
* node was not traversed during the previous traversal.
|
|
*/
|
|
const TState* previousState() const {
|
|
const TraversalData* pData = this->previousData();
|
|
return pData ? &pData->state : nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Gets the state of the current node during the current traversal. The
|
|
* current node is the one for which {@link beginNode} was most recently
|
|
* called.
|
|
*
|
|
* @return The state object for this node on the current traversal. It may be
|
|
* both read and written.
|
|
*/
|
|
TState& currentState() {
|
|
TraversalData& data = this->currentData();
|
|
return data.state;
|
|
}
|
|
|
|
/**
|
|
* @brief Gets the state of the current node during the current traversal. The
|
|
* current node is the one for which {@link beginNode} was most recently
|
|
* called.
|
|
*
|
|
* @return The state object for this node on the current traversal.
|
|
*/
|
|
const TState& currentState() const {
|
|
const TraversalData& data = this->currentData();
|
|
return data.state;
|
|
}
|
|
|
|
/**
|
|
* @brief Ends traversal of the given node.
|
|
*
|
|
* This method must be called in the opposite order of calls to
|
|
* {@link finishNode}.
|
|
*
|
|
* After `finishNode`, this node's parent node becomes the "current" node.
|
|
* {@link previousState} and {@link currentState} will return
|
|
* the states of this node's parent. A call to {@link beginNode} will
|
|
* delineate a new child of that same parent.
|
|
*
|
|
* @param pNode The node that is done being traversed.
|
|
*/
|
|
void finishNode([[maybe_unused]] const TNodePointer& pNode) {
|
|
// An assertion failure here indicates mismatched calls to beginNode /
|
|
// finishNode.
|
|
CESIUM_ASSERT(!this->_currentTraversal.empty());
|
|
CESIUM_ASSERT(!this->_parentIndices.empty());
|
|
CESIUM_ASSERT(this->currentData().pNode == pNode);
|
|
|
|
this->currentData().nextSiblingIndex =
|
|
int64_t(this->_currentTraversal.size());
|
|
|
|
// Now that this node is done, skip its subtree, if any, in the previous
|
|
// traversal. If this finished node doesn't exist in the previous traversal,
|
|
// look for the next node in the previous traversal at the current position.
|
|
const TraversalData* pPreviousData = this->previousData();
|
|
if (pPreviousData) {
|
|
CESIUM_ASSERT(pPreviousData->nextSiblingIndex >= 0);
|
|
this->_previousTraversalNextNodeIndex = pPreviousData->nextSiblingIndex;
|
|
}
|
|
|
|
this->_parentIndices.pop_back();
|
|
}
|
|
|
|
/**
|
|
* @brief Invokes a callback for each child of the current node that was
|
|
* traversed in the previous traversal.
|
|
*
|
|
* If the current node or its children were not traversed in the previous
|
|
* traversal, this method returns without invoking the callback at all.
|
|
*
|
|
* @tparam Func The type of the function to invoke.
|
|
* @param callback The function to invoke for each previously-traversed child.
|
|
* The function is passed the `TNodePointer` and the `TState`.
|
|
*/
|
|
template <typename Func> void forEachPreviousChild(Func&& callback) const {
|
|
int64_t parentPreviousIndex = this->previousDataIndex();
|
|
if (parentPreviousIndex < 0) {
|
|
// Current node was not previously traversed.
|
|
return;
|
|
}
|
|
|
|
const TraversalData& parentPreviousData =
|
|
this->_previousTraversal[size_t(parentPreviousIndex)];
|
|
|
|
for (size_t i = size_t(parentPreviousIndex + 1);
|
|
i < size_t(parentPreviousData.nextSiblingIndex);) {
|
|
CESIUM_ASSERT(i < this->_previousTraversal.size());
|
|
const TraversalData& data = this->_previousTraversal[i];
|
|
callback(data.pNode, data.state);
|
|
CESIUM_ASSERT(
|
|
data.nextSiblingIndex >= 0 && size_t(data.nextSiblingIndex) > i);
|
|
i = size_t(data.nextSiblingIndex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Invokes a callback for each descendant (children, grandchildren,
|
|
* etc.) of the current node that was traversed in the previous traversal.
|
|
*
|
|
* If the current node or its children were not traversed in the previous
|
|
* traversal, this method returns without invoking the callback at all.
|
|
*
|
|
* @tparam Func The type of the function to invoke.
|
|
* @param callback The function to invoke for each previously-traversed
|
|
* descendant. The function is passed the `TNodePointer` and the `TState`.
|
|
*/
|
|
template <typename Func>
|
|
void forEachPreviousDescendant(Func&& callback) const {
|
|
int64_t parentPreviousIndex = this->previousDataIndex();
|
|
if (parentPreviousIndex < 0) {
|
|
// Current node was not previously traversed.
|
|
return;
|
|
}
|
|
|
|
const TraversalData& parentPreviousData =
|
|
this->_previousTraversal[size_t(parentPreviousIndex)];
|
|
|
|
for (size_t i = size_t(parentPreviousIndex + 1);
|
|
i < size_t(parentPreviousData.nextSiblingIndex);
|
|
++i) {
|
|
CESIUM_ASSERT(i < this->_previousTraversal.size());
|
|
const TraversalData& data = this->_previousTraversal[i];
|
|
callback(data.pNode, data.state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Invokes a callback for each descendant (children, grandchildren,
|
|
* etc.) of the current node that has been traversed so far in the current
|
|
* traversal.
|
|
*
|
|
* If the current node's children were not traversed in the current
|
|
* traversal, this method returns without invoking the callback at all.
|
|
*
|
|
* @tparam Func The type of the function to invoke.
|
|
* @param callback The function to invoke for each descendant of the current
|
|
* tile that has been traversed in the current traversal. The function is
|
|
* passed the `TNodePointer` and the `TState`.
|
|
*/
|
|
template <typename Func> void forEachCurrentDescendant(Func&& callback) {
|
|
int64_t parentCurrentIndex = this->currentDataIndex();
|
|
CESIUM_ASSERT(parentCurrentIndex >= 0);
|
|
|
|
const TraversalData& parentCurrentData =
|
|
this->_currentTraversal[size_t(parentCurrentIndex)];
|
|
|
|
size_t endIndex = parentCurrentData.nextSiblingIndex >= 0
|
|
? size_t(parentCurrentData.nextSiblingIndex)
|
|
: this->_currentTraversal.size();
|
|
|
|
for (size_t i = size_t(parentCurrentIndex + 1); i < endIndex; ++i) {
|
|
TraversalData& data = this->_currentTraversal[i];
|
|
callback(data.pNode, data.state);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Gets a mapping of nodes to states for the current traversal.
|
|
*
|
|
* This is an inherently slow operation that should only be used in debug and
|
|
* test code.
|
|
*
|
|
* @return The mapping of nodes to current traversal states.
|
|
*/
|
|
std::unordered_map<TRawConstNodePointer, TState>
|
|
slowlyGetCurrentStates() const {
|
|
return this->slowlyGetStates(this->_currentTraversal);
|
|
}
|
|
|
|
/**
|
|
* @brief Gets a mapping of nodes to states for the previous traversal.
|
|
*
|
|
* This is an inherently slow operation that should only be used in debug and
|
|
* test code.
|
|
*
|
|
* @return The mapping of nodes to previous traversal states.
|
|
*/
|
|
std::unordered_map<TRawConstNodePointer, TState>
|
|
slowlyGetPreviousStates() const {
|
|
return this->slowlyGetStates(this->_previousTraversal);
|
|
}
|
|
|
|
private:
|
|
struct TraversalData {
|
|
TNodePointer pNode;
|
|
int64_t nextSiblingIndex;
|
|
TState state;
|
|
};
|
|
|
|
struct TraversalIndices {
|
|
int64_t previous;
|
|
int64_t current;
|
|
};
|
|
|
|
int64_t previousDataIndex() const {
|
|
CESIUM_ASSERT(!this->_parentIndices.empty());
|
|
|
|
int64_t result = this->_parentIndices.back().previous;
|
|
|
|
CESIUM_ASSERT(
|
|
result == -1 ||
|
|
(result >= 0 && size_t(result) < this->_previousTraversal.size()));
|
|
|
|
return result;
|
|
}
|
|
|
|
int64_t currentDataIndex() const {
|
|
CESIUM_ASSERT(!this->_parentIndices.empty());
|
|
|
|
// An assertion failure here may indicate beginTraversal wasn't called.
|
|
CESIUM_ASSERT(
|
|
this->_parentIndices.back().current >= 0 &&
|
|
this->_parentIndices.back().current <
|
|
static_cast<int64_t>(this->_currentTraversal.size()));
|
|
|
|
return this->_parentIndices.back().current;
|
|
}
|
|
|
|
const TraversalData* previousData() const {
|
|
int64_t previousIndex = this->previousDataIndex();
|
|
if (previousIndex < 0)
|
|
return nullptr;
|
|
|
|
const TraversalData& previousData =
|
|
this->_previousTraversal[size_t(previousIndex)];
|
|
|
|
CESIUM_ASSERT(previousData.pNode == this->currentData().pNode);
|
|
|
|
return &previousData;
|
|
}
|
|
|
|
TraversalData& currentData() {
|
|
int64_t currentIndex = this->currentDataIndex();
|
|
CESIUM_ASSERT(currentIndex >= 0);
|
|
return this->_currentTraversal[size_t(currentIndex)];
|
|
}
|
|
|
|
const TraversalData& currentData() const {
|
|
int64_t currentIndex = this->currentDataIndex();
|
|
CESIUM_ASSERT(currentIndex >= 0);
|
|
return this->_currentTraversal[size_t(currentIndex)];
|
|
}
|
|
|
|
std::unordered_map<TRawConstNodePointer, TState>
|
|
slowlyGetStates(const std::vector<TraversalData>& traversalData) const {
|
|
std::unordered_map<TRawConstNodePointer, TState> result;
|
|
|
|
for (const TraversalData& data : traversalData) {
|
|
result[data.pNode] = data.state;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<TraversalData> _previousTraversal;
|
|
std::vector<TraversalData> _currentTraversal;
|
|
|
|
// A stack of indices into the previous and current traversals. When
|
|
// `beginNode` is called, the index of that node within each traversal is
|
|
// pushed onto the end of this vector. This is always the index of the _last_
|
|
// node in the `_currentTraversal`, because `beginNode` always adds a new
|
|
// entry to the end of the `currentTraversal`. The previous traversal index
|
|
// may be -1 if the current node was not visited at all in the previous
|
|
// traversal. `finishNode` pops the last entry off the end of this array.
|
|
std::vector<TraversalIndices> _parentIndices;
|
|
|
|
// The index of the next node in the previous traversal. In `beginNode`, this
|
|
// new node is added to the end of `_currentTraversal`. In
|
|
// `_previousTraversal`, if it exists at all, it will be found at this index.
|
|
int64_t _previousTraversalNextNodeIndex = 0;
|
|
};
|
|
|
|
} // namespace CesiumUtility
|