mirror of
				https://github.com/Zelda64Recomp/Zelda64Recomp.git
				synced 2025-10-30 08:03:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1366 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1366 lines
		
	
	
	
		
			44 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // This file is originally from the repo: https://github.com/SergeyMakeev/SlotMap.
 | |
| // The original license and code are as follows:
 | |
| 
 | |
| // MIT License
 | |
| //
 | |
| // Copyright (c) 2022 Sergey Makeev
 | |
| //
 | |
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
| // of this software and associated documentation files (the "Software"), to deal
 | |
| // in the Software without restriction, including without limitation the rights
 | |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | |
| // copies of the Software, and to permit persons to whom the Software is
 | |
| // furnished to do so, subject to the following conditions:
 | |
| //
 | |
| // The above copyright notice and this permission notice shall be included in all
 | |
| // copies or substantial portions of the Software.
 | |
| //
 | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | |
| // SOFTWARE.
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <cstring>
 | |
| #include <deque>
 | |
| #include <functional>
 | |
| #include <optional>
 | |
| #include <stdint.h>
 | |
| #include <vector>
 | |
| #include <algorithm>
 | |
| 
 | |
| 
 | |
| #include <inttypes.h>
 | |
| #define PRIslotkey PRIu64
 | |
| 
 | |
| // TODO (detailed desc): you could override memory allocator by defining SLOT_MAP_ALLOC/SLOT_MAP_FREE macroses
 | |
| #if !defined(SLOT_MAP_ALLOC) || !defined(SLOT_MAP_FREE)
 | |
| 
 | |
| #if defined(_WIN32)
 | |
| // Windows
 | |
| #include <xmmintrin.h>
 | |
| #define SLOT_MAP_ALLOC(sizeInBytes, alignment) _mm_malloc(sizeInBytes, alignment)
 | |
| #define SLOT_MAP_FREE(ptr) _mm_free(ptr)
 | |
| #else
 | |
| // Posix
 | |
| #include <stdlib.h>
 | |
| #define SLOT_MAP_ALLOC(sizeInBytes, alignment) aligned_alloc(alignment, sizeInBytes)
 | |
| #define SLOT_MAP_FREE(ptr) free(ptr)
 | |
| #endif
 | |
| 
 | |
| #endif
 | |
| 
 | |
| // extern void _onAssertionFailed(const char* expression, const char* srcFile, unsigned int srcLine);
 | |
| //#define SLOT_MAP_ASSERT(expression) (void)((!!(expression)) || (_onAssertionFailed(#expression, __FILE__, (unsigned int)(__LINE__)), 0))
 | |
| 
 | |
| // TODO (detailed desc): you could override asserts by defining SLOT_MAP_ASSERT macro
 | |
| #if !defined(SLOT_MAP_ASSERT)
 | |
| #include <assert.h>
 | |
| #define SLOT_MAP_ASSERT(expression) assert(expression)
 | |
| #endif
 | |
| 
 | |
| namespace stl
 | |
| {
 | |
| // STL compatible allocator
 | |
| // Note: some platforms (macOS) does not support alignments smaller than `alignof(void*)`
 | |
| template <class T, size_t Alignment = std::max(alignof(T), alignof(void*))> struct Allocator
 | |
| {
 | |
|   public:
 | |
|     using value_type = T;
 | |
|     using pointer = T*;
 | |
|     using const_pointer = const T*;
 | |
|     using reference = T&;
 | |
|     using const_reference = const T&;
 | |
|     using size_type = size_t;
 | |
|     using difference_type = ptrdiff_t;
 | |
| 
 | |
|     template <class U> struct rebind
 | |
|     {
 | |
|         using other = Allocator<U, Alignment>;
 | |
|     };
 | |
| 
 | |
|     Allocator() noexcept {}
 | |
|     Allocator(const Allocator& /*other*/) noexcept {}
 | |
| 
 | |
|     template <typename U> Allocator(const Allocator<U, Alignment>& /* other */) noexcept {}
 | |
| 
 | |
|     ~Allocator() {}
 | |
| 
 | |
|     pointer address(reference x) const noexcept { return &x; }
 | |
|     const_pointer address(const_reference x) const noexcept { return &x; }
 | |
| 
 | |
|     pointer allocate(size_type n, [[maybe_unused]] const void* hint = 0)
 | |
|     {
 | |
|         size_t alignment = Alignment;
 | |
|         n = std::max(n, alignment);
 | |
|         pointer p = reinterpret_cast<pointer>(SLOT_MAP_ALLOC((sizeof(value_type) * n), alignment));
 | |
|         SLOT_MAP_ASSERT(p);
 | |
|         return p;
 | |
|     }
 | |
| 
 | |
|     void deallocate(pointer p, size_type /* n */) { SLOT_MAP_FREE(p); }
 | |
| 
 | |
|     size_type max_size() const noexcept { return std::numeric_limits<size_type>::max() / sizeof(value_type); }
 | |
| 
 | |
|     template <class U, class... Args> void construct(U* p, Args&&... args)
 | |
|     {
 | |
|         new (reinterpret_cast<void*>(p)) U(std::forward<Args>(args)...);
 | |
|     }
 | |
|     template <class U> void destroy(U* p) { p->~U(); }
 | |
| };
 | |
| 
 | |
| template <class T1, class T2, size_t Alignment>
 | |
| bool operator==(const Allocator<T1, Alignment>& /*lhs*/, const Allocator<T2, Alignment>& /*rhs*/) noexcept
 | |
| {
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| template <class T1, class T2, size_t Alignment>
 | |
| bool operator!=(const Allocator<T1, Alignment>& /*lhs*/, const Allocator<T2, Alignment>& /*rhs*/) noexcept
 | |
| {
 | |
|     return false;
 | |
| }
 | |
| } // namespace stl
 | |
| 
 | |
| namespace dod
 | |
| {
 | |
| 
 | |
| /*
 | |
| Even though slot map keys are technically typeless (uint64_t), we artificially add a new type to get extra compiler checks.
 | |
| 
 | |
| i.e., the following code should not compile
 | |
| ```
 | |
| slot_map<std::string> strings;
 | |
| slot_map<int> numbers;
 | |
| slot_map<int>::key numKey =  numbers.emplace(3);
 | |
| const std::string* value = strings.get(numKey);
 | |
| ```
 | |
| */
 | |
| 
 | |
| /*
 | |
| 
 | |
| 64-bit key
 | |
| 
 | |
| | Component      |  Number of bits        |
 | |
| | ---------------|------------------------|
 | |
| | tag            |  12                    |
 | |
| | version        |  20 (0..1,048,575      |
 | |
| | index          |  32 (0..4,294,967,295) |
 | |
| 
 | |
| */
 | |
| template <typename T> struct slot_map_key64
 | |
| {
 | |
|     using id_type = uint64_t;
 | |
|     using version_t = uint32_t;
 | |
|     using index_t = uint32_t;
 | |
|     using tag_t = uint16_t;
 | |
| 
 | |
|     static inline constexpr version_t kInvalidVersion = 0x0u;
 | |
|     static inline constexpr version_t kMinVersion = 0x1u;
 | |
|     static inline constexpr version_t kMaxVersion = 0x0fffffu;
 | |
|     static inline constexpr index_t kMaxIndex = 0xffffffffu;
 | |
|     static inline constexpr tag_t kMaxTag = 0x0fffu;
 | |
|     
 | |
|     static inline constexpr id_type kIndexMask = 0x00000000ffffffffull;
 | |
|     
 | |
|     static inline constexpr id_type kVersionMask = 0x0fffff00000000ull;
 | |
|     static inline constexpr id_type kVersionShift = 32ull;
 | |
| 
 | |
|     static inline constexpr id_type kHandleTagMask = 0xfff0000000000000ull;
 | |
|     static inline constexpr id_type kHandleTagShift = 52ull;
 | |
| 
 | |
|     static inline constexpr slot_map_key64 make(version_t version, index_t index) noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(version != kInvalidVersion);
 | |
|         SLOT_MAP_ASSERT(index <= kMaxIndex);
 | |
|         id_type v = (static_cast<id_type>(version) << kVersionShift) & kVersionMask;
 | |
|         id_type i = (static_cast<id_type>(index)) & kIndexMask;
 | |
|         return slot_map_key64{v | i};
 | |
|     }
 | |
| 
 | |
|     inline size_t hash() const noexcept { return std::hash<id_type>{}(raw); }
 | |
| 
 | |
|     static inline slot_map_key64 clearTagAndUpdateVersion(slot_map_key64 key, version_t version) noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(version != kInvalidVersion);
 | |
|         id_type ver = (static_cast<id_type>(version) << kVersionShift) & kVersionMask;
 | |
|         return slot_map_key64{((key.raw & (~kVersionMask)) | ver) & (~kHandleTagMask)};
 | |
|     }
 | |
|     static inline index_t toIndex(slot_map_key64 key) noexcept { return static_cast<index_t>(key.raw & kIndexMask); }
 | |
|     static inline version_t toVersion(slot_map_key64 key) noexcept
 | |
|     {
 | |
|         return static_cast<version_t>((key.raw & kVersionMask) >> kVersionShift);
 | |
|     }
 | |
|     static inline version_t increaseVersion(version_t version) noexcept { return (version + 1); }
 | |
| 
 | |
|     inline tag_t get_tag() const noexcept { return static_cast<tag_t>((raw & kHandleTagMask) >> kHandleTagShift); }
 | |
|     inline void set_tag(tag_t tag) noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(tag <= kMaxTag);
 | |
|         id_type ud = (static_cast<id_type>(tag) << kHandleTagShift) & kHandleTagMask;
 | |
|         raw = ((raw & (~kHandleTagMask)) | ud);
 | |
|     }
 | |
