From: sunyab Date: Sat, 3 Feb 2024 04:00:32 +0000 (-0800) Subject: pcp: Cache prim index traversals for variant selections X-Git-Tag: accepted/tizen/unified/x/20250428.070456~5^2~26 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=776eaada2f59916d447d8b349980b09d4566f395;p=platform%2Fcore%2Fuifw%2FOpenUSD.git pcp: Cache prim index traversals for variant selections Pcp now caches information computed used when searching for variant selections during prim indexing. This includes the translated path at each node where we look for authored variant selections, as well as whether that node actually contained any authored variant selections. This greatly improves performance when dealing with prim indexes that require recomputing variant selections many times. For example, in a synthetic test case involving a prim with 1000 references, adding 500 variant sets to that prim previously took ~25.5 seconds; with this change, it takes ~6.8 seconds. In a production test case with many inherit and variant arcs, the time spent computing variants dropped from ~30 seconds to ~4.5 seconds. The caching mechanism involves two new objects: - PcpNodeRef_PrivateSubtreeConstIterator, an iterator for traversing over a specified subtree within a prim index. - Pcp_TraversalCache, an object that uses the above iterator to traverse a subtree and caches information at each node in the traversal. Together, these objects allow us to transform the search for variant selections from a recursive traversal to a simple iteration, avoid expensive path translations, and skip over nodes where we've already determined no authored variant selections exist. (Internal change: 2314082) (Internal change: 2314085) (Internal change: 2314122) --- diff --git a/pxr/usd/pcp/CMakeLists.txt b/pxr/usd/pcp/CMakeLists.txt index b2ac36dd7..d5b07163b 100644 --- a/pxr/usd/pcp/CMakeLists.txt +++ b/pxr/usd/pcp/CMakeLists.txt @@ -63,6 +63,9 @@ pxr_library(pcp statistics utils + PRIVATE_HEADERS + traversalCache.h + PYTHON_CPPFILES moduleDeps.cpp diff --git a/pxr/usd/pcp/composeSite.cpp b/pxr/usd/pcp/composeSite.cpp index 72b6a0aa2..e739bfdef 100644 --- a/pxr/usd/pcp/composeSite.cpp +++ b/pxr/usd/pcp/composeSite.cpp @@ -474,6 +474,19 @@ PcpComposeSiteVariantSelections( } } +bool +PcpComposeSiteHasVariantSelections( + PcpLayerStackRefPtr const &layerStack, + SdfPath const &path) +{ + for (auto const& layer : layerStack->GetLayers()) { + if (layer->HasField(path, SdfFieldKeys->VariantSelection)) { + return true; + } + } + return false; +} + void PcpComposeSiteChildNames(SdfLayerRefPtrVector const &layers, SdfPath const &path, diff --git a/pxr/usd/pcp/composeSite.h b/pxr/usd/pcp/composeSite.h index 5adcd00be..c47409036 100644 --- a/pxr/usd/pcp/composeSite.h +++ b/pxr/usd/pcp/composeSite.h @@ -371,6 +371,12 @@ PcpComposeSiteVariantSelections(PcpNodeRef const &node, node.GetLayerStack(), node.GetPath(), result); } +PCP_API +bool +PcpComposeSiteHasVariantSelections( + PcpLayerStackRefPtr const &layerStack, + SdfPath const &path); + /// Compose child names. /// If the optional \p orderField is provided, its order will be applied. PCP_API diff --git a/pxr/usd/pcp/node.h b/pxr/usd/pcp/node.h index f6b2f7bdd..96244ace2 100644 --- a/pxr/usd/pcp/node.h +++ b/pxr/usd/pcp/node.h @@ -331,6 +331,8 @@ private: friend class PcpNodeRef_ChildrenReverseIterator; friend class PcpNodeRef_PrivateChildrenConstIterator; friend class PcpNodeRef_PrivateChildrenConstReverseIterator; + friend class PcpNodeRef_PrivateSubtreeConstIterator; + template friend class Pcp_TraversalCache; // Private constructor for internal use. PcpNodeRef(PcpPrimIndex_Graph* graph, size_t idx) diff --git a/pxr/usd/pcp/node_Iterator.h b/pxr/usd/pcp/node_Iterator.h index 833cebc25..de7d6c453 100644 --- a/pxr/usd/pcp/node_Iterator.h +++ b/pxr/usd/pcp/node_Iterator.h @@ -28,6 +28,7 @@ #include "pxr/pxr.h" #include "pxr/usd/pcp/node.h" +#include "pxr/usd/pcp/primIndex.h" #include "pxr/usd/pcp/primIndex_Graph.h" PXR_NAMESPACE_OPEN_SCOPE @@ -244,6 +245,137 @@ Pcp_GetChildren(const PcpNodeRef& node) IteratorType(node, /* end = */ true)); } +/// \class PcpNodeRef_PrivateSubtreeConstIterator +/// +/// Object used to iterate over all nodes in a subtree rooted at a +/// given node in the prim index graph in strong-to-weak order. +class PcpNodeRef_PrivateSubtreeConstIterator +{ +public: + using iterator_category = std::forward_iterator_tag; + using value_type = const PcpNodeRef; + using reference = const PcpNodeRef&; + using pointer = const PcpNodeRef*; + using difference_type = std::ptrdiff_t; + + /// If \p end is false, constructs an iterator representing the + /// beginning of the subtree of nodes starting at \p node. + /// + /// If \p end is true, constructs an iterator representing the + /// next weakest node after the subtree of nodes starting at \p node. + /// This may be an invalid node if \p node is the root node. + PcpNodeRef_PrivateSubtreeConstIterator(const PcpNodeRef& node, bool end) + : _node(node) + , _nodes(&_node._graph->_GetNode(0)) + , _pruneChildren(false) + { + if (end) { + _MoveToNext(); + } + } + + /// Causes the next increment of this iterator to ignore + /// descendants of the current node. + void PruneChildren() + { + _pruneChildren = true; + } + + reference operator*() const { return _node; } + pointer operator->() const { return &_node; } + + PcpNodeRef_PrivateSubtreeConstIterator& operator++() + { + if (_pruneChildren || !_MoveToFirstChild()) { + _MoveToNext(); + } + _pruneChildren = false; + return *this; + } + + PcpNodeRef_PrivateSubtreeConstIterator operator++(int) + { + PcpNodeRef_PrivateSubtreeConstIterator result(*this); + ++(*this); + return result; + } + + bool operator==(const PcpNodeRef_PrivateSubtreeConstIterator& other) const + { return _node == other._node; } + + bool operator!=(const PcpNodeRef_PrivateSubtreeConstIterator& other) const + { return !(*this == other); } + +private: + // If the current node has child nodes, move this iterator to the + // first child and return true. Otherwise return false. + bool _MoveToFirstChild() + { + auto& curNodeIdx = _node._nodeIdx; + const auto& nodeIndexes = _nodes[curNodeIdx].indexes; + const auto& invalid = PcpPrimIndex_Graph::_Node::_invalidNodeIndex; + + if (nodeIndexes.firstChildIndex != invalid) { + curNodeIdx = nodeIndexes.firstChildIndex; + return true; + } + return false; + } + + // If the current node has a direct sibling, move this iterator to + // that node. Otherwise, move this iterator to the next sibling of + // the nearest ancestor node with siblings. If no such node exists, + // (i.e., the current node is the weakest node in the index), this + // iterator will point to an invalid node. + void _MoveToNext() + { + auto& curNodeIdx = _node._nodeIdx; + const PcpPrimIndex_Graph::_Node::_Indexes* nodeIndexes = nullptr; + const auto& invalid = PcpPrimIndex_Graph::_Node::_invalidNodeIndex; + + while (curNodeIdx != invalid) { + // See if we can move to the current node's next sibling. + nodeIndexes = &_nodes[curNodeIdx].indexes; + if (nodeIndexes->nextSiblingIndex != invalid) { + curNodeIdx = nodeIndexes->nextSiblingIndex; + break; + } + + // If we can't, move to the current node's parent and try again. + curNodeIdx = nodeIndexes->arcParentIndex; + } + } + +private: + PcpNodeRef _node; + const PcpPrimIndex_Graph::_Node* _nodes; + bool _pruneChildren; +}; + +// Wrapper type for range-based for loops. +class PcpNodeRef_PrivateSubtreeConstRange +{ +public: + PcpNodeRef_PrivateSubtreeConstRange(const PcpNodeRef& node) + : _begin(node, /* end = */ false) + , _end(node, /* end = */ true) + { } + + PcpNodeRef_PrivateSubtreeConstIterator begin() const { return _begin; } + PcpNodeRef_PrivateSubtreeConstIterator end() const { return _end; } + +private: + PcpNodeRef_PrivateSubtreeConstIterator _begin, _end; +}; + +/// Return node range for subtree rooted at the given \p node. +inline +PcpNodeRef_PrivateSubtreeConstRange +Pcp_GetSubtreeRange(const PcpNodeRef& node) +{ + return PcpNodeRef_PrivateSubtreeConstRange(node); +} + PXR_NAMESPACE_CLOSE_SCOPE #endif // PXR_USD_PCP_NODE_ITERATOR_H diff --git a/pxr/usd/pcp/primIndex.cpp b/pxr/usd/pcp/primIndex.cpp index 540272da2..2780af3c6 100644 --- a/pxr/usd/pcp/primIndex.cpp +++ b/pxr/usd/pcp/primIndex.cpp @@ -41,6 +41,7 @@ #include "pxr/usd/pcp/primIndex_StackFrame.h" #include "pxr/usd/pcp/statistics.h" #include "pxr/usd/pcp/strengthOrdering.h" +#include "pxr/usd/pcp/traversalCache.h" #include "pxr/usd/pcp/types.h" #include "pxr/usd/pcp/utils.h" #include "pxr/usd/ar/resolver.h" @@ -1000,6 +1001,26 @@ struct Pcp_PrimIndexer using _TaskUniq = pxr_tsl::robin_set; _TaskUniq taskUniq; + // Caches for finding variant selections in the prim index. The map + // of caches is constructed lazily because this map isn't always + // needed. In particular, prim indexing doesn't look for variant + // selections in recursive prim indexing calls. + struct _VariantSelectionInfo + { + // Path in associate node's layer stack at which variant + // selections are authored. + SdfPath sitePath; + + // Whether authored selections were found or not yet checked. + enum Status { AuthoredSelections, NoSelections, Unknown }; + Status status = Unknown; + }; + + using _VariantTraversalCache = Pcp_TraversalCache<_VariantSelectionInfo>; + using _VariantTraversalCaches = std::unordered_map< + std::pair, _VariantTraversalCache, TfHash>; + std::optional<_VariantTraversalCaches> variantTraversalCache; + const bool evaluateImpliedSpecializes; const bool evaluateVariants; @@ -1029,6 +1050,16 @@ struct Pcp_PrimIndexer return _GetOriginatingIndex(previousFrame, outputs); } + _VariantTraversalCache& GetVariantTraversalCache( + PcpNodeRef const& node, SdfPath const& pathInNode) { + if (!variantTraversalCache) { + variantTraversalCache.emplace(); + } + + return variantTraversalCache->try_emplace( + {node, pathInNode}, node, pathInNode).first->second; + } + // Map the given node's path to the root of the final prim index // being computed. SdfPath MapNodePathToRoot(PcpNodeRef const& node) const { @@ -3760,106 +3791,82 @@ _ComposeVariantSelectionForNode( const SdfPath& pathInNode, const std::string & vset, std::string *vsel, - PcpNodeRef *nodeWithVsel, Pcp_PrimIndexer *indexer) { - TF_VERIFY(!pathInNode.IsEmpty()); - - // We are using path-translation to walk between nodes, so we - // are working exclusively in namespace paths, which must have - // no variant selection. - TF_VERIFY(!pathInNode.ContainsPrimVariantSelection(), - "Unexpected variant selection in namespace path <%s>", - pathInNode.GetText()); - - // If this node has an authored selection, use that. - // Note that we use this even if the authored selection is - // the empty string, which explicitly selects no variant. - if (_NodeCanContributeToVariant(node, pathInNode)) { - PcpLayerStackSite site(node.GetLayerStack(), pathInNode); - // pathInNode is a namespace path, not a storage path, - // so it will contain no variant selection (as verified above). - // To find the storage site, we need to insert any variant - // selection for this node. - if (node.GetArcType() == PcpArcTypeVariant) { - // We need to use the variant node's path at introduction - // instead of it's current path (i.e. node.GetPath()) because - // pathInNode may be an ancestor of the current path when - // dealing with ancestral variants. - const SdfPath variantPath = node.GetPathAtIntroduction(); - site.path = pathInNode.ReplacePrefix( - variantPath.StripAllVariantSelections(), - variantPath); - } - - std::unordered_set exprVarDependencies; - PcpErrorVector errors; + std::unordered_set exprVarDependencies; + PcpErrorVector errors; - const bool foundSelection = - PcpComposeSiteVariantSelection( - site.layerStack, site.path, vset, vsel, - &exprVarDependencies, &errors); + const bool foundSelection = + PcpComposeSiteVariantSelection( + node.GetLayerStack(), pathInNode, vset, vsel, + &exprVarDependencies, &errors); - if (!exprVarDependencies.empty()) { - indexer->outputs->expressionVariablesDependency.AddDependencies( - site.layerStack, std::move(exprVarDependencies)); - } - - if (!errors.empty()) { - for (const PcpErrorBasePtr& err : errors) { - indexer->RecordError(err); - } - } + if (!exprVarDependencies.empty()) { + indexer->outputs->expressionVariablesDependency.AddDependencies( + node.GetLayerStack(), std::move(exprVarDependencies)); + } - if (foundSelection) { - *nodeWithVsel = node; - return true; + if (!errors.empty()) { + for (const PcpErrorBasePtr& err : errors) { + indexer->RecordError(err); } } - return false; + return foundSelection; } // Check the tree of nodes rooted at the given node for any node // representing a prior selection for the given variant set for the path. static bool _FindPriorVariantSelection( - const PcpNodeRef& node, - const SdfPath &pathInNode, + const PcpNodeRef& startNode, + const SdfPath &pathInStartNode, const std::string & vset, std::string *vsel, - PcpNodeRef *nodeWithVsel) + PcpNodeRef *nodeWithVsel, + Pcp_PrimIndexer *indexer) { - // If this node represents a variant selection at the same - // effective depth of namespace, then check its selection. - if (node.GetArcType() == PcpArcTypeVariant) { - const SdfPath nodePathAtIntroduction = node.GetPathAtIntroduction(); - const std::pair nodeVsel = - nodePathAtIntroduction.GetVariantSelection(); - if (nodeVsel.first == vset) { - // The node has a variant selection for the variant set we're - // looking for, but we still have to check that the node actually - // represents the prim path we're choosing a variant selection for - // (as opposed to a different prim path that just happens to have - // a variant set with the same name. - if (nodePathAtIntroduction.GetPrimPath() == pathInNode) { - *vsel = nodeVsel.second; - *nodeWithVsel = node; - return true; - } - } - } + auto& traverser = + indexer->GetVariantTraversalCache(startNode, pathInStartNode); - TF_FOR_ALL(child, Pcp_GetChildrenRange(node)) { - const SdfPath pathInChild = - child->GetMapToParent().MapTargetToSource(pathInNode); - if (pathInChild.IsEmpty()) { - continue; - } + // Don't use a range-based for loop here so we can avoid asking for + // the path in the current node (which incurs expensive path translations) + // until we're absolutely sure we need it. + for (auto it = traverser.begin(), e = traverser.end(); it != e; ++it) { + const PcpNodeRef node = it.Node(); - if (_FindPriorVariantSelection( - *child, pathInChild, vset, vsel, nodeWithVsel)) { - return true; + // If this node represents a variant selection at the same + // effective depth of namespace, then check its selection. + if (node.GetArcType() == PcpArcTypeVariant) { + const SdfPath nodePathAtIntroduction = node.GetPathAtIntroduction(); + const std::pair nodeVsel = + nodePathAtIntroduction.GetVariantSelection(); + if (nodeVsel.first == vset) { + const SdfPath& pathInNode = it.PathInNode(); + + // If the path didn't translate to this node, it won't translate + // to any of the node's children, so we might as well prune the + // traversal here. + // + // We don't do this check earlier because we don't want to call + // PathInNode unless absolutely necessary, as it runs relatively + // expensive path translations. + if (pathInNode.IsEmpty()) { + it.PruneChildren(); + continue; + } + + // The node has a variant selection for the variant set we're + // looking for, but we still have to check that the node + // actually represents the prim path we're choosing a variant + // selection for (as opposed to a different prim path that just + // happens to have a variant set with the same name. + if (nodePathAtIntroduction.GetPrimPath() == it.PathInNode()) { + *vsel = nodeVsel.second; + *nodeWithVsel = node; + return true; + } + } } } @@ -3868,27 +3875,70 @@ _FindPriorVariantSelection( static bool _ComposeVariantSelectionAcrossNodes( - const PcpNodeRef& node, - const SdfPath& pathInNode, + const PcpNodeRef& startNode, + const SdfPath& pathInStartNode, const std::string & vset, std::string *vsel, PcpNodeRef *nodeWithVsel, Pcp_PrimIndexer *indexer) { // Compose variant selection in strong-to-weak order. - if (_ComposeVariantSelectionForNode( - node, pathInNode, vset, vsel, nodeWithVsel, indexer)) { - return true; - } + auto& traverser = + indexer->GetVariantTraversalCache(startNode, pathInStartNode); - TF_FOR_ALL(child, Pcp_GetChildrenRange(node)) { - const PcpNodeRef& childNode = *child; - const SdfPath pathInChildNode = - childNode.GetMapToParent().MapTargetToSource(pathInNode); + for (auto it = traverser.begin(), e = traverser.end(); it != e; ++it) { + auto [node, pathInNode, info] = *it; + + // If path translation to this node failed, it will fail for all + // other children so we can skip them entirely + if (pathInNode.IsEmpty()) { + it.PruneChildren(); + continue; + } + + if (!_NodeCanContributeToVariant(node, pathInNode)) { + continue; + } - if (!pathInChildNode.IsEmpty() && - _ComposeVariantSelectionAcrossNodes( - *child, pathInChildNode, vset, vsel, nodeWithVsel, indexer)) { + // Precompute whether the layer stack has any authored variant + // selections and cache that away. + using Info = Pcp_PrimIndexer::_VariantSelectionInfo; + if (info.status == Info::Unknown) { + info.sitePath = [&node=node, &pathInNode=pathInNode]() { + // pathInNode is a namespace path, not a storage path, + // so it will contain no variant selection (as verified above). + // To find the storage site, we need to insert any variant + // selection for this node. + if (node.GetArcType() == PcpArcTypeVariant) { + // We need to use the variant node's path at introduction + // instead of it's current path (i.e. node.GetPath()) because + // pathInNode may be an ancestor of the current path when + // dealing with ancestral variants. + const SdfPath variantPath = node.GetPathAtIntroduction(); + return pathInNode.ReplacePrefix( + variantPath.StripAllVariantSelections(), + variantPath); + } + return pathInNode; + }(); + + info.status = + PcpComposeSiteHasVariantSelections( + node.GetLayerStack(), info.sitePath) ? + Info::AuthoredSelections : Info::NoSelections; + } + + // If no variant selections are authored here, we can skip. + if (info.status == Info::NoSelections) { + continue; + } + + // If this node has an authored selection, use that. + // Note that we use this even if the authored selection is + // the empty string, which explicitly selects no variant. + if (_ComposeVariantSelectionForNode( + node, info.sitePath, vset, vsel, indexer)) { + *nodeWithVsel = node; return true; } } @@ -3943,7 +3993,7 @@ _ComposeVariantSelection( // First check if we have already resolved this variant set in the current // prim index. if (_FindPriorVariantSelection( - startNode, pathInStartNode, vset, vsel, nodeWithVsel)) { + startNode, pathInStartNode, vset, vsel, nodeWithVsel, indexer)) { PCP_INDEXING_MSG( indexer, node, *nodeWithVsel, diff --git a/pxr/usd/pcp/primIndex_Graph.h b/pxr/usd/pcp/primIndex_Graph.h index 3ff00b3e3..0cadf001f 100644 --- a/pxr/usd/pcp/primIndex_Graph.h +++ b/pxr/usd/pcp/primIndex_Graph.h @@ -234,6 +234,8 @@ private: friend class PcpNodeRef_ChildrenReverseIterator; friend class PcpNodeRef_PrivateChildrenConstIterator; friend class PcpNodeRef_PrivateChildrenConstReverseIterator; + friend class PcpNodeRef_PrivateSubtreeConstIterator; + template friend class Pcp_TraversalCache; // NOTE: These accessors assume the consumer will be changing the node // and may cause shared node data to be copied locally. diff --git a/pxr/usd/pcp/traversalCache.h b/pxr/usd/pcp/traversalCache.h new file mode 100644 index 000000000..db621d6d3 --- /dev/null +++ b/pxr/usd/pcp/traversalCache.h @@ -0,0 +1,228 @@ +// +// Copyright 2024 Pixar +// +// Licensed under the Apache License, Version 2.0 (the "Apache License") +// with the following modification; you may not use this file except in +// compliance with the Apache License and the following modification to it: +// Section 6. Trademarks. is deleted and replaced with: +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor +// and its affiliates, except as required to comply with Section 4(c) of +// the License and to reproduce the content of the NOTICE file. +// +// You may obtain a copy of the Apache License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the Apache License with the above modification is +// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the Apache License for the specific +// language governing permissions and limitations under the Apache License. +// +#ifndef PXR_USD_PCP_TRAVERSAL_CACHE_H +#define PXR_USD_PCP_TRAVERSAL_CACHE_H + +#include "pxr/pxr.h" + +#include "pxr/usd/pcp/node.h" +#include "pxr/usd/pcp/node_Iterator.h" +#include "pxr/usd/pcp/primIndex_Graph.h" +#include "pxr/usd/sdf/path.h" + +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +/// \class Pcp_TraversalCache +/// +/// Caches the traversal of a subtree in a prim index starting at a +/// given node and with a specified path within that node's layer stack. +/// As clients traverse through the subtree, the starting path will +/// be translated to each node and cached, so that repeated traversals +/// will not incur the same path translation costs. Clients may also +/// store data associated with each node in the subtree. +template +class Pcp_TraversalCache +{ +public: + /// \class iterator + /// Object for iterating over the subtree of nodes cached by + /// the owning Pcp_TraversalCache. + class iterator + { + public: + /// value type is a tuple of (Node(), PathInNode(), AssociatedData()). + /// See performance note on PathInNode(). + using value_type = std::tuple; + + /// Return the current node. + PcpNodeRef Node() + { + return *_iter; + } + + /// Return the original traversal path given to the owning + /// Pcp_TraversalCache translated to the current node. + /// + /// This function will translate and cache the traversal path for + /// this node and all parent nodes if they have not already been + /// computed. + SdfPath const& PathInNode() + { + return *_owner->_GetEntry(*_iter, /* computePaths = */ true).path; + } + + /// Return a reference to the data associated with the current node. + Data& AssociatedData() + { + return _owner->_GetEntry(*_iter, /* computePaths = */ false).data; + } + + /// Return value_type. Note that this will incur the cost of path + /// translations described in PathInNode(). If you don't need the + /// translated path, use one of the other member functions to avoid + /// this cost. + value_type operator*() + { + _Entry& e = _owner->_GetEntry(*_iter, /* computePaths = */ true); + return std::tie(*_iter, *e.path, e.data); + } + + /// Causes the next increment of this iterator to ignore descendants + /// of the current node. + void PruneChildren() + { + _iter.PruneChildren(); + } + + iterator& operator++() + { + ++_iter; + return *this; + } + + iterator operator++(int) + { + iterator result(*this); + ++_iter; + return result; + } + + bool operator==(iterator const& rhs) const + { return std::tie(_owner, _iter) == std::tie(rhs._owner, rhs._iter); } + + bool operator!=(iterator const& rhs) const + { return !(*this == rhs); } + + private: + friend class Pcp_TraversalCache; + iterator( + Pcp_TraversalCache* owner, + PcpNodeRef_PrivateSubtreeConstIterator iter) + : _owner(owner) + , _iter(iter) + { } + + Pcp_TraversalCache* const _owner = nullptr; + PcpNodeRef_PrivateSubtreeConstIterator _iter; + }; + + /// Construct a traversal cache for the subtree rooted at \p startNode and + /// the path \p pathInNode. \p pathInNode must be in \p startNode's + /// namespace. + Pcp_TraversalCache(PcpNodeRef const& startNode, SdfPath const& pathInNode) + : _startNode(startNode) + { + _ResizeForGraph(); + _cache[_startNode._GetNodeIndex()].path = pathInNode; + } + + Pcp_TraversalCache(Pcp_TraversalCache const&) = delete; + Pcp_TraversalCache(Pcp_TraversalCache &&) = delete; + Pcp_TraversalCache& operator=(Pcp_TraversalCache const&) = delete; + Pcp_TraversalCache& operator=(Pcp_TraversalCache &&) = delete; + + iterator begin() + { + _ResizeForGraph(); + return iterator( + this, + PcpNodeRef_PrivateSubtreeConstIterator( + _startNode, /* end = */ false)); + } + + iterator end() + { + _ResizeForGraph(); + return iterator( + this, + PcpNodeRef_PrivateSubtreeConstIterator( + _startNode, /* end = */ true)); + } + +private: + struct _Entry + { + // Traversal path translated to the entry's corresponding node + std::optional path; + + // Client data associated with this entry's corresponding node. + Data data; + }; + + void _ResizeForGraph() + { + PcpPrimIndex_Graph const* graph = _startNode.GetOwningGraph(); + + // We assume the graph will never shrink. + TF_VERIFY(graph->_GetNumNodes() >= _cache.size()); + + if (graph->_GetNumNodes() > _cache.size()) { + _cache.resize(graph->_GetNumNodes()); + } + } + + SdfPath _TranslatePathsForNode(PcpNodeRef const& node) + { + // "Recursively" map the path from the parent node to this node. + // This terminates because we'll eventually reach _startNode, + // and we populated its path in the c'tor. + _Entry& entry = _cache[node._GetNodeIndex()]; + if (!entry.path) { + PcpNodeRef const parentNode = node.GetParentNode(); + _Entry& parentEntry = _cache[parentNode._GetNodeIndex()]; + if (!parentEntry.path) { + parentEntry.path = _TranslatePathsForNode(parentNode); + } + + SdfPath const& pathInParent = *(parentEntry.path); + entry.path = pathInParent.IsEmpty() ? SdfPath() : + node.GetMapToParent().MapTargetToSource(pathInParent); + } + + return *entry.path; + } + + _Entry& _GetEntry(PcpNodeRef const& node, bool computePaths) + { + TF_VERIFY(node._GetNodeIndex() < _cache.size()); + + // If requested, make sure the translated path is populated before + // returning the _Entry to the caller. + if (computePaths) { + _TranslatePathsForNode(node); + } + return _cache[node._GetNodeIndex()]; + } + + PcpNodeRef _startNode; + std::vector<_Entry> _cache; +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif