33
44// / @file tree/ValueAccessor.h
55// /
6- // / @brief When traversing a grid in a spatially coherent pattern (e.g.,
7- // / iterating over neighboring voxels), request a @c ValueAccessor from the
8- // / grid (with Grid::getAccessor()) and use the accessor's @c getValue() and
9- // / @c setValue() methods. These will typically be significantly faster than
10- // / accessing voxels directly in the grid's tree.
6+ // / @brief ValueAccessors are designed to help accelerate accesses into the
7+ // / OpenVDB Tree structures by storing caches to Tree branches. When
8+ // / traversing a grid in a spatially coherent pattern (e.g., iterating over
9+ // / neighboring voxels), the same branches and nodes of the underlying tree
10+ // / can be hit. If you do this using the Tree/RootNode methods directly,
11+ // / traversal will occur at O(log(n)) (or O(n) depending on the hash map
12+ // / implementation) for every access. However, using a ValueAccessor allows
13+ // / for the Accessor to cache previously visited Nodes, providing possible
14+ // / subsequent access speeds of O(1) if the next access is close to a
15+ // / previously cached Node. Accessors are lightweight and can be configured
16+ // / to cache any number of arbitrary Tree levels.
17+ // /
18+ // / The ValueAccessor interfaces matches that of compatible OpenVDB Tree
19+ // / nodes. You can request an Accessor from a Grid (with Grid::getAccessor())
20+ // / or construct one directly from a Tree. You can use, for example, the
21+ // / accessor's @c getValue() and @c setValue() methods in place of those on
22+ // / OpenVDB Nodes/Trees.
1123// /
1224// / @par Example:
1325// / @code
@@ -200,26 +212,7 @@ class ValueAccessorBase
200212
201213// /////////////////////////////////////////////////////////////////////////////
202214
203- // / @brief A small class that contains a Mutex which is derived from by the
204- // / internal Value Accessor Implementation. This allows for the empty base
205- // / class optimization to be performed in the case where a Mutex/Lock is not
206- // / in use. From C++20 we can instead switch to [[no_unique_address]].
207- template <typename MutexT>
208- struct ValueAccessorLock
209- {
210- inline auto lock () const { return std::scoped_lock (m); }
211- private:
212- mutable MutexT m;
213- };
214-
215- // / @brief Specialization for the case where no Mutex is in use. See above.
216- template <>
217- struct ValueAccessorLock <void >
218- {
219- inline constexpr auto lock () const { return 0 ; }
220- };
221-
222- // /////////////////////////////////////////////////////////////////////////////
215+ // / @cond OPENVDB_DOCS_INTERNAL
223216
224217namespace value_accessor_internal
225218{
@@ -269,22 +262,115 @@ struct NodeListBuilder<NodeChainT, RootLevel, openvdb::index_sequence<Is...>>
269262 using ListT = typename NodeListBuilderImpl<NodeChainT, Is..., RootLevel>::ListT;
270263};
271264
265+
266+ template <typename TreeTypeT, typename NodeT>
267+ struct EnableLeafBuffer
268+ {
269+ using LeafNodeT = typename TreeTypeT::LeafNodeType;
270+ static constexpr bool value =
271+ std::is_same<NodeT, LeafNodeT>::value &&
272+ std::is_same<typename LeafNodeT::Buffer::StorageType,
273+ typename LeafNodeT::ValueType>::value;
274+ };
275+
276+ template <typename TreeTypeT, size_t ... Is>
277+ struct EnableLeafBuffer <TreeTypeT, openvdb::index_sequence<Is...>>
278+ {
279+ // Empty integer seq, no nodes being caches
280+ static constexpr bool value = false ;
281+ };
282+
283+ template <typename TreeTypeT, size_t First, size_t ... Is>
284+ struct EnableLeafBuffer <TreeTypeT, openvdb::index_sequence<First, Is...>>
285+ {
286+ private:
287+ using NodeChainT = typename TreeTypeT::RootNodeType::NodeChainType;
288+ using FirstNodeT = typename NodeChainT::template Get<First>;
289+ public:
290+ static constexpr bool value = EnableLeafBuffer<TreeTypeT, FirstNodeT>::value;
291+ };
292+
272293} // namespace value_accessor_internal
273294
295+ // / @endcond
296+
297+ // /////////////////////////////////////////////////////////////////////////////
298+
299+ // / The following classes exist to perform empty base class optimizations
300+ // / with the final ValueAccessor implementation. Depending on the template
301+ // / types provided to the derived implementation, some member variables may not
302+ // / be necessary (mutex, leaf buffer cache, etc). These classes allow for these
303+ // / variables to be compiled out. Note that from C++20 we can switch to
304+ // / [[no_unique_address]] member annotations instead.
305+
306+ // / @brief A small class that contains a Mutex which is derived from by the
307+ // / internal Value Accessor Implementation. This allows for the empty base
308+ // / class optimization to be performed in the case where a Mutex/Lock is not
309+ // / in use. From C++20 we can instead switch to [[no_unique_address]].
310+ template <typename MutexT>
311+ struct ValueAccessorLock
312+ {
313+ inline auto lock () const { return std::scoped_lock (m); }
314+ private:
315+ mutable MutexT m;
316+ };
317+
318+ // / @brief Specialization for the case where no Mutex is in use. See above.
319+ template <>
320+ struct ValueAccessorLock <void >
321+ {
322+ inline constexpr auto lock () const { return 0 ; }
323+ };
324+
325+ // / @brief A small class that contains a cached pointer to a LeafNode data
326+ // / buffer which is derived from by the internal Value Accessor
327+ // / Implementation. This allows for the empty base class optimization to be
328+ // / performed in the case where a LeafNode does not store a contiguous
329+ // / index-able buffer. From C++20 we can instead switch to
330+ // / [[no_unique_address]].
331+ template <typename TreeTypeT, typename IntegerSequence, typename Enable = void >
332+ struct ValueAccessorLeafBuffer
333+ {
334+ template <typename NodeT>
335+ static constexpr bool BypassLeafAPI =
336+ std::is_same<NodeT, typename TreeTypeT::LeafNodeType>::value;
337+ inline const typename TreeTypeT::ValueType* buffer () { assert (mBuffer ); return mBuffer ; }
338+ inline const typename TreeTypeT::ValueType* buffer () const { assert (mBuffer ); return mBuffer ; }
339+ inline void setBuffer (const typename TreeTypeT::ValueType* b) const { mBuffer = b; }
340+ private:
341+ mutable const typename TreeTypeT::ValueType* mBuffer ;
342+ };
343+
344+ // / @brief Specialization for the case where a Leaf Buffer cannot be cached.
345+ // These methods should never be invoked. See above.
346+ template <typename TreeTypeT, typename IntegerSequence>
347+ struct ValueAccessorLeafBuffer <TreeTypeT, IntegerSequence,
348+ typename std::enable_if<
349+ !value_accessor_internal::EnableLeafBuffer<TreeTypeT, IntegerSequence>::value
350+ >::type>
351+ {
352+ template <typename > static constexpr bool BypassLeafAPI = false ;
353+ inline constexpr typename TreeTypeT::ValueType* buffer () { assert (false ); return nullptr ; }
354+ inline constexpr typename TreeTypeT::ValueType* buffer () const { assert (false ); return nullptr ; }
355+ inline constexpr void setBuffer (const typename TreeTypeT::ValueType*) const { assert (false ); }
356+ };
357+
274358// /////////////////////////////////////////////////////////////////////////////
275359
276360// / @brief The Value Accessor Implementation and API methods. The majoirty of
277- // / the API matches the API of an OpenVDB Tree.
361+ // / the API matches the API of a compatible OpenVDB Tree Node .
278362template <typename _TreeType, bool IsSafe, typename MutexT, typename IntegerSequence>
279363class ValueAccessorImpl final :
280364 public ValueAccessorBase<_TreeType, IsSafe>,
365+ public ValueAccessorLeafBuffer<_TreeType, IntegerSequence>,
281366 public ValueAccessorLock<MutexT>
282367{
283368public:
284369 // / @note Not strictly the only Base Type but provided for backwards
285370 // / compatibility.
286371 using BaseT = ValueAccessorBase<_TreeType, IsSafe>;
287372 using LockT = ValueAccessorLock<MutexT>;
373+ using LeafCacheT = ValueAccessorLeafBuffer<_TreeType, IntegerSequence>;
288374
289375 using TreeType = _TreeType;
290376 using ValueType = typename TreeType::ValueType;
@@ -322,13 +408,12 @@ class ValueAccessorImpl final :
322408 // / index-able array of values then this returns true.
323409 template <typename NodeT>
324410 static constexpr bool IsLeafAndBypassLeafAPI =
325- std::is_same<NodeT, LeafNodeT>::value &&
326- std::is_same<typename LeafNodeT::Buffer::StorageType, ValueType>::value;
411+ LeafCacheT::template BypassLeafAPI<NodeT>;
327412
328413 // / @brief Helper alias which is true if the lowest cached node level is
329- // / a LeafNode type and has a compatible value type for optimized
330- // / access
331- static constexpr bool BypassLeafAPI = IsLeafAndBypassLeafAPI<NodeTypeAtLevel<0 >>;
414+ // / a LeafNode type and has a compatible value type for optimized access.
415+ static constexpr bool BypassLeafAPI =
416+ IsLeafAndBypassLeafAPI<NodeTypeAtLevel<0 >>;
332417
333418 // / @brief The number of node levels that this accessor can cache,
334419 // / excluding the RootNode.
@@ -339,10 +424,10 @@ class ValueAccessorImpl final :
339424 // / @brief Constructor from a tree
340425 ValueAccessorImpl (TreeType& tree)
341426 : BaseT(tree)
427+ , LeafCacheT()
342428 , LockT()
343429 , mKeys ()
344- , mNodes ()
345- , mBuffer () {
430+ , mNodes () {
346431 this ->clear ();
347432 }
348433
@@ -380,8 +465,7 @@ class ValueAccessorImpl final :
380465 if (!this ->isHashed <NodeType>(xyz)) return nullptr ;
381466
382467 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
383- assert (mBuffer );
384- return &(mBuffer [LeafNodeT::coordToOffset (xyz)]);
468+ return &(LeafCacheT::buffer ()[LeafNodeT::coordToOffset (xyz)]);
385469 }
386470 else {
387471 auto node = mNodes .template get <Idx>();
@@ -413,9 +497,8 @@ class ValueAccessorImpl final :
413497 assert (node);
414498
415499 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
416- assert (mBuffer );
417500 const auto offset = LeafNodeT::coordToOffset (xyz);
418- value = mBuffer [offset];
501+ value = LeafCacheT::buffer () [offset];
419502 return node->isValueOn (offset);
420503 }
421504 else {
@@ -473,9 +556,8 @@ class ValueAccessorImpl final :
473556 assert (node);
474557
475558 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
476- assert (mBuffer );
477559 const auto offset = LeafNodeT::coordToOffset (xyz);
478- const_cast <ValueType&>(mBuffer [offset]) = value;
560+ const_cast <ValueType&>(LeafCacheT::buffer () [offset]) = value;
479561 const_cast <NodeType*>(node)->setValueOn (offset);
480562 }
481563 else {
@@ -504,8 +586,7 @@ class ValueAccessorImpl final :
504586 if (!this ->isHashed <NodeType>(xyz)) return false ;
505587
506588 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
507- assert (mBuffer );
508- const_cast <ValueType&>(mBuffer [LeafNodeT::coordToOffset (xyz)]) = value;
589+ const_cast <ValueType&>(LeafCacheT::buffer ()[LeafNodeT::coordToOffset (xyz)]) = value;
509590 }
510591 else {
511592 auto node = mNodes .template get <Idx>();
@@ -532,9 +613,8 @@ class ValueAccessorImpl final :
532613 assert (node);
533614
534615 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
535- assert (mBuffer );
536616 const auto offset = LeafNodeT::coordToOffset (xyz);
537- const_cast <ValueType&>(mBuffer [offset]) = value;
617+ const_cast <ValueType&>(LeafCacheT::buffer () [offset]) = value;
538618 const_cast <NodeType*>(node)->setValueOff (offset);
539619 }
540620 else {
@@ -558,9 +638,8 @@ class ValueAccessorImpl final :
558638 assert (node);
559639
560640 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
561- assert (mBuffer );
562641 const auto offset = LeafNodeT::coordToOffset (xyz);
563- op (const_cast <ValueType&>(mBuffer [offset]));
642+ op (const_cast <ValueType&>(LeafCacheT::buffer () [offset]));
564643 const_cast <NodeType*>(node)->setActiveState (offset, true );
565644 }
566645 else {
@@ -583,10 +662,9 @@ class ValueAccessorImpl final :
583662 assert (node);
584663
585664 if constexpr (IsLeafAndBypassLeafAPI<NodeType>) {
586- assert (mBuffer );
587665 const auto offset = LeafNodeT::coordToOffset (xyz);
588666 bool state = node->isValueOn (offset);
589- op (const_cast <ValueType&>(mBuffer [offset]), state);
667+ op (const_cast <ValueType&>(LeafCacheT::buffer () [offset]), state);
590668 const_cast <NodeType*>(node)->setActiveState (offset, state);
591669 }
592670 else {
@@ -803,7 +881,9 @@ class ValueAccessorImpl final :
803881 {
804882 mKeys .fill (Coord::max ());
805883 mNodes .foreach ([](auto & node) { node = nullptr ; });
806- mBuffer = nullptr ;
884+ if constexpr (BypassLeafAPI) {
885+ LeafCacheT::setBuffer (nullptr );
886+ }
807887 if (BaseT::mTree ) {
808888 static constexpr int64_t Idx = NodeLevelList::template Index<RootNodeT>;
809889 mNodes .template get <Idx>() = const_cast <RootNodeT*>(&(BaseT::mTree ->root ()));
@@ -860,7 +940,7 @@ class ValueAccessorImpl final :
860940 mKeys [Idx] = xyz & ~(NodeT::DIM-1 );
861941 mNodes .template get <Idx>() = const_cast <NodeT*>(node);
862942 if constexpr (IsLeafAndBypassLeafAPI<NodeT>) {
863- mBuffer = node->buffer ().data ();
943+ LeafCacheT::setBuffer ( node->buffer ().data () );
864944 }
865945 }
866946 }
@@ -938,7 +1018,6 @@ class ValueAccessorImpl final :
9381018private:
9391019 mutable std::array<Coord, NumCacheLevels> mKeys ;
9401020 mutable typename NodePtrList::AsTupleList mNodes ;
941- mutable const ValueType* mBuffer ;
9421021}; // ValueAccessorImpl
9431022
9441023} // namespace tree
0 commit comments