| 
 | |
|     slot_map_key64() noexcept = default;
 | |
|     explicit slot_map_key64(id_type raw) noexcept
 | |
|         : raw(raw)
 | |
|     {
 | |
|     }
 | |
|     slot_map_key64(const slot_map_key64&) noexcept = default;
 | |
|     slot_map_key64& operator=(const slot_map_key64&) noexcept = default;
 | |
|     slot_map_key64(slot_map_key64&&) noexcept = default;
 | |
|     slot_map_key64& operator=(slot_map_key64&&) noexcept = default;
 | |
| 
 | |
|     bool operator==(const slot_map_key64& other) const noexcept { return raw == other.raw; }
 | |
|     bool operator<(const slot_map_key64& other) const noexcept { return raw < other.raw; }
 | |
| 
 | |
|     // implicit conversion to id_type (useful for printing and debug)
 | |
|     operator id_type() const noexcept { return raw; }
 | |
| 
 | |
|     static inline slot_map_key64 invalid() noexcept { return slot_map_key64{0}; }
 | |
| 
 | |
|     id_type raw;
 | |
| };
 | |
| 
 | |
| /*
 | |
| 
 | |
| 32-bit key
 | |
| 
 | |
| | Component      |  Number of bits     |
 | |
| | ---------------|---------------------|
 | |
| | tag            |  2                  |
 | |
| | version        |  10 (0..1023)       |
 | |
| | index          |  20 (0..1,048,576)  |
 | |
| 
 | |
| */
 | |
| template <typename T> struct slot_map_key32
 | |
| {
 | |
|     using id_type = uint32_t;
 | |
|     using version_t = uint16_t;
 | |
|     using index_t = uint32_t;
 | |
|     using tag_t = uint8_t;
 | |
| 
 | |
|     static inline constexpr version_t kInvalidVersion = 0x0u;
 | |
|     static inline constexpr version_t kMinVersion = 0x1u;
 | |
|     static inline constexpr version_t kMaxVersion = 0x0400u;
 | |
|     static inline constexpr index_t kMaxIndex = 0x000fffffu;
 | |
|     static inline constexpr tag_t kMaxTag = 0x03u;
 | |
| 
 | |
|     static inline constexpr id_type kIndexMask = 0x0fffffull;
 | |
| 
 | |
|     static inline constexpr id_type kVersionMask = 0x3ff00000ull;
 | |
|     static inline constexpr id_type kVersionShift = 20ull;
 | |
| 
 | |
|     static inline constexpr id_type kHandleTagMask = 0xc0000000ull;
 | |
|     static inline constexpr id_type kHandleTagShift = 30ull;
 | |
| 
 | |
|     static inline constexpr slot_map_key32 make(version_t version, index_t index) noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(version != kInvalidVersion);
 | |
|         SLOT_MAP_ASSERT(index <= kMaxIndex);
 | |
|         id_type v = (static_cast<id_type>(version) << kVersionShift) & kVersionMask;
 | |
|         id_type i = (static_cast<id_type>(index)) & kIndexMask;
 | |
|         return slot_map_key32{v | i};
 | |
|     }
 | |
| 
 | |
|     inline size_t hash() const noexcept { return std::hash<id_type>{}(raw); }
 | |
| 
 | |
|     static inline slot_map_key32 clearTagAndUpdateVersion(slot_map_key32 key, version_t version) noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(version != kInvalidVersion);
 | |
|         id_type ver = (static_cast<id_type>(version) << kVersionShift) & kVersionMask;
 | |
|         return slot_map_key32{((key.raw & (~kVersionMask)) | ver) & (~kHandleTagMask)};
 | |
|     }
 | |
|     static inline index_t toIndex(slot_map_key32 key) noexcept { return static_cast<index_t>(key.raw & kIndexMask); }
 | |
|     static inline version_t toVersion(slot_map_key32 key) noexcept
 | |
|     {
 | |
|         return static_cast<version_t>((key.raw & kVersionMask) >> kVersionShift);
 | |
|     }
 | |
|     static inline version_t increaseVersion(version_t version) noexcept { return (version + 1); }
 | |
| 
 | |
|     inline tag_t get_tag() const noexcept { return static_cast<tag_t>((raw & kHandleTagMask) >> kHandleTagShift); }
 | |
|     inline void set_tag(tag_t tag) noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(tag <= kMaxTag);
 | |
|         id_type ud = (static_cast<id_type>(tag) << kHandleTagShift) & kHandleTagMask;
 | |
|         raw = ((raw & (~kHandleTagMask)) | ud);
 | |
|     }
 | |
| 
 | |
|     slot_map_key32() noexcept = default;
 | |
|     explicit slot_map_key32(id_type raw) noexcept
 | |
|         : raw(raw)
 | |
|     {
 | |
|     }
 | |
|     slot_map_key32(const slot_map_key32&) noexcept = default;
 | |
|     slot_map_key32& operator=(const slot_map_key32&) noexcept = default;
 | |
|     slot_map_key32(slot_map_key32&&) noexcept = default;
 | |
|     slot_map_key32& operator=(slot_map_key32&&) noexcept = default;
 | |
| 
 | |
