diff --git a/CHANGES.md b/CHANGES.md index 818b5311c..7843038d8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Change Log +### ? - ? + +##### Additions :tada: + +- Added `diff` method to `TreeTraversalState`. + ### v0.48.0 - 2025-06-02 ##### Breaking Changes :mega: diff --git a/CesiumUtility/include/CesiumUtility/TreeTraversalState.h b/CesiumUtility/include/CesiumUtility/TreeTraversalState.h index d0fe2b2a0..a0a2ff076 100644 --- a/CesiumUtility/include/CesiumUtility/TreeTraversalState.h +++ b/CesiumUtility/include/CesiumUtility/TreeTraversalState.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -313,6 +314,333 @@ template class TreeTraversalState { return this->slowlyGetStates(this->_previousTraversal); } + class Differences; + + /** + * @brief Compares the current traversal against the previous one. Provides an + * iterator over all of the nodes that had a different state in the two + * traversals. + * + * The iteration also includes: + * + * * Each node that was visited previously but was not visited in the + * current traversal. + * * Each node that was not visited previously but was visited in the + * current traversal. + * + * Nodes are iterated depth-first, pre-order, so differences in a parent node + * are provided before any differences in children. + * + * This method should only be called after the {@link finishNode} for the + * root node, and before {@link beginTraversal}. In other words, it should + * not be called while a traversal is in progress. + * + * Starting a traversal after calling this method invalidates the returned + * instance. + */ + Differences differences() const noexcept { + // Assert that a traversal is not currently in progress. + CESIUM_ASSERT(this->_parentIndices.empty()); + + return Differences{ + *this, + this->_previousTraversal.size(), + this->_currentTraversal.size()}; + } + +#pragma region Differences Implementation + + class difference_iterator; + + /** + * @brief Represents a single difference reported by {@link differences}. + */ + struct Difference { + /** + * @brief The type used to report the node with a difference. + * + * This will be a simple pointer if `TNodePointer` is a simple pointer. + * Otherwise, it will be a const reference to `TNodePointer`. + */ + using PointerStorageType = std::conditional_t< + std::is_pointer_v, + TNodePointer, + const TNodePointer&>; + + /** + * @brief The type used to report the previous and current states of the + * node. + * + * This will be a `TState` instance of `TState` is trivially copy + * constructible. Otherwise, it will be a const reference to the `TState`. + */ + using StateStorageType = std::conditional_t< + std::is_trivially_copy_constructible_v, + TState, + const TState&>; + + /** + * @brief The node with a different state. + */ + PointerStorageType pNode; + + /** + * @brief The state of the node in the previous traversal, or a + * default-constructed instance if the node was not visited at all in the + * previous traversal. + */ + StateStorageType previousState; + + /** + * @brief The state of the node in the current traversal, or a + * default-constructed instance if the node was not visited at all in the + * current traversal. + */ + StateStorageType currentState; + + // These operators allow a `Difference` instance to be returned by value + // from the difference_iterator's `operator->` method. + + /** @private */ + Difference* operator->() { return this; } + + /** @private */ + const Difference* operator->() const { return this; } + }; + +private: + struct TraversalData; + +public: + /** + * @brief The type of the iterator created by {@link Differences}. + */ + class difference_iterator { + public: + /** + * @brief The iterator category tag denoting this is a forward iterator. + */ + using iterator_category = std::forward_iterator_tag; + /** + * @brief The type of value that is being iterated over. + */ + using value_type = Difference; + /** + * @brief The type used to identify distance between iterators. + * + * This is `void` because there is no meaningful measure of distance between + * tiles. + */ + using difference_type = void; + /** + * @brief A pointer to the type being iterated over. + */ + using pointer = const value_type*; + /** + * @brief A reference to the type being iterated over. + */ + using reference = const value_type&; + + /** + * @brief Returns a reference to the current difference being iterated. + */ + value_type operator*() const noexcept; + /** + * @brief Returns a pointer to the current difference being iterated. + */ + value_type operator->() const noexcept; + + /** + * @brief Advances the iterator to the next difference (pre-incrementing). + */ + difference_iterator& operator++() noexcept; + /** + * @brief Advances the iterator to the next difference (post-incrementing). + */ + difference_iterator operator++(int) noexcept; + + /** + * @brief Gets the "end" iterator for the descendants of the current node. + * + * This can be used to skip differences in this node's descendant nodes, or + * to otherwise treat them specially. It refers to one difference past the + * last difference for any descendants of this tile, in the same way that a + * normal "end" iterator is after the last element in a collection. + */ + difference_iterator descendantsEnd(); + + /** @brief Checks if two iterators are at the same difference. */ + bool operator==(const difference_iterator& rhs) const noexcept; + /** @brief Checks if two iterators are not at the same difference. */ + bool operator!=(const difference_iterator& rhs) const noexcept; + + private: + /** + * @brief Creates the "begin" iterator for this state. + */ + explicit difference_iterator( + const TreeTraversalState* pState) noexcept; + + /** + * @brief Creates an iterator at a specific position, which must be valid. + */ + difference_iterator( + const TreeTraversalState* pState, + int64_t previousIndex, + int64_t currentIndex) noexcept; + + /** + * @brief Creates an iterator that points to the first difference that + * occurs on or after a given pair of traversal indices. + * + * The given indices must either both be after the end of the traversal + * vectors, or they must point to entries for the same node. + * + * If they point to the same node, and the state of that node is the same in + * the previous and current traversals, then the iterator will be advanced + * to the next actual difference. + * + * @param pState The tree traversal state being differenced. + * @param previousIndex The index of the current node in the previous + * traversal. + * @param currentIndex The index of the current node in the current + * traversal. + * @returns The created iterator. + */ + static difference_iterator createOnOrAfter( + const TreeTraversalState* pState, + int64_t previousIndex, + int64_t currentIndex); + + /** + * @brief Advances the iterator from its current position on a node that was + * visited in both the previous and current traversals. + * + * Use this overload if the previous and current traversal data for the + * current node are already known. Otherwise, use the one taking no + * parameters. + */ + void advanceFromMatchedNode( + const TraversalData& previousData, + const TraversalData& currentData); + + /** + * @brief Advances the iterator from its current position on a node that was + * visited in both the previous and current traversals. + * + * Use this overload if the previous and current traversal data for the + * current node are not already known. + */ + void advanceFromMatchedNode(); + + /** + * @brief If the iterator already points to a node where the state was + * different in the two traversals, this method does nothing. Otherwise, it + * advances the iterator until it points to a valid difference. On + * invocation of this method, the iterator _must_ point to a node that was + * visited in both the previous and current traversals. + */ + void advanceFromMatchedNodeUnlessDifferent(); + + /** + * @brief Advance this iterator to the next node, which might not actually + * represent a difference. + * + * If this method returns true, the iterator definitely points to the next + * difference. + * + * If this method returns false, the iterator will either represent the end + * of the traversal, or it will point to a node that existed in both + * traversals. In the latter case, the state between the two traversals may + * or may not be different. + * + * @returns true if the current position represents a structural difference + * ({@link DifferenceType::NodeOnlyInPrevious} or + * {@link DifferenceType::NodeOnlyInCurrent}); otherwise, false. + */ + bool advanceOnce( + const TraversalData& previousData, + const TraversalData& currentData); + + /** + * @brief Moves the current iterator to the next difference after the + * current one. Unlike {@link advanceToCurrentDifference}, this method + * always moves the iterator. Calling this on an iterator that is already at + * the end is undefined behavior. + */ + void advanceToNextDifference() noexcept; + + // The instance for which we're comparing traversals. + const TreeTraversalState* _pState; + + // The index of the current difference in _previousTraversal. + int64_t _previousIndex; + + // The index of the current difference in _currentTraversal. + int64_t _currentIndex; + + // The type of difference we're currently enumerating. + enum class DifferenceType { + // A node that was visited in both traversals, but with a different state + // in each. + StateChange, + // A node that was only visited in the previous traversal. + NodeOnlyInPrevious, + // A node that was only visited in the current traversal. + NodeOnlyInCurrent + } _differenceType; + + // If the _differenceType is NodeOnlyInPrevious or NodeOnlyInCurrent, this + // field is set to the index of the next node that is expected to be in both + // traversals again. + int64_t _nextSiblingIndex; + + static const inline TState DEFAULT_STATE{}; + + friend class TreeTraversalState; + }; + + /** + * @brief Returned by the {@link differences} method to allow iteration over + * the differences between two traversals of the same tree. + */ + class Differences { + public: + /** + * @brief Gets an iterator pointing to the first difference. + */ + difference_iterator begin() const noexcept { + return difference_iterator(this->_pState); + } + + /** + * @brief Gets an iterator pointing to one past the last difference. + */ + difference_iterator end() const noexcept { + return difference_iterator( + this->_pState, + int64_t(this->_previousTraversalSize), + int64_t(this->_currentTraversalSize)); + } + + private: + Differences( + const TreeTraversalState& traversalState, + size_t previousTraversalSize, + size_t currentTraversalSize) noexcept + : _pState(&traversalState), + _previousTraversalSize(previousTraversalSize), + _currentTraversalSize(currentTraversalSize) {} + + const TreeTraversalState* _pState; + size_t _previousTraversalSize; + size_t _currentTraversalSize; + + friend class TreeTraversalState; + }; + +#pragma endregion + private: struct TraversalData { TNodePointer pNode; @@ -401,6 +729,366 @@ template class TreeTraversalState { // 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; + + template + friend class TreeTraversalStateDiffIterator; }; +#pragma region Differences Implementation + +template +typename TreeTraversalState::Difference +TreeTraversalState::difference_iterator::operator*() + const noexcept { + if (this->_differenceType == DifferenceType::StateChange) { + CESIUM_ASSERT( + this->_previousIndex >= 0 && + size_t(this->_previousIndex) < + this->_pState->_previousTraversal.size()); + CESIUM_ASSERT( + this->_currentIndex >= 0 && + size_t(this->_currentIndex) < this->_pState->_currentTraversal.size()); + + const TreeTraversalState::TraversalData& + previousData = + this->_pState->_previousTraversal[size_t(this->_previousIndex)]; + const TreeTraversalState::TraversalData& currentData = + this->_pState->_currentTraversal[size_t(this->_currentIndex)]; + + CESIUM_ASSERT(previousData.pNode == currentData.pNode); + + return value_type{ + .pNode = previousData.pNode, + .previousState = previousData.state, + .currentState = currentData.state}; + } else if (this->_differenceType == DifferenceType::NodeOnlyInPrevious) { + CESIUM_ASSERT( + this->_previousIndex >= 0 && + size_t(this->_previousIndex) < + this->_pState->_previousTraversal.size()); + + const TreeTraversalState::TraversalData& data = + this->_pState->_previousTraversal[size_t(this->_previousIndex)]; + + return value_type{ + .pNode = data.pNode, + .previousState = data.state, + .currentState = DEFAULT_STATE}; + } else { + CESIUM_ASSERT( + this->_currentIndex >= 0 && + size_t(this->_currentIndex) < this->_pState->_currentTraversal.size()); + + const TreeTraversalState::TraversalData& data = + this->_pState->_currentTraversal[size_t(this->_currentIndex)]; + + return value_type{ + .pNode = data.pNode, + .previousState = DEFAULT_STATE, + .currentState = data.state}; + } +} + +template +typename TreeTraversalState::Difference +TreeTraversalState::difference_iterator::operator->() + const noexcept { + return this->operator*(); +} + +template +typename TreeTraversalState::difference_iterator& +TreeTraversalState::difference_iterator:: +operator++() noexcept { + this->advanceToNextDifference(); + return *this; +} + +template +typename TreeTraversalState::difference_iterator +TreeTraversalState::difference_iterator::operator++( + int) noexcept { + difference_iterator result = *this; + return ++result; +} + +template +typename TreeTraversalState::difference_iterator +TreeTraversalState::difference_iterator:: + descendantsEnd() { + if (this->_differenceType == DifferenceType::StateChange) { + const TreeTraversalState::TraversalData& + previousData = + this->_pState->_previousTraversal[size_t(this->_previousIndex)]; + const TreeTraversalState::TraversalData& currentData = + this->_pState->_currentTraversal[size_t(this->_currentIndex)]; + + difference_iterator result( + this->_pState, + previousData.nextSiblingIndex, + currentData.nextSiblingIndex); + + result.advanceFromMatchedNodeUnlessDifferent(); + + return result; + } else if (this->_differenceType == DifferenceType::NodeOnlyInPrevious) { + const TreeTraversalState::TraversalData& + previousData = + this->_pState->_previousTraversal[size_t(this->_previousIndex)]; + + difference_iterator result = difference_iterator( + this->_pState, + previousData.nextSiblingIndex, + this->_currentIndex); + result._differenceType = this->_differenceType; + result._nextSiblingIndex = this->_nextSiblingIndex; + + if (previousData.nextSiblingIndex == this->_nextSiblingIndex) { + result._differenceType = DifferenceType::StateChange; + result.advanceFromMatchedNodeUnlessDifferent(); + } + + return result; + } else { + const TreeTraversalState::TraversalData& currentData = + this->_pState->_currentTraversal[size_t(this->_currentIndex)]; + + difference_iterator result = difference_iterator( + this->_pState, + this->_previousIndex, + currentData.nextSiblingIndex); + result._differenceType = this->_differenceType; + result._nextSiblingIndex = this->_nextSiblingIndex; + + if (currentData.nextSiblingIndex == this->_nextSiblingIndex) { + result._differenceType = DifferenceType::StateChange; + result.advanceFromMatchedNodeUnlessDifferent(); + } + + return result; + } +} + +template +bool TreeTraversalState::difference_iterator::operator==( + const TreeTraversalState::difference_iterator& rhs) const noexcept { + return this->_previousIndex == rhs._previousIndex && + this->_currentIndex == rhs._currentIndex && + this->_pState == rhs._pState; +} + +template +bool TreeTraversalState::difference_iterator::operator!=( + const TreeTraversalState::difference_iterator& rhs) const noexcept { + return !(*this == rhs); +} + +template +TreeTraversalState::difference_iterator:: + difference_iterator( + const TreeTraversalState* pState) noexcept + : difference_iterator(pState, 0, 0) { + // The iterator starts at the root tile in both traversals. But either + // traversal could be empty, or the root tile may not be a difference. + bool hasPreviousTraversal = !pState->_previousTraversal.empty(); + bool hasCurrentTraversal = !pState->_currentTraversal.empty(); + + if (hasPreviousTraversal && hasCurrentTraversal) { + this->advanceFromMatchedNodeUnlessDifferent(); + } else if (hasPreviousTraversal) { + // There are no nodes at all in the current traversal, so all previous + // states are differences. + this->_differenceType = DifferenceType::NodeOnlyInPrevious; + this->_nextSiblingIndex = int64_t(pState->_previousTraversal.size()); + } else if (hasCurrentTraversal) { + // There were no nodes at all in the previous traversal, so all current + // states are differences. + this->_differenceType = DifferenceType::NodeOnlyInCurrent; + this->_nextSiblingIndex = int64_t(pState->_currentTraversal.size()); + } else { + // Both traversals are empty, so there are no differences. + } +} + +template +TreeTraversalState::difference_iterator:: + difference_iterator( + const TreeTraversalState* pState, + int64_t previousIndex, + int64_t currentIndex) noexcept + : _pState(pState), + _previousIndex(previousIndex), + _currentIndex(currentIndex), + _differenceType(DifferenceType::StateChange), + _nextSiblingIndex(-1) {} + +template +/*static*/ typename TreeTraversalState< + TNodePointer, + TState>::difference_iterator +TreeTraversalState::difference_iterator::createOnOrAfter( + const TreeTraversalState* pState, + int64_t previousIndex, + int64_t currentIndex) { + CESIUM_ASSERT(previousIndex >= 0); + CESIUM_ASSERT(currentIndex >= 0); + + if (previousIndex >= int64_t(pState->_previousTraversal.size()) || + currentIndex >= int64_t(pState->_currentTraversal.size())) { + // If either index is past the end, they both should be. + CESIUM_ASSERT(previousIndex >= int64_t(pState->_previousTraversal.size())); + CESIUM_ASSERT(currentIndex >= int64_t(pState->_currentTraversal.size())); + + // Return the end iterator. + return difference_iterator( + pState, + int64_t(pState->_previousTraversal.size()), + int64_t(pState->_currentTraversal.size())); + } + + // Valid indices - they must point to the same node. + const TraversalData& previousData = + pState->_previousTraversal[size_t(previousIndex)]; + const TraversalData& currentData = + pState->_currentTraversal[size_t(currentIndex)]; + + CESIUM_ASSERT(previousData.pNode == currentData.pNode); + if (previousData.pNode != currentData.pNode) { + // This shouldn't happen. Stop the iteration by setting this iterator + // equal to the end() iterator. + return difference_iterator( + pState, + int64_t(pState->_previousTraversal.size()), + int64_t(pState->_currentTraversal.size())); + } + + // Create an iterator that may initially _not_ point to a difference. + difference_iterator result(pState, previousIndex, currentIndex); + + if (previousData.state == currentData.state) { + // This is not a valid difference, so move to the next one. + result.advanceFromMatchedNode(); + } + + return result; +} + +template +bool TreeTraversalState::difference_iterator::advanceOnce( + const TraversalData& previousData, + const TraversalData& currentData) { + CESIUM_ASSERT(previousData.pNode == currentData.pNode); + + bool previousTraversalVisitedChildren = + previousData.nextSiblingIndex > this->_previousIndex + 1; + bool currentTraversalVisitedChildren = + currentData.nextSiblingIndex > this->_currentIndex + 1; + + ++this->_previousIndex; + ++this->_currentIndex; + + if (previousTraversalVisitedChildren && !currentTraversalVisitedChildren) { + // No descendants in current traversal, so every previous traversal + // descendant is a difference. + this->_differenceType = DifferenceType::NodeOnlyInPrevious; + this->_nextSiblingIndex = previousData.nextSiblingIndex; + return true; + } else if ( + currentTraversalVisitedChildren && !previousTraversalVisitedChildren) { + // No descendants in previous traversal, so every current traversal + // descendant is a difference. + this->_differenceType = DifferenceType::NodeOnlyInCurrent; + this->_nextSiblingIndex = currentData.nextSiblingIndex; + return true; + } + + return false; +} + +template +void TreeTraversalState::difference_iterator:: + advanceFromMatchedNode( + const TraversalData& previousData, + const TraversalData& currentData) { + bool isStructuralDifference = this->advanceOnce(previousData, currentData); + if (isStructuralDifference) { + // Found a node that exists in one traversal and not the other. + return; + } + + this->advanceFromMatchedNodeUnlessDifferent(); +} + +template +void TreeTraversalState::difference_iterator:: + advanceFromMatchedNode() { + if (this->_previousIndex < + int64_t(this->_pState->_previousTraversal.size()) && + this->_currentIndex < int64_t(this->_pState->_currentTraversal.size())) { + const TraversalData& previousData = + this->_pState->_previousTraversal[size_t(this->_previousIndex)]; + const TraversalData& currentData = + this->_pState->_currentTraversal[size_t(this->_currentIndex)]; + this->advanceFromMatchedNode(previousData, currentData); + } +} + +template +void TreeTraversalState::difference_iterator:: + advanceFromMatchedNodeUnlessDifferent() { + while (this->_previousIndex < + int64_t(this->_pState->_previousTraversal.size()) && + this->_currentIndex < + int64_t(this->_pState->_currentTraversal.size())) { + const TraversalData& previousData = + this->_pState->_previousTraversal[size_t(this->_previousIndex)]; + const TraversalData& currentData = + this->_pState->_currentTraversal[size_t(this->_currentIndex)]; + + CESIUM_ASSERT(previousData.pNode == currentData.pNode); + + if (previousData.state != currentData.state) { + // Found the next difference. + return; + } + + bool isStructuralDifference = this->advanceOnce(previousData, currentData); + if (isStructuralDifference) { + // Found a node that exists in one traversal and not the other. + return; + } + } + + // If we get here without returning, we're done iterating. We should be at the + // end of both traversals. + CESIUM_ASSERT( + this->_previousIndex == + int64_t(this->_pState->_previousTraversal.size())); + CESIUM_ASSERT( + this->_currentIndex == int64_t(this->_pState->_currentTraversal.size())); +} + +template +void TreeTraversalState::difference_iterator:: + advanceToNextDifference() noexcept { + if (this->_differenceType == DifferenceType::StateChange) { + // We're on a matched node, advanced to the next difference. + this->advanceFromMatchedNode(); + } else { + // We're on a node that only exists in one of the traversals. + int64_t index = this->_differenceType == DifferenceType::NodeOnlyInPrevious + ? ++this->_previousIndex + : ++this->_currentIndex; + if (index >= this->_nextSiblingIndex) { + // We reached the end of the nodes that don't exist in the other + // traversal. The next node is guaranteed to exist in both traversals, but + // may or may not have a different state. + this->_differenceType = DifferenceType::StateChange; + this->advanceFromMatchedNodeUnlessDifferent(); + } + } +} + +#pragma endregion + } // namespace CesiumUtility diff --git a/CesiumUtility/test/TestTreeTraversalState.cpp b/CesiumUtility/test/TestTreeTraversalState.cpp index a0c484fbe..8e08518ff 100644 --- a/CesiumUtility/test/TestTreeTraversalState.cpp +++ b/CesiumUtility/test/TestTreeTraversalState.cpp @@ -12,6 +12,23 @@ struct Node { std::string name; }; +std::vector> +getDifferences(const TreeTraversalState& traversalState) { + std::vector> result; + + auto differences = traversalState.differences(); + for (auto it = differences.begin(); it != differences.end(); ++it) { + const auto& difference = *it; + [[maybe_unused]] auto descendantsEnd = it.descendantsEnd(); + result.emplace_back( + difference.pNode, + difference.previousState, + difference.currentState); + } + + return result; +} + } // namespace TEST_CASE("TreeTraversalState") { @@ -81,24 +98,29 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&a); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 1); + traversalState.currentState() = 1; traversalState.beginNode(&b); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 2); + traversalState.currentState() = 2; traversalState.finishNode(&b); traversalState.beginNode(&c); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 3); + traversalState.currentState() = 3; traversalState.beginNode(&e); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 5); + traversalState.currentState() = 5; traversalState.finishNode(&e); traversalState.beginNode(&f); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 6); + traversalState.currentState() = 6; traversalState.finishNode(&f); traversalState.finishNode(&c); @@ -106,10 +128,15 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&d); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 4); + traversalState.currentState() = 4; traversalState.finishNode(&d); traversalState.finishNode(&a); // clang-format on + + SUBCASE("and diff reports no differences") { + CHECK(getDifferences(traversalState).empty()); + } } SUBCASE("Second traversal can skip children") { @@ -119,24 +146,40 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&a); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 1); + traversalState.currentState() = 1; traversalState.beginNode(&b); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 2); + traversalState.currentState() = 2; traversalState.finishNode(&b); traversalState.beginNode(&c); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 3); + traversalState.currentState() = 3; traversalState.finishNode(&c); traversalState.beginNode(&d); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 4); + traversalState.currentState() = 4; traversalState.finishNode(&d); traversalState.finishNode(&a); // clang-format on + + SUBCASE("and diff reports the differences") { + std::vector> differences = + getDifferences(traversalState); + REQUIRE(differences.size() == 2); + CHECK(std::get<0>(differences[0]) == &e); + CHECK(std::get<1>(differences[0]) == 5); + CHECK(std::get<2>(differences[0]) == int()); + CHECK(std::get<0>(differences[1]) == &f); + CHECK(std::get<1>(differences[1]) == 6); + CHECK(std::get<2>(differences[1]) == int()); + } } SUBCASE("Second traversal can visit new children") { @@ -148,19 +191,23 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&a); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 1); + traversalState.currentState() = 1; traversalState.beginNode(&b); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 2); + traversalState.currentState() = 2; traversalState.finishNode(&b); traversalState.beginNode(&c); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 3); + traversalState.currentState() = 3; traversalState.beginNode(&e); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 5); + traversalState.currentState() = 5; traversalState.beginNode(&g); CHECK(traversalState.previousState() == nullptr); @@ -172,6 +219,7 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&f); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 6); + traversalState.currentState() = 6; traversalState.finishNode(&f); traversalState.finishNode(&c); @@ -179,10 +227,20 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&d); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 4); + traversalState.currentState() = 4; traversalState.finishNode(&d); traversalState.finishNode(&a); // clang-format on + + SUBCASE("and diff reports the differences") { + std::vector> differences = + getDifferences(traversalState); + REQUIRE(differences.size() == 1); + CHECK(std::get<0>(differences[0]) == &g); + CHECK(std::get<1>(differences[0]) == int()); + CHECK(std::get<2>(differences[0]) == 7); + } } SUBCASE("Second traversal can add two new levels") { @@ -194,19 +252,23 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&a); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 1); + traversalState.currentState() = 1; traversalState.beginNode(&b); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 2); + traversalState.currentState() = 2; traversalState.finishNode(&b); traversalState.beginNode(&c); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 3); + traversalState.currentState() = 3; traversalState.beginNode(&e); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 5); + traversalState.currentState() = 5; traversalState.beginNode(&g); CHECK(traversalState.previousState() == nullptr); @@ -223,6 +285,7 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&f); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 6); + traversalState.currentState() = 6; traversalState.finishNode(&f); traversalState.finishNode(&c); @@ -230,10 +293,23 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&d); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 4); + traversalState.currentState() = 4; traversalState.finishNode(&d); traversalState.finishNode(&a); // clang-format on + + SUBCASE("and diff reports the differences") { + std::vector> differences = + getDifferences(traversalState); + REQUIRE(differences.size() == 2); + CHECK(std::get<0>(differences[0]) == &g); + CHECK(std::get<1>(differences[0]) == int()); + CHECK(std::get<2>(differences[0]) == 7); + CHECK(std::get<0>(differences[1]) == &h); + CHECK(std::get<1>(differences[1]) == int()); + CHECK(std::get<2>(differences[1]) == 8); + } } SUBCASE( @@ -360,24 +436,46 @@ TEST_CASE("TreeTraversalState") { traversalState.beginNode(&a); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 1); + traversalState.currentState() = 1; traversalState.beginNode(&b); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 2); + traversalState.currentState() = 2; traversalState.finishNode(&b); traversalState.beginNode(&c); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 3); + traversalState.currentState() = 3; traversalState.finishNode(&c); traversalState.beginNode(&d); REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 4); + traversalState.currentState() = 4; traversalState.finishNode(&d); traversalState.finishNode(&a); // clang-format on + + SUBCASE("and diff reports the differences") { + std::vector> differences = + getDifferences(traversalState); + REQUIRE(differences.size() == 4); + CHECK(std::get<0>(differences[0]) == &e); + CHECK(std::get<1>(differences[0]) == 5); + CHECK(std::get<2>(differences[0]) == int()); + CHECK(std::get<0>(differences[1]) == &g); + CHECK(std::get<1>(differences[1]) == 7); + CHECK(std::get<2>(differences[1]) == int()); + CHECK(std::get<0>(differences[2]) == &h); + CHECK(std::get<1>(differences[2]) == 8); + CHECK(std::get<2>(differences[2]) == int()); + CHECK(std::get<0>(differences[3]) == &f); + CHECK(std::get<1>(differences[3]) == 6); + CHECK(std::get<2>(differences[3]) == int()); + } } } @@ -438,11 +536,13 @@ TEST_CASE("TreeTraversalState") { traversalState.currentState() = 1; REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 1); + traversalState.currentState() = 1; traversalState.beginNode(&b); traversalState.currentState() = 2; REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 2); + traversalState.currentState() = 2; traversalState.beginNode(&f); traversalState.currentState() = 6; @@ -460,22 +560,37 @@ TEST_CASE("TreeTraversalState") { traversalState.currentState() = 3; REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 3); + traversalState.currentState() = 3; traversalState.beginNode(&d); traversalState.currentState() = 4; REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 4); + traversalState.currentState() = 4; traversalState.finishNode(&d); traversalState.beginNode(&e); traversalState.currentState() = 5; REQUIRE(traversalState.previousState() != nullptr); CHECK(*traversalState.previousState() == 5); + traversalState.currentState() = 5; traversalState.finishNode(&e); traversalState.finishNode(&c); traversalState.finishNode(&a); // clang-format on + + SUBCASE("and diff reports the differences") { + std::vector> differences = + getDifferences(traversalState); + REQUIRE(differences.size() == 2); + CHECK(std::get<0>(differences[0]) == &f); + CHECK(std::get<1>(differences[0]) == int()); + CHECK(std::get<2>(differences[0]) == 6); + CHECK(std::get<0>(differences[1]) == &g); + CHECK(std::get<1>(differences[1]) == int()); + CHECK(std::get<2>(differences[1]) == 7); + } } }