1//
2// Redistribution and use in source and binary forms, with or without
3// modification, are permitted provided that the following conditions
4// are met:
5// * Redistributions of source code must retain the above copyright
6// notice, this list of conditions and the following disclaimer.
7// * Redistributions in binary form must reproduce the above copyright
8// notice, this list of conditions and the following disclaimer in the
9// documentation and/or other materials provided with the distribution.
10// * Neither the name of NVIDIA CORPORATION nor the names of its
11// contributors may be used to endorse or promote products derived
12// from this software without specific prior written permission.
13//
14// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
15// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
18// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25//
26// Copyright (c) 2008-2021 NVIDIA Corporation. All rights reserved.
27// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
28// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
29
30#ifndef PSFOUNDATION_PSHASHINTERNALS_H
31#define PSFOUNDATION_PSHASHINTERNALS_H
32
33#include "PsBasicTemplates.h"
34#include "PsArray.h"
35#include "PsBitUtils.h"
36#include "PsHash.h"
37#include "foundation/PxIntrinsics.h"
38
39#if PX_VC
40#pragma warning(push)
41#pragma warning(disable : 4127) // conditional expression is constant
42#endif
43namespace physx
44{
45namespace shdfnd
46{
47namespace internal
48{
49template <class Entry, class Key, class HashFn, class GetKey, class Allocator, bool compacting>
50class HashBase : private Allocator
51{
52 void init(uint32_t initialTableSize, float loadFactor)
53 {
54 mBuffer = NULL;
55 mEntries = NULL;
56 mEntriesNext = NULL;
57 mHash = NULL;
58 mEntriesCapacity = 0;
59 mHashSize = 0;
60 mLoadFactor = loadFactor;
61 mFreeList = uint32_t(EOL);
62 mTimestamp = 0;
63 mEntriesCount = 0;
64
65 if(initialTableSize)
66 reserveInternal(size: initialTableSize);
67 }
68
69 public:
70 typedef Entry EntryType;
71
72 HashBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : Allocator(PX_DEBUG_EXP("hashBase"))
73 {
74 init(initialTableSize, loadFactor);
75 }
76
77 HashBase(uint32_t initialTableSize, float loadFactor, const Allocator& alloc) : Allocator(alloc)
78 {
79 init(initialTableSize, loadFactor);
80 }
81
82 HashBase(const Allocator& alloc) : Allocator(alloc)
83 {
84 init(initialTableSize: 64, loadFactor: 0.75f);
85 }
86
87 ~HashBase()
88 {
89 destroy(); // No need to clear()
90
91 if(mBuffer)
92 Allocator::deallocate(mBuffer);
93 }
94
95 static const uint32_t EOL = 0xffffffff;
96
97 PX_INLINE Entry* create(const Key& k, bool& exists)
98 {
99 uint32_t h = 0;
100 if(mHashSize)
101 {
102 h = hash(k);
103 uint32_t index = mHash[h];
104 while(index != EOL && !HashFn().equal(GetKey()(mEntries[index]), k))
105 index = mEntriesNext[index];
106 exists = index != EOL;
107 if(exists)
108 return mEntries + index;
109 }
110 else
111 exists = false;
112
113 if(freeListEmpty())
114 {
115 grow();
116 h = hash(k);
117 }
118
119 uint32_t entryIndex = freeListGetNext();
120
121 mEntriesNext[entryIndex] = mHash[h];
122 mHash[h] = entryIndex;
123
124 mEntriesCount++;
125 mTimestamp++;
126
127 return mEntries + entryIndex;
128 }
129
130 PX_INLINE const Entry* find(const Key& k) const
131 {
132 if(!mEntriesCount)
133 return NULL;
134
135 const uint32_t h = hash(k);
136 uint32_t index = mHash[h];
137 while(index != EOL && !HashFn().equal(GetKey()(mEntries[index]), k))
138 index = mEntriesNext[index];
139 return index != EOL ? mEntries + index : NULL;
140 }
141
142 PX_INLINE bool erase(const Key& k, Entry& e)
143 {
144 if(!mEntriesCount)
145 return false;
146
147 const uint32_t h = hash(k);
148 uint32_t* ptr = mHash + h;
149 while(*ptr != EOL && !HashFn().equal(GetKey()(mEntries[*ptr]), k))
150 ptr = mEntriesNext + *ptr;
151
152 if(*ptr == EOL)
153 return false;
154
155 PX_PLACEMENT_NEW(&e, Entry)(mEntries[*ptr]);
156
157 return eraseInternal(ptr);
158 }
159
160 PX_INLINE bool erase(const Key& k)
161 {
162 if(!mEntriesCount)
163 return false;
164
165 const uint32_t h = hash(k);
166 uint32_t* ptr = mHash + h;
167 while(*ptr != EOL && !HashFn().equal(GetKey()(mEntries[*ptr]), k))
168 ptr = mEntriesNext + *ptr;
169
170 if(*ptr == EOL)
171 return false;
172
173 return eraseInternal(ptr);
174 }
175
176 PX_INLINE uint32_t size() const
177 {
178 return mEntriesCount;
179 }
180
181 PX_INLINE uint32_t capacity() const
182 {
183 return mHashSize;
184 }
185
186 void clear()
187 {
188 if(!mHashSize || mEntriesCount == 0)
189 return;
190
191 destroy();
192
193 intrinsics::memSet(dest: mHash, c: EOL, count: mHashSize * sizeof(uint32_t));
194
195 const uint32_t sizeMinus1 = mEntriesCapacity - 1;
196 for(uint32_t i = 0; i < sizeMinus1; i++)
197 {
198 prefetchLine(ptr: mEntriesNext + i, offset: 128);
199 mEntriesNext[i] = i + 1;
200 }
201 mEntriesNext[mEntriesCapacity - 1] = uint32_t(EOL);
202 mFreeList = 0;
203 mEntriesCount = 0;
204 }
205
206 void reserve(uint32_t size)
207 {
208 if(size > mHashSize)
209 reserveInternal(size);
210 }
211
212 PX_INLINE const Entry* getEntries() const
213 {
214 return mEntries;
215 }
216
217 PX_INLINE Entry* insertUnique(const Key& k)
218 {
219 PX_ASSERT(find(k) == NULL);
220 uint32_t h = hash(k);
221
222 uint32_t entryIndex = freeListGetNext();
223
224 mEntriesNext[entryIndex] = mHash[h];
225 mHash[h] = entryIndex;
226
227 mEntriesCount++;
228 mTimestamp++;
229
230 return mEntries + entryIndex;
231 }
232
233 private:
234 void destroy()
235 {
236 for(uint32_t i = 0; i < mHashSize; i++)
237 {
238 for(uint32_t j = mHash[i]; j != EOL; j = mEntriesNext[j])
239 mEntries[j].~Entry();
240 }
241 }
242
243 template <typename HK, typename GK, class A, bool comp>
244 PX_NOINLINE void copy(const HashBase<Entry, Key, HK, GK, A, comp>& other);
245
246 // free list management - if we're coalescing, then we use mFreeList to hold
247 // the top of the free list and it should always be equal to size(). Otherwise,
248 // we build a free list in the next() pointers.
249
250 PX_INLINE void freeListAdd(uint32_t index)
251 {
252 if(compacting)
253 {
254 mFreeList--;
255 PX_ASSERT(mFreeList == mEntriesCount);
256 }
257 else
258 {
259 mEntriesNext[index] = mFreeList;
260 mFreeList = index;
261 }
262 }
263
264 PX_INLINE void freeListAdd(uint32_t start, uint32_t end)
265 {
266 if(!compacting)
267 {
268 for(uint32_t i = start; i < end - 1; i++) // add the new entries to the free list
269 mEntriesNext[i] = i + 1;
270
271 // link in old free list
272 mEntriesNext[end - 1] = mFreeList;
273 PX_ASSERT(mFreeList != end - 1);
274 mFreeList = start;
275 }
276 else if(mFreeList == EOL) // don't reset the free ptr for the compacting hash unless it's empty
277 mFreeList = start;
278 }
279
280 PX_INLINE uint32_t freeListGetNext()
281 {
282 PX_ASSERT(!freeListEmpty());
283 if(compacting)
284 {
285 PX_ASSERT(mFreeList == mEntriesCount);
286 return mFreeList++;
287 }
288 else
289 {
290 uint32_t entryIndex = mFreeList;
291 mFreeList = mEntriesNext[mFreeList];
292 return entryIndex;
293 }
294 }
295
296 PX_INLINE bool freeListEmpty() const
297 {
298 if(compacting)
299 return mEntriesCount == mEntriesCapacity;
300 else
301 return mFreeList == EOL;
302 }
303
304 PX_INLINE void replaceWithLast(uint32_t index)
305 {
306 PX_PLACEMENT_NEW(mEntries + index, Entry)(mEntries[mEntriesCount]);
307 mEntries[mEntriesCount].~Entry();
308 mEntriesNext[index] = mEntriesNext[mEntriesCount];
309
310 uint32_t h = hash(GetKey()(mEntries[index]));
311 uint32_t* ptr;
312 for(ptr = mHash + h; *ptr != mEntriesCount; ptr = mEntriesNext + *ptr)
313 PX_ASSERT(*ptr != EOL);
314 *ptr = index;
315 }
316
317 PX_INLINE uint32_t hash(const Key& k, uint32_t hashSize) const
318 {
319 return HashFn()(k) & (hashSize - 1);
320 }
321
322 PX_INLINE uint32_t hash(const Key& k) const
323 {
324 return hash(k, mHashSize);
325 }
326
327 PX_INLINE bool eraseInternal(uint32_t* ptr)
328 {
329 const uint32_t index = *ptr;
330
331 *ptr = mEntriesNext[index];
332
333 mEntries[index].~Entry();
334
335 mEntriesCount--;
336 mTimestamp++;
337
338 if (compacting && index != mEntriesCount)
339 replaceWithLast(index);
340
341 freeListAdd(index);
342 return true;
343 }
344
345 void reserveInternal(uint32_t size)
346 {
347 if(!isPowerOfTwo(x: size))
348 size = nextPowerOfTwo(x: size);
349
350 PX_ASSERT(!(size & (size - 1)));
351
352 // decide whether iteration can be done on the entries directly
353 bool resizeCompact = compacting || freeListEmpty();
354
355 // define new table sizes
356 uint32_t oldEntriesCapacity = mEntriesCapacity;
357 uint32_t newEntriesCapacity = uint32_t(float(size) * mLoadFactor);
358 uint32_t newHashSize = size;
359
360 // allocate new common buffer and setup pointers to new tables
361 uint8_t* newBuffer;
362 uint32_t* newHash;
363 uint32_t* newEntriesNext;
364 Entry* newEntries;
365 {
366 uint32_t newHashByteOffset = 0;
367 uint32_t newEntriesNextBytesOffset = newHashByteOffset + newHashSize * sizeof(uint32_t);
368 uint32_t newEntriesByteOffset = newEntriesNextBytesOffset + newEntriesCapacity * sizeof(uint32_t);
369 newEntriesByteOffset += (16 - (newEntriesByteOffset & 15)) & 15;
370 uint32_t newBufferByteSize = newEntriesByteOffset + newEntriesCapacity * sizeof(Entry);
371
372 newBuffer = reinterpret_cast<uint8_t*>(Allocator::allocate(newBufferByteSize, __FILE__, __LINE__));
373 PX_ASSERT(newBuffer);
374
375 newHash = reinterpret_cast<uint32_t*>(newBuffer + newHashByteOffset);
376 newEntriesNext = reinterpret_cast<uint32_t*>(newBuffer + newEntriesNextBytesOffset);
377 newEntries = reinterpret_cast<Entry*>(newBuffer + newEntriesByteOffset);
378 }
379
380 // initialize new hash table
381 intrinsics::memSet(dest: newHash, c: uint32_t(EOL), count: newHashSize * sizeof(uint32_t));
382
383 // iterate over old entries, re-hash and create new entries
384 if(resizeCompact)
385 {
386 // check that old free list is empty - we don't need to copy the next entries
387 PX_ASSERT(compacting || mFreeList == EOL);
388
389 for(uint32_t index = 0; index < mEntriesCount; ++index)
390 {
391 uint32_t h = hash(GetKey()(mEntries[index]), newHashSize);
392 newEntriesNext[index] = newHash[h];
393 newHash[h] = index;
394
395 PX_PLACEMENT_NEW(newEntries + index, Entry)(mEntries[index]);
396 mEntries[index].~Entry();
397 }
398 }
399 else
400 {
401 // copy old free list, only required for non compact resizing
402 intrinsics::memCopy(dest: newEntriesNext, src: mEntriesNext, count: mEntriesCapacity * sizeof(uint32_t));
403
404 for(uint32_t bucket = 0; bucket < mHashSize; bucket++)
405 {
406 uint32_t index = mHash[bucket];
407 while(index != EOL)
408 {
409 uint32_t h = hash(GetKey()(mEntries[index]), newHashSize);
410 newEntriesNext[index] = newHash[h];
411 PX_ASSERT(index != newHash[h]);
412
413 newHash[h] = index;
414
415 PX_PLACEMENT_NEW(newEntries + index, Entry)(mEntries[index]);
416 mEntries[index].~Entry();
417
418 index = mEntriesNext[index];
419 }
420 }
421 }
422
423 // swap buffer and pointers
424 Allocator::deallocate(mBuffer);
425 mBuffer = newBuffer;
426 mHash = newHash;
427 mHashSize = newHashSize;
428 mEntriesNext = newEntriesNext;
429 mEntries = newEntries;
430 mEntriesCapacity = newEntriesCapacity;
431
432 freeListAdd(oldEntriesCapacity, newEntriesCapacity);
433 }
434
435 void grow()
436 {
437 PX_ASSERT((mFreeList == EOL) || (compacting && (mEntriesCount == mEntriesCapacity)));
438
439 uint32_t size = mHashSize == 0 ? 16 : mHashSize * 2;
440 reserve(size);
441 }
442
443 uint8_t* mBuffer;
444 Entry* mEntries;
445 uint32_t* mEntriesNext; // same size as mEntries
446 uint32_t* mHash;
447 uint32_t mEntriesCapacity;
448 uint32_t mHashSize;
449 float mLoadFactor;
450 uint32_t mFreeList;
451 uint32_t mTimestamp;
452 uint32_t mEntriesCount; // number of entries
453
454 public:
455 class Iter
456 {
457 public:
458 PX_INLINE Iter(HashBase& b) : mBucket(0), mEntry(uint32_t(b.EOL)), mTimestamp(b.mTimestamp), mBase(b)
459 {
460 if(mBase.mEntriesCapacity > 0)
461 {
462 mEntry = mBase.mHash[0];
463 skip();
464 }
465 }
466
467 PX_INLINE void check() const
468 {
469 PX_ASSERT(mTimestamp == mBase.mTimestamp);
470 }
471 PX_INLINE const Entry& operator*() const
472 {
473 check();
474 return mBase.mEntries[mEntry];
475 }
476 PX_INLINE Entry& operator*()
477 {
478 check();
479 return mBase.mEntries[mEntry];
480 }
481 PX_INLINE const Entry* operator->() const
482 {
483 check();
484 return mBase.mEntries + mEntry;
485 }
486 PX_INLINE Entry* operator->()
487 {
488 check();
489 return mBase.mEntries + mEntry;
490 }
491 PX_INLINE Iter operator++()
492 {
493 check();
494 advance();
495 return *this;
496 }
497 PX_INLINE Iter operator++(int)
498 {
499 check();
500 Iter i = *this;
501 advance();
502 return i;
503 }
504 PX_INLINE bool done() const
505 {
506 check();
507 return mEntry == mBase.EOL;
508 }
509
510 private:
511 PX_INLINE void advance()
512 {
513 mEntry = mBase.mEntriesNext[mEntry];
514 skip();
515 }
516 PX_INLINE void skip()
517 {
518 while(mEntry == mBase.EOL)
519 {
520 if(++mBucket == mBase.mHashSize)
521 break;
522 mEntry = mBase.mHash[mBucket];
523 }
524 }
525
526 Iter& operator=(const Iter&);
527
528 uint32_t mBucket;
529 uint32_t mEntry;
530 uint32_t mTimestamp;
531 HashBase& mBase;
532 };
533
534 /*!
535 Iterate over entries in a hash base and allow entry erase while iterating
536 */
537 class EraseIterator
538 {
539 public:
540 PX_INLINE EraseIterator(HashBase& b): mBase(b)
541 {
542 reset();
543 }
544
545 PX_INLINE Entry* eraseCurrentGetNext(bool eraseCurrent)
546 {
547 if(eraseCurrent && mCurrentEntryIndexPtr)
548 {
549 mBase.eraseInternal(mCurrentEntryIndexPtr);
550 // if next was valid return the same ptr, if next was EOL search new hash entry
551 if(*mCurrentEntryIndexPtr != mBase.EOL)
552 return mBase.mEntries + *mCurrentEntryIndexPtr;
553 else
554 return traverseHashEntries();
555 }
556
557 // traverse mHash to find next entry
558 if(mCurrentEntryIndexPtr == NULL)
559 return traverseHashEntries();
560
561 const uint32_t index = *mCurrentEntryIndexPtr;
562 if(mBase.mEntriesNext[index] == mBase.EOL)
563 {
564 return traverseHashEntries();
565 }
566 else
567 {
568 mCurrentEntryIndexPtr = mBase.mEntriesNext + index;
569 return mBase.mEntries + *mCurrentEntryIndexPtr;
570 }
571 }
572
573 PX_INLINE void reset()
574 {
575 mCurrentHashIndex = 0;
576 mCurrentEntryIndexPtr = NULL;
577 }
578
579 private:
580 PX_INLINE Entry* traverseHashEntries()
581 {
582 mCurrentEntryIndexPtr = NULL;
583 while (mCurrentEntryIndexPtr == NULL && mCurrentHashIndex < mBase.mHashSize)
584 {
585 if (mBase.mHash[mCurrentHashIndex] != mBase.EOL)
586 {
587 mCurrentEntryIndexPtr = mBase.mHash + mCurrentHashIndex;
588 mCurrentHashIndex++;
589 return mBase.mEntries + *mCurrentEntryIndexPtr;
590 }
591 else
592 {
593 mCurrentHashIndex++;
594 }
595 }
596 return NULL;
597 }
598
599 EraseIterator& operator=(const EraseIterator&);
600 private:
601 uint32_t* mCurrentEntryIndexPtr;
602 uint32_t mCurrentHashIndex;
603 HashBase& mBase;
604 };
605};
606
607template <class Entry, class Key, class HashFn, class GetKey, class Allocator, bool compacting>
608template <typename HK, typename GK, class A, bool comp>
609PX_NOINLINE void
610HashBase<Entry, Key, HashFn, GetKey, Allocator, compacting>::copy(const HashBase<Entry, Key, HK, GK, A, comp>& other)
611{
612 reserve(size: other.mEntriesCount);
613
614 for(uint32_t i = 0; i < other.mEntriesCount; i++)
615 {
616 for(uint32_t j = other.mHash[i]; j != EOL; j = other.mEntriesNext[j])
617 {
618 const Entry& otherEntry = other.mEntries[j];
619
620 bool exists;
621 Entry* newEntry = create(k: GK()(otherEntry), exists);
622 PX_ASSERT(!exists);
623
624 PX_PLACEMENT_NEW(newEntry, Entry)(otherEntry);
625 }
626 }
627}
628
629template <class Key, class HashFn, class Allocator = typename AllocatorTraits<Key>::Type, bool Coalesced = false>
630class HashSetBase
631{
632 PX_NOCOPY(HashSetBase)
633 public:
634 struct GetKey
635 {
636 PX_INLINE const Key& operator()(const Key& e)
637 {
638 return e;
639 }
640 };
641
642 typedef HashBase<Key, Key, HashFn, GetKey, Allocator, Coalesced> BaseMap;
643 typedef typename BaseMap::Iter Iterator;
644
645 HashSetBase(uint32_t initialTableSize, float loadFactor, const Allocator& alloc)
646 : mBase(initialTableSize, loadFactor, alloc)
647 {
648 }
649
650 HashSetBase(const Allocator& alloc) : mBase(64, 0.75f, alloc)
651 {
652 }
653
654 HashSetBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : mBase(initialTableSize, loadFactor)
655 {
656 }
657
658 bool insert(const Key& k)
659 {
660 bool exists;
661 Key* e = mBase.create(k, exists);
662 if(!exists)
663 PX_PLACEMENT_NEW(e, Key)(k);
664 return !exists;
665 }
666
667 PX_INLINE bool contains(const Key& k) const
668 {
669 return mBase.find(k) != 0;
670 }
671 PX_INLINE bool erase(const Key& k)
672 {
673 return mBase.erase(k);
674 }
675 PX_INLINE uint32_t size() const
676 {
677 return mBase.size();
678 }
679 PX_INLINE uint32_t capacity() const
680 {
681 return mBase.capacity();
682 }
683 PX_INLINE void reserve(uint32_t size)
684 {
685 mBase.reserve(size);
686 }
687 PX_INLINE void clear()
688 {
689 mBase.clear();
690 }
691
692 protected:
693 BaseMap mBase;
694};
695
696template <class Key, class Value, class HashFn, class Allocator = typename AllocatorTraits<Pair<const Key, Value> >::Type>
697class HashMapBase
698{
699 PX_NOCOPY(HashMapBase)
700 public:
701 typedef Pair<const Key, Value> Entry;
702
703 struct GetKey
704 {
705 PX_INLINE const Key& operator()(const Entry& e)
706 {
707 return e.first;
708 }
709 };
710
711 typedef HashBase<Entry, Key, HashFn, GetKey, Allocator, true> BaseMap;
712 typedef typename BaseMap::Iter Iterator;
713 typedef typename BaseMap::EraseIterator EraseIterator;
714
715 HashMapBase(uint32_t initialTableSize, float loadFactor, const Allocator& alloc)
716 : mBase(initialTableSize, loadFactor, alloc)
717 {
718 }
719
720 HashMapBase(const Allocator& alloc) : mBase(64, 0.75f, alloc)
721 {
722 }
723
724 HashMapBase(uint32_t initialTableSize = 64, float loadFactor = 0.75f) : mBase(initialTableSize, loadFactor)
725 {
726 }
727
728 bool insert(const Key /*&*/ k, const Value /*&*/ v)
729 {
730 bool exists;
731 Entry* e = mBase.create(k, exists);
732 if(!exists)
733 PX_PLACEMENT_NEW(e, Entry)(k, v);
734 return !exists;
735 }
736
737 Value& operator[](const Key& k)
738 {
739 bool exists;
740 Entry* e = mBase.create(k, exists);
741 if(!exists)
742 PX_PLACEMENT_NEW(e, Entry)(k, Value());
743
744 return e->second;
745 }
746
747 PX_INLINE const Entry* find(const Key& k) const
748 {
749 return mBase.find(k);
750 }
751 PX_INLINE bool erase(const Key& k)
752 {
753 return mBase.erase(k);
754 }
755 PX_INLINE bool erase(const Key& k, Entry& e)
756 {
757 return mBase.erase(k, e);
758 }
759 PX_INLINE uint32_t size() const
760 {
761 return mBase.size();
762 }
763 PX_INLINE uint32_t capacity() const
764 {
765 return mBase.capacity();
766 }
767 PX_INLINE Iterator getIterator()
768 {
769 return Iterator(mBase);
770 }
771 PX_INLINE EraseIterator getEraseIterator()
772 {
773 return EraseIterator(mBase);
774 }
775 PX_INLINE void reserve(uint32_t size)
776 {
777 mBase.reserve(size);
778 }
779 PX_INLINE void clear()
780 {
781 mBase.clear();
782 }
783
784 protected:
785 BaseMap mBase;
786};
787}
788
789} // namespace shdfnd
790} // namespace physx
791
792#if PX_VC
793#pragma warning(pop)
794#endif
795#endif // #ifndef PSFOUNDATION_PSHASHINTERNALS_H
796

source code of qtquick3dphysics/src/3rdparty/PhysX/source/foundation/include/PsHashInternals.h