|     bool operator==(const slot_map_key32& other) const noexcept { return raw == other.raw; }
 | |
|     bool operator<(const slot_map_key32& other) const noexcept { return raw < other.raw; }
 | |
| 
 | |
|     // implicit conversion to id_type (useful for printing and debug)
 | |
|     operator id_type() const noexcept { return raw; }
 | |
| 
 | |
|     static inline slot_map_key32 invalid() noexcept { return slot_map_key32{0}; }
 | |
| 
 | |
|     id_type raw;
 | |
| };
 | |
| 
 | |
| /*
 | |
|   A slot map is a high-performance associative container with persistent unique keys to access stored values. Upon insertion, a key is
 | |
|   returned that can be used to later access or remove the values. Insertion, removal, and access are all guaranteed to take O(1) time (best,
 | |
|   worst, and average case) Great for storing collections of objects that need stable, safe references but have no clear ownership.
 | |
| 
 | |
|   The difference between a std::unordered_map and a slot map is that the slot map generates and returns the key when inserting a value. A
 | |
|   key is always unique and will only refer to the value that was inserted.
 | |
| 
 | |
|   Usage example:
 | |
|   ```
 | |
|   slot_map<std::string> strings;
 | |
|   auto red = strings.emplace("Red");
 | |
|   auto green = strings.emplace("Green");
 | |
|   auto blue = strings.emplace("Blue");
 | |
| 
 | |
|   const std::string* val1 = strings.get(red);
 | |
|   if (val1)
 | |
|   {
 | |
|     printf("red = '%s'\n", val1->c_str());
 | |
|   }
 | |
| 
 | |
|   strings.erase(green);
 | |
|   printf("%d\n", strings.has(green));
 | |
|   printf("%d\n", strings.has(blue));
 | |
|   ```
 | |
| 
 | |
|   Output:
 | |
|   ```
 | |
|   red = 'Red'
 | |
|   0
 | |
|   1
 | |
|   ```
 | |
| 
 | |
| */
 | |
| 
 | |
| /*
 | |
|   Usefull reading:
 | |
| 
 | |
|   Niklas Gray
 | |
|   Building a Data-Oriented Entity System (part 1), 2014
 | |
|   http://bitsquid.blogspot.com/2014/08/building-data-oriented-entity-system.html
 | |
| 
 | |
|   Noel Llopis
 | |
|   Managing Data Relationships, 2010
 | |
|   https://gamesfromwithin.com/managing-data-relationships
 | |
| 
 | |
|   Stefan Reinalter
 | |
|   Adventures in data-oriented design - Part 3c: External References, 2013
 | |
|   https://blog.molecular-matters.com/2013/07/24/adventures-in-data-oriented-design-part-3c-external-references/
 | |
| 
 | |
|   Niklas Gray
 | |
|   Managing Decoupling Part 4 - The ID Lookup Table, 2011
 | |
|   https://bitsquid.blogspot.com/2011/09/managing-decoupling-part-4-id-lookup.html
 | |
| 
 | |
|   Sander Mertens
 | |
|   Making the most of ECS identifiers, 2020
 | |
|   https://ajmmertens.medium.com/doing-a-lot-with-a-little-ecs-identifiers-25a72bd2647
 | |
| 
 | |
|   Michele Caini
 | |
|   ECS back and forth. Part 9 - Sparse sets and EnTT, 2020
 | |
|   https://skypjack.github.io/2020-08-02-ecs-baf-part-9/
 | |
| 
 | |
|   Andre Weissflog
 | |
|   Handles are the better pointers, 2018
 | |
|   https://floooh.github.io/2018/06/17/handles-vs-pointers.html
 | |
| 
 | |
|   Allan Deutsch
 | |
|   C++Now 2017: "The Slot Map Data Structure", 2017
 | |
|   https://www.youtube.com/watch?v=SHaAR7XPtNU
 | |
| 
 | |
|   Jeff Gates
 | |
|   Init, Update, Draw - Data Arrays, 2012
 | |
|   https://greysphere.tumblr.com/post/31601463396/data-arrays
 | |
| */
 | |
| template <typename T, typename TKeyType = slot_map_key64<T>, size_t PAGESIZE = 4096, size_t MINFREEINDICES = 64> class slot_map
 | |
| {
 | |
|   public:
 | |
|     using key = TKeyType;
 | |
|     using version_t = typename TKeyType::version_t;
 | |
|     using index_t = typename TKeyType::index_t;
 | |
|     using tag_t = typename TKeyType::tag_t;
 | |
|     using size_type = uint32_t;
 | |
| 
 | |
|     /*
 | |
|         kPageSize = 4096 (default)
 | |
| 
 | |
|         Page size in elements. When all page items(slots) are disabled we automatically release that page to save memory.
 | |
|         The page-based allocator also helps with pointers' stability and prevents memory spikes on reallocation.
 | |
| 
 | |
|         4096 seems like a reasonable number of items per page.
 | |
|         I.e., for simple uint32_t type, it will take 16Kb of RAM per page = good compromise between CPU cache utilization,
 | |
|         allocation granularity, and memory overhead from deactivated slots.
 | |
|     */
 | |
|     static inline constexpr size_type kPageSize = static_cast<size_type>(PAGESIZE);
 | |
| 
 | |
|     /*
 | |
|         kMinFreeIndices = 64 (default)
 | |
| 
 | |
|         When a slot is reused, its version is automatically incremented (to make existing keys invalid).
 | |
|         But since we only use 16-bits for version numbers, there is a possibility that the version counter will wrap around,
 | |
|         and a new item will get the same key as a removed item.
 | |
| 
 | |
|         Once the version counter overflows, we disable that slot so that no new keys are returned for this slot
 | |
|         (this gives us a guarantee that there are no key collisions)
 | |
| 
 | |
|         To prevent version overflow from happening too often, we need to ensure that we don't reuse the same slot too often.
 | |
|         So we do not reuse indices as long as their number is below a certain threshold.
 | |
|     */
 | |
|     static inline constexpr size_type kMinFreeIndices = static_cast<size_type>(MINFREEINDICES);
 | |
| 
 | |
|   private:
 | |
|     using ValueStorage = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
 | |
| 
 | |
|     struct Meta
 | |
|     {
 | |
|         version_t version; // note: 0 is reserved for kInvalidVersion
 | |
|         uint8_t tombstone; // note: we only need 1 bit for tombstone marker
 | |
|         uint8_t inactive;  // note: we only need 1 bit for inactive marker
 | |
|     };
 | |
| 
 | |
|     struct Page
 | |
|     {
 | |
|         void* rawMemory;
 | |
|         ValueStorage* values;
 | |
|         Meta* meta;
 | |
|         size_type numInactiveSlots;
 | |
|         size_type numUsedElements;
 | |
| 
 | |
|         Page() noexcept
 | |
|             : rawMemory(nullptr)
 | |
|             , values(nullptr)
 | |
|             , meta(nullptr)
 | |
|             , numInactiveSlots(0)
 | |
|             , numUsedElements(0)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         Page(const Page&) = delete;
 | |
|         Page& operator=(const Page&) = delete;
 | |
|         Page& operator=(Page&&) = delete;
 | |
|         Page(Page&& other) noexcept
 | |
|             : rawMemory(nullptr)
 | |
|             , values(nullptr)
 | |
|             , meta(nullptr)
 | |
|             , numInactiveSlots(0)
 | |
|             , numUsedElements(0)
 | |
|         {
 | |
|             std::swap(rawMemory, other.rawMemory);
 | |
|             std::swap(meta, other.meta);
 | |
|             std::swap(values, other.values);
 | |
|             std::swap(numInactiveSlots, other.numInactiveSlots);
 | |
|             std::swap(numUsedElements, other.numUsedElements);
 | |
|         }
 | |
|         ~Page() { deallocate(); }
 | |
| 
 | |
|         void deallocate()
 | |
|         {
 | |
|             if (!rawMemory)
 | |
|             {
 | |
|                 SLOT_MAP_ASSERT(!values);
 | |
|                 SLOT_MAP_ASSERT(!meta);
 | |
|                 return;
 | |
|             }
 | |
|             SLOT_MAP_ASSERT(values);
 | |
|             SLOT_MAP_ASSERT(meta);
 | |
| 
 | |
|             SLOT_MAP_FREE(rawMemory);
 | |
|             rawMemory = nullptr;
 | |
|             values = nullptr;
 | |
|             meta = nullptr;
 | |
|         }
 | |
| 
 | |
|         void allocate()
 | |
|         {
 | |
|             SLOT_MAP_ASSERT(!rawMemory);
 | |
|             SLOT_MAP_ASSERT(!values);
 | |
|             SLOT_MAP_ASSERT(!meta);
 | |
| 
 | |
|             size_type metaSize = static_cast<size_type>(sizeof(Meta)) * kPageSize;
 | |
|             size_type dataSize = static_cast<size_type>(sizeof(ValueStorage)) * kPageSize;
 | |
|             size_type alignedDataSize = align(dataSize, static_cast<size_type>(alignof(Meta)));
 | |
|             size_type alignment = std::max(static_cast<size_type>(alignof(Meta)), static_cast<size_type>(alignof(ValueStorage)));
 | |
|             // some platforms (macOS) does not support alignments smaller than `alignof(void*)`
 | |
|             // and 16 bytes seem like a nice compromise
 | |
|             alignment = std::max(alignment, 16u);
 | |
|             size_type numBytes = alignedDataSize + metaSize;
 | |
| 
 | |
|             /*
 | |
|               C++11 std::aligned_alloc
 | |
| 
 | |
|               Passing a size which is not an integral multiple of alignment or an alignment which is not valid or not supported by the
 | |
|               implementation causes the function to fail and return a null pointer (C11, as published, specified undefined behavior in
 | |
|               this case, this was corrected by DR 460)
 | |
|             */
 | |
|             numBytes = align(numBytes, alignment);
 | |
|             SLOT_MAP_ASSERT((numBytes % alignment) == 0);
 | |
|             rawMemory = SLOT_MAP_ALLOC(static_cast<size_t>(numBytes), static_cast<size_t>(alignment));
 | |
|             SLOT_MAP_ASSERT(rawMemory);
 | |
| 
 | |
|             numInactiveSlots = 0;
 | |
|             numUsedElements = 0;
 | |
|             values = reinterpret_cast<ValueStorage*>(rawMemory);
 | |
|             meta = reinterpret_cast<Meta*>(reinterpret_cast<char*>(rawMemory) + alignedDataSize);
 | |
| 
 | |
|             // TODO: remove rawMemory member
 | |
|             SLOT_MAP_ASSERT(values == rawMemory);
 | |
|             SLOT_MAP_ASSERT(values);
 | |
|             SLOT_MAP_ASSERT(meta);
 | |
|             SLOT_MAP_ASSERT(isPointerAligned(values, alignof(ValueStorage)));
 | |
|             SLOT_MAP_ASSERT(isPointerAligned(meta, alignof(Meta)));
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     static inline size_type align(size_type cursor, size_type alignment) noexcept { return (cursor + (alignment - 1)) & ~(alignment - 1); }
 | |
|     static inline bool isPointerAligned(void* cursor, size_t alignment) noexcept { return (uintptr_t(cursor) & (alignment - 1)) == 0; }
 | |
| 
 | |
|     template <typename TYPE, class... Args> static void construct(void* mem, Args&&... args)
 | |
|     {
 | |
|         new (mem) TYPE(std::forward<Args>(args)...);
 | |
|     }
 | |
|     template <typename TYPE> void destruct(TYPE* p) { p->~T(); }
 | |
| 
 | |
|     const T* getImpl(key k) const noexcept
 | |
|     {
 | |
|         index_t index = key::toIndex(k);
 | |
|         if (index > getMaxValidIndex())
 | |
|         {
 | |
|             // index out of bounds
 | |
|             return nullptr;
 | |
|         }
 | |
| 
 | |
|         PageAddr addr = getAddrFromIndex(index);
 | |
|         if (!isActivePage(addr))
 | |
|         {
 | |
|             return nullptr;
 | |
|         }
 | |
| 
 | |
|         const Meta& m = getMetaByAddr(addr);
 | |
| 
 | |
|         version_t version = key::toVersion(k);
 | |
|         if (m.version != version)
 | |
|         {
 | |
|             // version mismatch, slot has been reused
 | |
|             return nullptr;
 | |
|         }
 | |
|         SLOT_MAP_ASSERT(m.version != key::kInvalidVersion);
 | |
|         SLOT_MAP_ASSERT(m.tombstone == 0);
 | |
|         SLOT_MAP_ASSERT(m.inactive == 0);
 | |
| 
 | |
|         const ValueStorage& v = getValueByAddr(addr);
 | |
|         const T* value = reinterpret_cast<const T*>(&v);
 | |
|         return value;
 | |
|     }
 | |
| 
 | |
|     index_t appendElement()
 | |
|     {
 | |
|         if (pages.empty() || pages.back().numUsedElements == kPageSize)
 | |
|         {
 | |
|             Page& p = pages.emplace_back();
 | |
|             p.allocate();
 | |
|         }
 | |
| 
 | |
|         Page& lastPage = pages.back();
 | |
| 
 | |
|         size_type elementIndex = lastPage.numUsedElements;
 | |
|         SLOT_MAP_ASSERT(elementIndex <= kPageSize);
 | |
|         lastPage.numUsedElements++;
 | |
| 
 | |
|         Meta& m = lastPage.meta[elementIndex];
 | |
|         m.version = key::kMinVersion;
 | |
|         m.tombstone = 0;
 | |
|         m.inactive = 0;
 | |
| 
 | |
|         SLOT_MAP_ASSERT(pages.size() >= 1);
 | |
|         index_t index = static_cast<index_t>(getIndexFromAddr(PageAddr{static_cast<size_type>(pages.size()) - 1, elementIndex}));
 | |
|         return index;
 | |
|     }
 | |
| 
 | |
|     struct PageAddr
 | |
|     {
 | |
|         size_type page;
 | |
|         size_type index;
 | |
|     };
 | |
| 
 | |
|     template <typename INTEGRAL_TYPE> inline static constexpr bool isPow2(INTEGRAL_TYPE x) noexcept
 | |
|     {
 | |
|         static_assert(std::is_integral<INTEGRAL_TYPE>::value, "isPow2 must be called on an integer type.");
 | |
|         return (x & (x - 1)) == 0 && (x != 0);
 | |
|     }
 | |
| 
 | |
|     static inline PageAddr getAddrFromIndex(size_type index) noexcept
 | |
|     {
 | |
|         static_assert(isPow2(kPageSize), "kPageSize is expected to be a power of two.");
 | |
|         PageAddr res;
 | |
|         res.page = index / kPageSize;
 | |
|         res.index = index % kPageSize;
 | |
|         return res;
 | |
|     }
 | |
| 
 | |
|     // to global index
 | |
|     static inline index_t getIndexFromAddr(PageAddr addr) noexcept
 | |
|     {
 | |
|         size_type index = (addr.page * kPageSize + addr.index);
 | |
|         return index_t(index);
 | |
|     }
 | |
| 
 | |
|     const Meta& getMetaByAddrImpl(PageAddr addr) const noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(addr.page < pages.size());
 | |
|         const Page& page = pages[addr.page];
 | |
|         SLOT_MAP_ASSERT(page.meta);
 | |
|         SLOT_MAP_ASSERT(addr.index < kPageSize);
 | |
|         return page.meta[addr.index];
 | |
|     }
 | |
| 
 | |
|     const ValueStorage& getValueByAddrImpl(PageAddr addr) const noexcept
 | |
|     {
 | |
|         SLOT_MAP_ASSERT(addr.page < pages.size());
 | |
|         const Page& page = pages[addr.page];
 | |
|         SLOT_MAP_ASSERT(page.values);
 | |
|         SLOT_MAP_ASSERT(addr.index < kPageSize);
 | |
|         return page.values[addr.index];
 | |
|     }
 | |
| 
 | |
|     const Meta& getMetaByAddr(PageAddr addr) const noexcept { return getMetaByAddrImpl(addr); }
 | |
|     const ValueStorage& getValueByAddr(PageAddr addr) const noexcept { return getValueByAddrImpl(addr); }
 | |
| 
 | |
|     Meta& getMetaByAddr(PageAddr addr) noexcept
 | |
|     {
 | |
|         const Meta& constRes = getMetaByAddrImpl(addr);
 | |
|         return const_cast<Meta&>(constRes);
 | |
|     }
 | |
| 
 | |
|     ValueStorage& getValueByAddr(PageAddr addr) noexcept
 | |
|     {
 | |
|         const ValueStorage& constRes = getValueByAddrImpl(addr);
 | |
|         return const_cast<ValueStorage&>(constRes);
 | |
|     }
 | |
| 
 | |
|     bool isTombstone(size_type index) const noexcept
 | |
|     {
 | |
|         PageAddr addr = getAddrFromIndex(index);
 | |
|         SLOT_MAP_ASSERT(addr.page < pages.size());
 | |
|         const Page& page = pages[addr.page];
 | |
|         if (!page.meta)
 | |
|         {
 | |
|             return true;
 | |
|         }
 | |
|         SLOT_MAP_ASSERT(addr.index < kPageSize);
 | |
|         return (page.meta[addr.index].tombstone != 0);
 | |
|     }
 | |
| 
 | |
|     bool isActivePage(PageAddr addr) const noexcept
 | |
|     {
 | |
|         if (addr.page >= pages.size())
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|         const Page& page = pages[addr.page];
 | |
|         return (page.meta != nullptr);
 | |
|     }
 | |
| 
 | |
|     size_type getMaxValidIndex() const noexcept { return maxValidIndex; }
 | |
| 
 | |
|     void copyFrom(const slot_map& other)
 | |
|     {
 | |
|         reset();
 | |
| 
 | |
|         SLOT_MAP_ASSERT(numItems == 0);
 | |
|         SLOT_MAP_ASSERT(maxValidIndex == 0);
 | |
|         SLOT_MAP_ASSERT(pages.empty());
 | |
|         SLOT_MAP_ASSERT(freeIndices.empty());
 | |
| 
 | |
|         static_assert(std::is_standard_layout<Meta>::value && std::is_trivially_copyable<Meta>::value,
 | |
|                       "Meta is expected to be memcopyable (POD type)");
 | |
| 
 | |
|         numItems = other.numItems;
 | |
|         maxValidIndex = other.maxValidIndex;
 | |
| 
 | |
|         for (size_t pageIndex = 0; pageIndex < other.pages.size(); pageIndex++)
 | |
|         {
 | |
|             const Page& otherPage = other.pages[pageIndex];
 | |
|             if (otherPage.meta)
 | |
|             {
 | |
|                 SLOT_MAP_ASSERT(otherPage.values);
 | |
| 
 | |
|                 // active page
 | |
|                 Page& p = pages.emplace_back();
 | |
|                 p.allocate();
 | |
|                 p.numInactiveSlots = otherPage.numInactiveSlots;
 | |
|                 p.numUsedElements = otherPage.numUsedElements;
 | |
| 
 | |
|                 // copy meta
 | |
|                 size_type metaSize = static_cast<size_type>(sizeof(Meta)) * kPageSize;
 | |
|                 std::memcpy(p.meta, otherPage.meta, metaSize);
 | |
| 
 | |
|                 // copy data
 | |
|                 if constexpr (std::is_standard_layout<T>::value && std::is_trivially_copyable<T>::value)
 | |
|                 {
 | |
|                     size_type dataSize = static_cast<size_type>(sizeof(ValueStorage)) * kPageSize;
 | |
|                     std::memcpy(p.values, otherPage.values, dataSize);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     for (size_type elementIndex = 0; elementIndex < p.numUsedElements; elementIndex++)
 | |
|                     {
 | |
|                         PageAddr addr;
 | |
|                         addr.page = static_cast<size_type>(pageIndex);
 | |
|                         addr.index = elementIndex;
 | |
| 
 | |
|                         const ValueStorage& otherVal = other.getValueByAddr(addr);
 | |
|                         const T* otherV = reinterpret_cast<const T*>(&otherVal);
 | |
|                         ValueStorage& val = getValueByAddr(addr);
 | |
| 
 | |
|                         // copy constructor
 | |
|                         construct<T>(&val, *otherV);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 SLOT_MAP_ASSERT(otherPage.meta == nullptr);
 | |
|                 SLOT_MAP_ASSERT(otherPage.values == nullptr);
 | |
| 
 | |
|                 // inactive page
 | |
|                 Page& p = pages.emplace_back();
 | |
|                 p.numInactiveSlots = otherPage.numInactiveSlots;
 | |
|                 p.numUsedElements = otherPage.numUsedElements;
 | |
|                 SLOT_MAP_ASSERT(p.values == nullptr);
 | |
|                 SLOT_MAP_ASSERT(p.meta == nullptr);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void callDtors()
 | |
|     {
 | |
|         size_type numItemsDestroyed = 0;
 | |
|         for (size_t pageIndex = 0; pageIndex < pages.size(); pageIndex++)
 | |
|         {
 | |
|             Page& page = pages[pageIndex];
 | |
|             if (page.meta == nullptr)
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
|             for (size_type elementIndex = 0; elementIndex < page.numUsedElements; elementIndex++)
 | |
|             {
 | |
|                 PageAddr addr;
 | |
|                 addr.page = static_cast<size_type>(pageIndex);
 | |
|                 addr.index = static_cast<size_type>(elementIndex);
 | |
| 
 | |
|                 Meta& m = getMetaByAddr(addr);
 | |
|                 if (m.tombstone != 0)
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
|                 if constexpr (!std::is_trivially_destructible<T>::value)
 | |
|                 {
 | |
|                     ValueStorage& v = getValueByAddr(addr);
 | |
|                     destruct(reinterpret_cast<const T*>(&v));
 | |
|                 }
 | |
|                 numItemsDestroyed++;
 | |
|             }
 | |
|         }
 | |
|         SLOT_MAP_ASSERT(numItemsDestroyed == numItems);
 | |
|     }
 | |
| 
 | |
|     enum class EraseResult
 | |
|     {
 | |
|         NotFound,
 | |
|         ErasedAndIndexRecycled,
 | |
|         ErasedAndPageDeactivated,
 | |
|     };
 | |
| 
 | |
|     template <bool VERSION_CHECK> EraseResult eraseImpl(key k)
 | |
|     {
 | |
|         index_t index = key::toIndex(k);
 | |
|         if (index > getMaxValidIndex())
 | |
|         {
 | |
|             return EraseResult::NotFound;
 | |
|         }
 | |
| 
 | |
|         PageAddr addr = getAddrFromIndex(index);
 | |
|         if (!isActivePage(addr))
 | |
|         {
 | |
|             return EraseResult::NotFound;
 | |
|         }
 | |
| 
 | |
|         Meta& m = getMetaByAddr(addr);
 | |
|         if (m.tombstone != 0)
 | |
|         {
 | |
|             return EraseResult::NotFound;
 | |
|         }
 | |
| 
 | |
|         version_t slotVersion = m.version;
 | |
| 
 | |
|         if constexpr (VERSION_CHECK)
 | |
|         {
 | |
|             version_t version = key::toVersion(k);
 | |
|             if (slotVersion != version || slotVersion == key::kInvalidVersion || version == key::kInvalidVersion)
 | |
|             {
 | |
|                 return EraseResult::NotFound;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         bool deactivateSlot = (slotVersion == key::kMaxVersion);
 | |
|         if (deactivateSlot)
 | |
|         {
 | |
|             // version overflow = deactivate slot
 | |
|             m.inactive = 1;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // increase version
 | |
|             slotVersion = key::increaseVersion(slotVersion);
 | |
|             SLOT_MAP_ASSERT(slotVersion != key::kInvalidVersion);
 | |
|             SLOT_MAP_ASSERT(slotVersion > m.version);
 | |
|         }
 | |
| 
 | |
|         m.version = slotVersion;
 | |
|         m.tombstone = 1;
 | |
| 
 | |
|         if constexpr (!std::is_trivially_destructible<T>::value)
 | |
|         {
 | |
|             ValueStorage& v = getValueByAddr(addr);
 | |
|             const T* pv = reinterpret_cast<const T*>(&v);
 | |
|             destruct(pv);
 | |
|         }
 | |
|         numItems--;
 | |
| 
 | |
|         if (deactivateSlot)
 | |
|         {
 | |
|             Page& page = pages[addr.page];
 | |
|             page.numInactiveSlots++;
 | |
|             if (page.numInactiveSlots == kPageSize)
 | |
|             {
 | |
|                 page.deallocate();
 | |
|                 return EraseResult::ErasedAndPageDeactivated;
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // recycle index id (note: tag is not saved!)
 | |
|             freeIndices.emplace_back(key::clearTagAndUpdateVersion(k, slotVersion));
 | |
|         }
 | |
|         return EraseResult::ErasedAndIndexRecycled;
 | |
|     }
 | |
| 
 | |
|   public:
 | |
|     slot_map()
 | |
|         : numItems(0)
 | |
|         , maxValidIndex(0)
 | |
|     {
 | |
|     }
 | |
|     ~slot_map() { callDtors(); }
 | |
| 
 | |
|     /*
 | |
|       Returns true if the slot map contains a specific key
 | |
|     */
 | |
|     bool has_key(key k) const noexcept
 | |
|     {
 | |
|         index_t index = key::toIndex(k);
 | |
|         if (index > getMaxValidIndex())
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|         version_t version = key::toVersion(k);
 | |
|         PageAddr addr = getAddrFromIndex(index);
 | |
|         if (!isActivePage(addr))
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|         const Meta& m = getMetaByAddr(addr);
 | |
|         return (m.version == version);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Clears the slot map and releases any allocated memory.
 | |
|       Note: By calling this function, you must guarantee that no handles are in use!
 | |
|       Otherwise calling this function might be dangerous and lead to key "collisions".
 | |
|       You might consider using "clear()" instead.
 | |
|     */
 | |
|     void reset()
 | |
|     {
 | |
|         callDtors();
 | |
| 
 | |
|         numItems = 0;
 | |
|         maxValidIndex = 0;
 | |
| 
 | |
|         // Release used memory (using swap trick)
 | |
|         if (!pages.empty())
 | |
|         {
 | |
|             std::vector<Page, stl::Allocator<Page>> tmpPages;
 | |
|             pages.swap(tmpPages);
 | |
|         }
 | |
| 
 | |
|         if (!freeIndices.empty())
 | |
|         {
 | |
|             std::deque<key, stl::Allocator<key>> tmpFreeIndices;
 | |
|             freeIndices.swap(tmpFreeIndices);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Clears the slot map but keeps the allocated memory for reuse.
 | |
|       Automatically increases version for all the removed elements (the same as calling "erase()" for all existing elements)
 | |
|     */
 | |
|     void clear()
 | |
|     {
 | |
|         for (size_t pageIndex = 0; pageIndex < pages.size(); pageIndex++)
 | |
|         {
 | |
|             Page& page = pages[pageIndex];
 | |
|             if (page.meta == nullptr)
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
|             for (size_type elementIndex = 0; elementIndex < page.numUsedElements; elementIndex++)
 | |
|             {
 | |
|                 index_t index = getIndexFromAddr(PageAddr{static_cast<size_type>(pageIndex), elementIndex});
 | |
|                 // note: version doesn't matter here
 | |
|                 EraseResult res = eraseImpl<false>(key::make(key::kMinVersion, index));
 | |
|                 if (res == EraseResult::ErasedAndPageDeactivated)
 | |
|                 {
 | |
|                     // noting left on this page - go to the next page
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         SLOT_MAP_ASSERT(numItems == 0);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       If key exists returns a const pointer to the value corresponding to the given key or returns null elsewere.
 | |
|     */
 | |
|     const T* get(key k) const noexcept { return getImpl(k); }
 | |
| 
 | |
|     /*
 | |
|       If key exists returns a pointer to the value corresponding to the given key or returns null elsewere.
 | |
|     */
 | |
|     T* get(key k) noexcept
 | |
|     {
 | |
|         const T* constRes = getImpl(k);
 | |
|         return const_cast<T*>(constRes);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Constructs element in-place and returns a unique key that can be used to access this value.
 | |
|     */
 | |
|     template <class... Args> key emplace(Args&&... args)
 | |
|     {
 | |
|         // Use recycled IDs only if we accumulated enough of them
 | |
|         if (static_cast<size_type>(freeIndices.size()) > kMinFreeIndices)
 | |
|         {
 | |
|             key k = freeIndices.front();
 | |
|             freeIndices.pop_front();
 | |
| 
 | |
|             index_t index = key::toIndex(k);
 | |
|             SLOT_MAP_ASSERT(index <= getMaxValidIndex());
 | |
| 
 | |
|             PageAddr addr = getAddrFromIndex(index);
 | |
|             Meta& m = getMetaByAddr(addr);
 | |
|             SLOT_MAP_ASSERT(m.inactive == 0);
 | |
|             SLOT_MAP_ASSERT(m.tombstone != 0);
 | |
|             SLOT_MAP_ASSERT(k.get_tag() == 0);
 | |
| 
 | |
|             m.tombstone = 0;
 | |
| 
 | |
|             ValueStorage& v = getValueByAddr(addr);
 | |
|             construct<T>(&v, std::forward<Args>(args)...);
 | |
|             numItems++;
 | |
|             return k;
 | |
|         }
 | |
| 
 | |
|         // allocate new item
 | |
|         index_t index = appendElement();
 | |
|         maxValidIndex = std::max(maxValidIndex, index);
 | |
| 
 | |
|         PageAddr addr = getAddrFromIndex(index);
 | |
|         const Meta& m = getMetaByAddr(addr);
 | |
|         SLOT_MAP_ASSERT(m.tombstone == 0);
 | |
| 
 | |
|         ValueStorage& v = getValueByAddr(addr);
 | |
|         construct<T>(&v, std::forward<Args>(args)...);
 | |
|         numItems++;
 | |
|         key k = key::make(m.version, index);
 | |
|         return k;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Removes element (if such key exists) from the slot map.
 | |
|     */
 | |
|     void erase(key k) { eraseImpl<true>(k); }
 | |
| 
 | |
|     /*
 | |
|       Removes element (if such key exists) from the slot map, returning the value at the key if the key was not previously removed.
 | |
|     */
 | |
|     std::optional<T> pop(key k)
 | |
|     {
 | |
|         T* val = get(k);
 | |
|         if (val == nullptr)
 | |
|         {
 | |
|             return {};
 | |
|         }
 | |
| 
 | |
|         T res(std::move(*val));
 | |
|         eraseImpl<true>(k);
 | |
|         return res;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Returns true if the slot map is empty.
 | |
|     */
 | |
|     bool empty() const noexcept { return (numItems == 0); }
 | |
| 
 | |
|     /*
 | |
|       Returns the number of elements in the slot map.
 | |
|     */
 | |
|     size_type size() const noexcept { return numItems; }
 | |
| 
 | |
|     /*
 | |
|       Exchanges the content of the slot map by the content of another slot map object of the same type.
 | |
|     */
 | |
|     void swap(slot_map& other) noexcept
 | |
|     {
 | |
|         pages.swap(other.pages);
 | |
|         freeIndices.swap(other.freeIndices);
 | |
|         std::swap(numItems, other.numItems);
 | |
|         std::swap(maxValidIndex, other.maxValidIndex);
 | |
|     }
 | |
| 
 | |
|     // copy constructor
 | |
|     slot_map(const slot_map& other)
 | |
|         : numItems(0)
 | |
|         , maxValidIndex(0)
 | |
|     {
 | |
|         copyFrom(other);
 | |
|     }
 | |
| 
 | |
|     // copy assignment
 | |
|     slot_map& operator=(const slot_map& other)
 | |
|     {
 | |
|         copyFrom(other);
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     // move constructor
 | |
|     slot_map(slot_map&& other) noexcept
 | |
|         : numItems(other.numItems)
 | |
|         , maxValidIndex(other.maxValidIndex)
 | |
|     {
 | |
|         std::swap(pages, other.pages);
 | |
|         std::swap(freeIndices, other.freeIndices);
 | |
|         other.numItems = 0;
 | |
|         other.maxValidIndex = 0;
 | |
|     }
 | |
| 
 | |
|     // move asignment
 | |
|     slot_map& operator=(slot_map&& other) noexcept
 | |
|     {
 | |
|         // reset and swap
 | |
|         reset();
 | |
| 
 | |
|         pages.swap(other.pages);
 | |
|         freeIndices.swap(other.freeIndices);
 | |
|         std::swap(numItems, other.numItems);
 | |
|         std::swap(maxValidIndex, other.maxValidIndex);
 | |
|         return *this;
 | |
|     }
 | |
| 
 | |
|     struct Stats
 | |
|     {
 | |
|         size_type numPagesTotal = 0;
 | |
|         size_type numInactivePages = 0;
 | |
|         size_type numActivePages = 0;
 | |
| 
 | |
|         size_type numItemsTotal = 0;
 | |
|         size_type numAliveItems = 0;
 | |
|         size_type numTombstoneItems = 0;
 | |
|         size_type numInactiveItems = 0;
 | |
|     };
 | |
| 
 | |
|     /*
 | |
|       Returns the internal stats for debug purposes
 | |
| 
 | |
|       Note: O(n) complexity!
 | |
|     */
 | |
|     Stats debug_stats() const noexcept
 | |
|     {
 | |
|         Stats stats;
 | |
|         stats.numPagesTotal = static_cast<size_type>(pages.size());
 | |
| 
 | |
|         for (size_t pageIndex = 0; pageIndex < pages.size(); pageIndex++)
 | |
|         {
 | |
|             const Page& page = pages[pageIndex];
 | |
|             if (page.meta == nullptr)
 | |
|             {
 | |
|                 stats.numInactivePages++;
 | |
|                 continue;
 | |
|             }
 | |
|             stats.numActivePages++;
 | |
| 
 | |
|             stats.numItemsTotal += page.numUsedElements;
 | |
|             for (size_type elementIndex = 0; elementIndex < page.numUsedElements; elementIndex++)
 | |
|             {
 | |
|                 PageAddr addr;
 | |
|                 addr.page = static_cast<size_type>(pageIndex);
 | |
|                 addr.index = static_cast<size_type>(elementIndex);
 | |
|                 const Meta& m = getMetaByAddr(addr);
 | |
|                 if (m.inactive != 0)
 | |
|                 {
 | |
|                     stats.numInactiveItems++;
 | |
|                 }
 | |
|                 else if (m.tombstone != 0)
 | |
|                 {
 | |
|                     stats.numTombstoneItems++;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     stats.numAliveItems++;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         return stats;
 | |
|     }
 | |
| 
 | |
|   public:
 | |
|     // iterators.....
 | |
| 
 | |
|     // for(const auto& kv : slot_map.items()) {} // key/value
 | |
|     // for(const auto& v : slot_map) {} // values only
 | |
| 
 | |
|     // values iterator...................................
 | |
|     class const_values_iterator
 | |
|     {
 | |
|       private:
 | |
|         const T* getCurrent() const noexcept
 | |
|         {
 | |
|             SLOT_MAP_ASSERT(currentIndex <= slotMap->getMaxValidIndex());
 | |
|             PageAddr addr = slotMap->getAddrFromIndex(currentIndex);
 | |
|             SLOT_MAP_ASSERT(slotMap->isActivePage(addr));
 | |
|             const ValueStorage& v = slotMap->getValueByAddr(addr);
 | |
|             const T* value = reinterpret_cast<const T*>(&v);
 | |
|             return value;
 | |
|         }
 | |
| 
 | |
|       public:
 | |
|         explicit const_values_iterator(const slot_map* _slotMap, size_type index) noexcept
 | |
|             : slotMap(_slotMap)
 | |
|             , currentIndex(index)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         const T& operator*() const noexcept { return *getCurrent(); }
 | |
|         const T* operator->() const noexcept { return getCurrent(); }
 | |
| 
 | |
|         bool operator==(const const_values_iterator& other) const noexcept
 | |
|         {
 | |
|             return slotMap == other.slotMap && currentIndex == other.currentIndex;
 | |
|         }
 | |
|         bool operator!=(const const_values_iterator& other) const noexcept
 | |
|         {
 | |
|             return slotMap != other.slotMap || currentIndex != other.currentIndex;
 | |
|         }
 | |
| 
 | |
|         const_values_iterator& operator++() noexcept
 | |
|         {
 | |
|             do
 | |
|             {
 | |
|                 currentIndex++;
 | |
|             } while (currentIndex <= slotMap->getMaxValidIndex() && slotMap->isTombstone(currentIndex));
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         const_values_iterator operator++(int) noexcept
 | |
|         {
 | |
|             const_values_iterator res = *this;
 | |
|             ++*this;
 | |
|             return res;
 | |
|         }
 | |
| 
 | |
|       private:
 | |
|         const slot_map* slotMap;
 | |
|         size_type currentIndex;
 | |
|     };
 | |
| 
 | |
|     const_values_iterator begin() const noexcept
 | |
|     {
 | |
|         if (pages.empty()) return end();
 | |
| 
 | |
|         size_type index = 0;
 | |
|         while (index <= getMaxValidIndex() && isTombstone(index))
 | |
|         {
 | |
|             index++;
 | |
|         }
 | |
|         return const_values_iterator(this, index);
 | |
|     }
 | |
|     const_values_iterator end() const noexcept { return const_values_iterator(this, getMaxValidIndex() + static_cast<size_type>(1)); }
 | |
| 
 | |
|     // key-value iterator...................................
 | |
|     class const_kv_iterator
 | |
|     {
 | |
|       public:
 | |
|         // pretty much similar to std::reference_wrapper
 | |
|         template <typename TYPE> struct reference
 | |
|         {
 | |
|             TYPE* ptr = nullptr;
 | |
| 
 | |
|             explicit reference(TYPE* _ptr)
 | |
|                 : ptr(_ptr)
 | |
|             {
 | |
|             }
 | |
| 
 | |
|             reference(const reference& /*other*/) noexcept = default;
 | |
|             reference(reference&& /*other*/) noexcept = default;
 | |
|             reference& operator=(const reference& /*other*/) noexcept = default;
 | |
|             reference& operator=(reference&& /*other*/) noexcept = default;
 | |
| 
 | |
|             void set(TYPE* _ptr) noexcept { ptr = _ptr; }
 | |
| 
 | |
|             TYPE& get() const noexcept
 | |
|             {
 | |
|                 SLOT_MAP_ASSERT(ptr);
 | |
|                 return *ptr;
 | |
|             }
 | |
| 
 | |
|             operator TYPE&() const noexcept { return get(); }
 | |
|         };
 | |
| 
 | |
|         using KeyValue = std::pair<key, const reference<const T>>;
 | |
| 
 | |
|       private:
 | |
|         void updateTmpKV() const noexcept
 | |
|         {
 | |
|             SLOT_MAP_ASSERT(currentIndex <= slotMap->getMaxValidIndex());
 | |
|             PageAddr addr = slotMap->getAddrFromIndex(currentIndex);
 | |
|             SLOT_MAP_ASSERT(slotMap->isActivePage(addr));
 | |
|             const Meta& m = slotMap->getMetaByAddr(addr);
 | |
|             const ValueStorage& v = slotMap->getValueByAddr(addr);
 | |
|             const T* value = reinterpret_cast<const T*>(&v);
 | |
|             tmpKv.first = key::make(m.version, index_t(currentIndex));
 | |
|             const reference<const T>& ref = tmpKv.second;
 | |
|             const_cast<reference<const T>&>(ref).set(value);
 | |
|         }
 | |
| 
 | |
|       public:
 | |
|         explicit const_kv_iterator(const slot_map* _slotMap, size_type index) noexcept
 | |
|             : slotMap(_slotMap)
 | |
|             , currentIndex(index)
 | |
|             , tmpKv(key::invalid(), reference<const T>(nullptr))
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         const KeyValue& operator*() const noexcept
 | |
|         {
 | |
|             updateTmpKV();
 | |
|             return tmpKv;
 | |
|         }
 | |
| 
 | |
|         const KeyValue* operator->() const noexcept
 | |
|         {
 | |
|             updateTmpKV();
 | |
|             return &tmpKv;
 | |
|         }
 | |
| 
 | |
|         bool operator==(const const_kv_iterator& other) const noexcept
 | |
|         {
 | |
|             return slotMap == other.slotMap && currentIndex == other.currentIndex;
 | |
|         }
 | |
|         bool operator!=(const const_kv_iterator& other) const noexcept
 | |
|         {
 | |
|             return slotMap != other.slotMap || currentIndex != other.currentIndex;
 | |
|         }
 | |
| 
 | |
|         const_kv_iterator& operator++() noexcept
 | |
|         {
 | |
|             do
 | |
|             {
 | |
|                 currentIndex++;
 | |
|             } while (currentIndex <= slotMap->getMaxValidIndex() && slotMap->isTombstone(currentIndex));
 | |
|             return *this;
 | |
|         }
 | |
| 
 | |
|         const_kv_iterator operator++(int) noexcept
 | |
|         {
 | |
|             const_kv_iterator res = *this;
 | |
|             ++*this;
 | |
|             return res;
 | |
|         }
 | |
| 
 | |
|       private:
 | |
|         const slot_map* slotMap;
 | |
|         size_type currentIndex;
 | |
|         // unfortunately we need this to make it look like standard STL iterator (operator* and operator->)
 | |
|         mutable KeyValue tmpKv;
 | |
|     };
 | |
| 
 | |
|     // proxy items object
 | |
|     class Items
 | |
|     {
 | |
|         const slot_map* slotMap;
 | |
| 
 | |
|       public:
 | |
|         explicit Items(const slot_map* _slotMap) noexcept
 | |
|             : slotMap(_slotMap)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         const_kv_iterator begin() const noexcept
 | |
|         {
 | |
|             if (slotMap->pages.empty()) return end();
 | |
|             size_type index = 0;
 | |
|             while (index <= slotMap->getMaxValidIndex() && slotMap->isTombstone(index))
 | |
|             {
 | |
|                 index++;
 | |
|             }
 | |
|             return const_kv_iterator(slotMap, index);
 | |
|         }
 | |
|         const_kv_iterator end() const noexcept
 | |
|         {
 | |
|             return const_kv_iterator(slotMap, slotMap->getMaxValidIndex() + static_cast<size_type>(1));
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     Items items() const noexcept { return Items(this); }
 | |
| 
 | |
|   private:
 | |
|     std::vector<Page, stl::Allocator<Page>> pages;
 | |
|     std::deque<key, stl::Allocator<key>> freeIndices;
 | |
|     size_type numItems;
 | |
|     index_t maxValidIndex;
 | |
| };
 | |
| 
 | |
| template <class T, size_t PAGESIZE = 4096, size_t MINFREEINDICES = 64>
 | |
| using slot_map32 = slot_map<T, dod::slot_map_key32<T>, PAGESIZE, MINFREEINDICES>;
 | |
| 
 | |
| template <class T, size_t PAGESIZE = 4096, size_t MINFREEINDICES = 64>
 | |
| using slot_map64 = slot_map<T, dod::slot_map_key64<T>, PAGESIZE, MINFREEINDICES>;
 | |
| 
 | |
| } // namespace dod
 | |
| 
 | |
| // std::hash support
 | |
| namespace std
 | |
| {
 | |
| template <typename T> struct hash<typename dod::slot_map_key64<T>>
 | |
| {
 | |
|     size_t operator()(const typename dod::slot_map_key64<T>& key) const noexcept { return key.hash(); }
 | |
| };
 | |
| 
 | |
| template <typename T> struct hash<typename dod::slot_map_key32<T>>
 | |
| {
 | |
|     size_t operator()(const typename dod::slot_map_key32<T>& key) const noexcept { return key.hash(); }
 | |
| };
 | |
| 
 | |
| } // namespace std
 | 
